@threenative/core 0.3.0 → 0.3.2
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/README.md +10 -0
- package/capabilities.json +1881 -131
- package/dist/assets-kyoF7JlJ.d.ts +103 -0
- package/dist/{audio-Dp2mXpD3.d.ts → audio-BFiGneTL.d.ts} +62 -0
- package/dist/canvas-layer-BLVijiUJ.d.ts +62 -0
- package/dist/{game-CYIaKhgl.d.ts → game-XGrTzapq.d.ts} +350 -164
- package/dist/gpu-readback-D2iRvoe9.d.ts +112 -0
- package/dist/hot.d.ts +5 -3
- package/dist/hot.js +5 -1
- package/dist/index.d.ts +813 -143
- package/dist/index.js +4548 -941
- package/dist/net.d.ts +65 -0
- package/dist/net.js +643 -0
- package/dist/playtest.d.ts +29 -5
- package/dist/playtest.js +181 -35
- package/dist/react.d.ts +4 -2
- package/dist/{canvas-layer-CtrZHgIh.d.ts → renderer-C6hqZpoG.d.ts} +237 -75
- package/dist/world.d.ts +203 -4
- package/dist/world.js +2536 -25
- package/gpl/LICENSE.GPL +117 -0
- package/gpl/convert.py +192 -0
- package/gpl/recipes/_common.py +169 -0
- package/gpl/recipes/bake_ao.py +111 -0
- package/gpl/recipes/decimate.py +64 -0
- package/gpl/recipes/retarget.py +131 -0
- package/gpl/recipes/unwrap.py +71 -0
- package/mcp/blender-server.mjs +632 -0
- package/mcp/blender.mjs +27 -0
- package/mcp/engine-server.mjs +271 -23
- package/mcp/engine.mjs +15 -7
- package/mcp/install.d.mts +37 -0
- package/mcp/install.mjs +94 -26
- package/mcp/servers.d.mts +34 -0
- package/mcp/servers.mjs +110 -9
- package/package.json +36 -8
- package/patches/three@0.185.1.patch +249 -14
- package/scripts/ensure-mcp.mjs +20 -13
- package/scripts/bundle-engine-mcp.mjs +0 -15
- package/scripts/generate-version.mjs +0 -13
package/mcp/servers.mjs
CHANGED
|
@@ -24,6 +24,13 @@ export const MCP_SERVERS = Object.freeze({
|
|
|
24
24
|
command: "node",
|
|
25
25
|
args: Object.freeze(["./node_modules/@threenative/core/mcp/engine.mjs"]),
|
|
26
26
|
}),
|
|
27
|
+
// Drives an installed Blender headlessly so a downloaded .fbx becomes a GLB. It ships no
|
|
28
|
+
// Blender and probes for one only when a tool is called, so a project that never converts a
|
|
29
|
+
// model pays nothing for having this server wired.
|
|
30
|
+
"threenative-blender": Object.freeze({
|
|
31
|
+
command: "node",
|
|
32
|
+
args: Object.freeze(["./node_modules/@threenative/core/mcp/blender.mjs"]),
|
|
33
|
+
}),
|
|
27
34
|
});
|
|
28
35
|
|
|
29
36
|
/** The external package each shim launches. Core installs asset and sculpt transitively and bundles
|
|
@@ -31,15 +38,66 @@ export const MCP_SERVERS = Object.freeze({
|
|
|
31
38
|
* separately. The pinned engine version remains an emergency fallback for a legacy development
|
|
32
39
|
* checkout whose bundle has not been built. */
|
|
33
40
|
export const MCP_PACKAGES = Object.freeze({
|
|
34
|
-
assets: Object.freeze({ name: "threenative-asset-mcp", version: "0.
|
|
35
|
-
sculpt: Object.freeze({ name: "threenative-sculpt-mcp", version: "0.1.
|
|
36
|
-
engine: Object.freeze({ name: "threenative-engine-mcp", version: "0.2.
|
|
41
|
+
assets: Object.freeze({ name: "threenative-asset-mcp", version: "0.8.0" }),
|
|
42
|
+
sculpt: Object.freeze({ name: "threenative-sculpt-mcp", version: "0.1.1" }),
|
|
43
|
+
engine: Object.freeze({ name: "threenative-engine-mcp", version: "0.2.2" }),
|
|
44
|
+
blender: Object.freeze({ name: "threenative-blender-mcp", version: "0.1.2" }),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/** How one host spells a stdio MCP server. Hosts agree on what to run and disagree on where to
|
|
48
|
+
* write it down, so the servers are declared once above and translated here. `key` is the property
|
|
49
|
+
* the host reads its server table from; `entry` is one server in that host's own shape. */
|
|
50
|
+
const SERVER_FORMATS = Object.freeze({
|
|
51
|
+
// Claude Code (`.mcp.json`), Cursor and the Gemini CLI all read this one.
|
|
52
|
+
mcpServers: Object.freeze({
|
|
53
|
+
key: "mcpServers",
|
|
54
|
+
entry: (server) => ({
|
|
55
|
+
command: server.command,
|
|
56
|
+
args: [...server.args],
|
|
57
|
+
...(server.env === undefined ? {} : { env: { ...server.env } }),
|
|
58
|
+
}),
|
|
59
|
+
}),
|
|
60
|
+
// VS Code names the table `servers` and requires an explicit transport per server.
|
|
61
|
+
vscode: Object.freeze({
|
|
62
|
+
key: "servers",
|
|
63
|
+
entry: (server) => ({
|
|
64
|
+
type: "stdio",
|
|
65
|
+
command: server.command,
|
|
66
|
+
args: [...server.args],
|
|
67
|
+
...(server.env === undefined ? {} : { env: { ...server.env } }),
|
|
68
|
+
}),
|
|
69
|
+
}),
|
|
70
|
+
// opencode folds the command and its arguments into one array and spells the environment
|
|
71
|
+
// `environment`. A server it does not mark enabled is written down and never started.
|
|
72
|
+
opencode: Object.freeze({
|
|
73
|
+
key: "mcp",
|
|
74
|
+
entry: (server) => ({
|
|
75
|
+
type: "local",
|
|
76
|
+
command: [server.command, ...server.args],
|
|
77
|
+
enabled: true,
|
|
78
|
+
...(server.env === undefined ? {} : { environment: { ...server.env } }),
|
|
79
|
+
}),
|
|
80
|
+
}),
|
|
81
|
+
// Zed calls them context servers and needs `source: "custom"` to accept a command of our own.
|
|
82
|
+
zed: Object.freeze({
|
|
83
|
+
key: "context_servers",
|
|
84
|
+
entry: (server) => ({
|
|
85
|
+
source: "custom",
|
|
86
|
+
command: server.command,
|
|
87
|
+
args: [...server.args],
|
|
88
|
+
...(server.env === undefined ? {} : { env: { ...server.env } }),
|
|
89
|
+
}),
|
|
90
|
+
}),
|
|
37
91
|
});
|
|
38
92
|
|
|
93
|
+
export const SERVER_FORMAT_NAMES = Object.freeze(Object.keys(SERVER_FORMATS));
|
|
94
|
+
|
|
39
95
|
/** Adds the ThreeNative servers to an existing config without touching anything else in it, and
|
|
40
96
|
* reports whether the file needs rewriting. Fails closed on a config that is not an object: an
|
|
41
|
-
* unreadable
|
|
42
|
-
export function mergeMcpServers(existing) {
|
|
97
|
+
* unreadable config is the user's, and overwriting it would lose servers we did not write. */
|
|
98
|
+
export function mergeMcpServers(existing, format = "mcpServers") {
|
|
99
|
+
const shape = SERVER_FORMATS[format];
|
|
100
|
+
if (shape === undefined) throw new Error(`Unknown MCP config format '${format}'.`);
|
|
43
101
|
const malformed =
|
|
44
102
|
existing !== undefined &&
|
|
45
103
|
(typeof existing !== "object" || existing === null || Array.isArray(existing));
|
|
@@ -47,13 +105,56 @@ export function mergeMcpServers(existing) {
|
|
|
47
105
|
throw new Error("MCP config root must be an object.");
|
|
48
106
|
}
|
|
49
107
|
const previous = existing ?? {};
|
|
50
|
-
const
|
|
108
|
+
const existingServers = previous[shape.key];
|
|
109
|
+
if (
|
|
110
|
+
existingServers !== undefined &&
|
|
111
|
+
(typeof existingServers !== "object" ||
|
|
112
|
+
existingServers === null ||
|
|
113
|
+
Array.isArray(existingServers))
|
|
114
|
+
) {
|
|
115
|
+
throw new Error(`MCP config '${shape.key}' must be an object.`);
|
|
116
|
+
}
|
|
117
|
+
const servers = { ...(existingServers ?? {}) };
|
|
118
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
119
|
+
if (typeof server !== "object" || server === null || Array.isArray(server)) {
|
|
120
|
+
throw new Error(`MCP server '${name}' in '${shape.key}' must be an object.`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
51
123
|
let changed = false;
|
|
124
|
+
const conflicts = [];
|
|
52
125
|
for (const [name, server] of Object.entries(MCP_SERVERS)) {
|
|
53
|
-
const wanted =
|
|
54
|
-
if (
|
|
126
|
+
const wanted = shape.entry(server);
|
|
127
|
+
if (Object.hasOwn(servers, name)) {
|
|
128
|
+
if (JSON.stringify(servers[name]) !== JSON.stringify(wanted)) conflicts.push(name);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
55
131
|
servers[name] = wanted;
|
|
56
132
|
changed = true;
|
|
57
133
|
}
|
|
58
|
-
return { changed, config: { ...previous,
|
|
134
|
+
return { changed, conflicts, config: { ...previous, [shape.key]: servers } };
|
|
59
135
|
}
|
|
136
|
+
|
|
137
|
+
/** One server as a Codex TOML table. Derived from `MCP_SERVERS` rather than written out beside it:
|
|
138
|
+
* two hand-maintained lists of the same three servers drift, and the drift is silent until an
|
|
139
|
+
* agent in Codex quietly has no asset tools. */
|
|
140
|
+
function codexServerBlock(name, server) {
|
|
141
|
+
const args = server.args.map((argument) => JSON.stringify(argument)).join(", ");
|
|
142
|
+
const lines = [
|
|
143
|
+
`[mcp_servers.${name}]`,
|
|
144
|
+
`command = ${JSON.stringify(server.command)}`,
|
|
145
|
+
`args = [${args}]`,
|
|
146
|
+
];
|
|
147
|
+
if (server.env !== undefined) {
|
|
148
|
+
lines.push("", `[mcp_servers.${name}.env]`);
|
|
149
|
+
for (const [key, value] of Object.entries(server.env)) {
|
|
150
|
+
lines.push(`${key} = ${JSON.stringify(value)}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return lines.join("\n");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export const CODEX_MCP_SERVERS = Object.freeze(
|
|
157
|
+
Object.entries(MCP_SERVERS).map(([name, server]) =>
|
|
158
|
+
Object.freeze({ body: codexServerBlock(name, server), name }),
|
|
159
|
+
),
|
|
160
|
+
);
|
package/package.json
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threenative/core",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"license": "MIT",
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"license": "MIT AND GPL-2.0-or-later",
|
|
5
5
|
"description": "The small vanilla Three.js game runtime foundation.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"threenative",
|
|
8
|
+
"three",
|
|
9
|
+
"threejs",
|
|
10
|
+
"webgpu",
|
|
11
|
+
"game-engine",
|
|
12
|
+
"game-framework",
|
|
13
|
+
"gamedev",
|
|
14
|
+
"typescript"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/ThreeNativeHQ/threenative#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/ThreeNativeHQ/threenative/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/ThreeNativeHQ/threenative.git",
|
|
23
|
+
"directory": "packages/core"
|
|
24
|
+
},
|
|
6
25
|
"type": "module",
|
|
7
26
|
"files": [
|
|
8
27
|
"dist",
|
|
28
|
+
"LICENSE",
|
|
9
29
|
"mcp",
|
|
10
|
-
"
|
|
30
|
+
"gpl",
|
|
31
|
+
"scripts/apply-three-patch.mjs",
|
|
32
|
+
"scripts/ensure-mcp.mjs",
|
|
33
|
+
"scripts/postinstall.mjs",
|
|
11
34
|
"patches",
|
|
12
35
|
"capabilities.json",
|
|
13
36
|
"README.md"
|
|
@@ -40,6 +63,10 @@
|
|
|
40
63
|
"types": "./dist/world.d.ts",
|
|
41
64
|
"import": "./dist/world.js"
|
|
42
65
|
},
|
|
66
|
+
"./net": {
|
|
67
|
+
"types": "./dist/net.d.ts",
|
|
68
|
+
"import": "./dist/net.js"
|
|
69
|
+
},
|
|
43
70
|
"./package.json": "./package.json"
|
|
44
71
|
},
|
|
45
72
|
"publishConfig": {
|
|
@@ -49,7 +76,7 @@
|
|
|
49
76
|
"dependencies": {
|
|
50
77
|
"three": "0.185.1",
|
|
51
78
|
"three-mesh-bvh": "0.9.14",
|
|
52
|
-
"threenative-asset-mcp": "0.
|
|
79
|
+
"threenative-asset-mcp": "0.8.0",
|
|
53
80
|
"threenative-sculpt-mcp": "0.1.1",
|
|
54
81
|
"zustand": "5.0.14"
|
|
55
82
|
},
|
|
@@ -62,11 +89,12 @@
|
|
|
62
89
|
"react-reconciler": "0.33.0",
|
|
63
90
|
"tsup": "8.5.1",
|
|
64
91
|
"typescript": "5.9.3",
|
|
65
|
-
"
|
|
66
|
-
"threenative
|
|
92
|
+
"threenative-blender-mcp": "0.1.2",
|
|
93
|
+
"@threenative/playtest": "0.3.2",
|
|
94
|
+
"threenative-engine-mcp": "0.2.2"
|
|
67
95
|
},
|
|
68
96
|
"peerDependencies": {
|
|
69
|
-
"@threenative/playtest": ">=0.3.
|
|
97
|
+
"@threenative/playtest": ">=0.3.2 <0.4.0",
|
|
70
98
|
"react": ">=19.2.0 <20",
|
|
71
99
|
"react-reconciler": ">=0.33.0 <0.34"
|
|
72
100
|
},
|
|
@@ -83,7 +111,7 @@
|
|
|
83
111
|
},
|
|
84
112
|
"scripts": {
|
|
85
113
|
"postinstall": "node ./scripts/postinstall.mjs",
|
|
86
|
-
"build": "node scripts/generate-version.mjs && tsup --config tsup.config.ts && node scripts/bundle-engine-mcp.mjs && publint",
|
|
114
|
+
"build": "node scripts/generate-version.mjs && tsup --config tsup.config.ts && node scripts/bundle-engine-mcp.mjs && node scripts/bundle-blender-mcp.mjs && publint",
|
|
87
115
|
"typecheck": "tsc --noEmit",
|
|
88
116
|
"test": "publint --strict"
|
|
89
117
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
diff --git a/build/three.cjs b/build/three.cjs
|
|
2
|
-
index
|
|
2
|
+
index 8939a67dd76325ccd25ee8806fc4d22b1caea88a..b0f917241f1f2cd25baa21dec20edda32176ef6a 100644
|
|
3
3
|
--- a/build/three.cjs
|
|
4
4
|
+++ b/build/three.cjs
|
|
5
5
|
@@ -26116,6 +26116,8 @@ function copyArrayContents( src, target ) {
|
|
@@ -12,7 +12,7 @@ index 8939a67d..b0f91724 100644
|
|
|
12
12
|
* Constructs a new batched mesh.
|
|
13
13
|
*
|
|
14
14
|
diff --git a/build/three.core.js b/build/three.core.js
|
|
15
|
-
index
|
|
15
|
+
index 0bb9262cd029f411933d077fd44197a51ec5e8e9..868a634b4294be5f32664912a50813d55d6dd6e7 100644
|
|
16
16
|
--- a/build/three.core.js
|
|
17
17
|
+++ b/build/three.core.js
|
|
18
18
|
@@ -26136,6 +26136,8 @@ function copyArrayContents( src, target ) {
|
|
@@ -25,7 +25,7 @@ index 0bb9262c..868a634b 100644
|
|
|
25
25
|
* Constructs a new batched mesh.
|
|
26
26
|
*
|
|
27
27
|
diff --git a/build/three.webgpu.js b/build/three.webgpu.js
|
|
28
|
-
index
|
|
28
|
+
index 2e490796d4d4aa9479b04a5c46ddbf31cc05b1a1..d05599255744ccfca19dbfd0433da612583ce6ea 100644
|
|
29
29
|
--- a/build/three.webgpu.js
|
|
30
30
|
+++ b/build/three.webgpu.js
|
|
31
31
|
@@ -18500,7 +18500,8 @@ const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder ) => {
|
|
@@ -86,7 +86,56 @@ index 2e490796..325b497a 100644
|
|
|
86
86
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
@@ -
|
|
89
|
+
@@ -29911,6 +29928,18 @@ class RenderObject {
|
|
90
|
+
*/
|
|
91
|
+
this._sourceMaterial = renderer._currentSourceMaterial;
|
|
92
|
+
|
|
93
|
+
+ /**
|
|
94
|
+
+ * The version of `_sourceMaterial` this render object was last validated against.
|
|
95
|
+
+ *
|
|
96
|
+
+ * An override material is shared by every object in the pass and is used as a per-object
|
|
97
|
+
+ * scratchpad, so its own `version` moves for reasons that have nothing to do with this
|
|
98
|
+
+ * render object. The source material's version is the signal that does.
|
|
99
|
+
+ *
|
|
100
|
+
+ * @type {number}
|
|
101
|
+
+ * @default 0
|
|
102
|
+
+ */
|
|
103
|
+
+ this.sourceVersion = ( this._sourceMaterial === null || this._sourceMaterial === undefined ) ? 0 : this._sourceMaterial.version;
|
|
104
|
+
+
|
|
105
|
+
/**
|
|
106
|
+
* An event listener which is defined by `RenderObjects`. It performs
|
|
107
|
+
* clean up tasks when `dispose()` on this render object.
|
|
108
|
+
@@ -30707,9 +30736,18 @@ class RenderObjects {
|
|
109
|
+
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
- if ( renderObject.version !== material.version || renderObject.needsUpdate ) {
|
|
113
|
+
+ // When the renderer is drawing through an override material, that material is shared by
|
|
114
|
+
+ // the whole pass and mutated per object, so its `version` alone answers the wrong
|
|
115
|
+
+ // question. The source material this object actually carries answers the right one.
|
|
116
|
+
+
|
|
117
|
+
+ const sourceMaterial = this.renderer._currentSourceMaterial;
|
|
118
|
+
+ const sourceTracked = sourceMaterial !== null && sourceMaterial !== undefined;
|
|
119
|
+
+ const sourceReplaced = sourceTracked && renderObject._sourceMaterial !== sourceMaterial;
|
|
120
|
+
+ const sourceChanged = sourceTracked && ( sourceReplaced || renderObject.sourceVersion !== sourceMaterial.version );
|
|
121
|
+
|
|
122
|
+
- if ( renderObject.initialCacheKey !== renderObject.getCacheKey() ) {
|
|
123
|
+
+ if ( renderObject.version !== material.version || sourceChanged || renderObject.needsUpdate ) {
|
|
124
|
+
+
|
|
125
|
+
+ if ( sourceReplaced || renderObject.initialCacheKey !== renderObject.getCacheKey() ) {
|
|
126
|
+
|
|
127
|
+
renderObject.dispose();
|
|
128
|
+
|
|
129
|
+
@@ -30719,6 +30757,8 @@ class RenderObjects {
|
|
130
|
+
|
|
131
|
+
renderObject.version = material.version;
|
|
132
|
+
|
|
133
|
+
+ if ( sourceTracked ) renderObject.sourceVersion = sourceMaterial.version;
|
|
134
|
+
+
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
}
|
|
138
|
+
@@ -39725,7 +39765,8 @@ class VelocityNode extends TempNode {
|
|
90
139
|
|
|
91
140
|
const previousModelMatrix = getPreviousMatrix( object );
|
|
92
141
|
|
|
@@ -96,7 +145,7 @@ index 2e490796..325b497a 100644
|
|
|
96
145
|
|
|
97
146
|
//
|
|
98
147
|
|
|
99
|
-
@@ -53841,7 +
|
|
148
|
+
@@ -53841,7 +53882,7 @@ class NodeBuilder {
|
|
100
149
|
|
|
101
150
|
const mrt = this.renderer.getMRT();
|
|
102
151
|
|
|
@@ -105,7 +154,58 @@ index 2e490796..325b497a 100644
|
|
|
105
154
|
|
|
106
155
|
}
|
|
107
156
|
|
|
108
|
-
@@ -
|
|
157
|
+
@@ -62731,6 +62772,7 @@ class Renderer {
|
|
158
|
+
let materialDisplacementMap;
|
|
159
|
+
let materialDisplacementScale;
|
|
160
|
+
let materialDisplacementBias;
|
|
161
|
+
+ let materialAlphaTest;
|
|
162
|
+
|
|
163
|
+
const previousSourceMaterial = this._currentSourceMaterial;
|
|
164
|
+
|
|
165
|
+
@@ -62756,6 +62798,7 @@ class Renderer {
|
|
166
|
+
materialDisplacementMap = overrideMaterial.displacementMap;
|
|
167
|
+
materialDisplacementScale = overrideMaterial.displacementScale;
|
|
168
|
+
materialDisplacementBias = overrideMaterial.displacementBias;
|
|
169
|
+
+ materialAlphaTest = overrideMaterial.alphaTest;
|
|
170
|
+
|
|
171
|
+
if ( material.positionNode && material.positionNode.isNode ) {
|
|
172
|
+
|
|
173
|
+
@@ -62763,7 +62806,15 @@ class Renderer {
|
|
174
|
+
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
- overrideMaterial.alphaTest = material.alphaTest;
|
|
178
|
+
+ // `Material.alphaTest`'s setter bumps `version` whenever alpha-test-ness flips, and this
|
|
179
|
+
+ // override material is shared by every caster in the pass. A scene that mixes alpha-tested
|
|
180
|
+
+ // casters with opaque ones therefore moves that shared version several times per frame,
|
|
181
|
+
+ // and every OTHER render object drawn with it then fails the version gate in
|
|
182
|
+
+ // `RenderObjects.get` and recomputes a full material cache key that always matches.
|
|
183
|
+
+ // Writing the backing field leaves the `alphaTest` getter — and so the cache key — still
|
|
184
|
+
+ // reporting this caster's value, while each render object tracks the staleness signal
|
|
185
|
+
+ // that belongs to it: its own source material's version.
|
|
186
|
+
+ overrideMaterial._alphaTest = material.alphaTest;
|
|
187
|
+
overrideMaterial.alphaMap = material.alphaMap;
|
|
188
|
+
overrideMaterial.displacementMap = material.displacementMap;
|
|
189
|
+
overrideMaterial.displacementScale = material.displacementScale;
|
|
190
|
+
@@ -62825,6 +62876,7 @@ class Renderer {
|
|
191
|
+
scene.overrideMaterial.displacementMap = materialDisplacementMap;
|
|
192
|
+
scene.overrideMaterial.displacementScale = materialDisplacementScale;
|
|
193
|
+
scene.overrideMaterial.displacementBias = materialDisplacementBias;
|
|
194
|
+
+ scene.overrideMaterial._alphaTest = materialAlphaTest;
|
|
195
|
+
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@@ -79586,7 +79638,8 @@ class WGSLNodeBuilder extends NodeBuilder {
|
|
199
|
+
|
|
200
|
+
uniformGPU = buffer;
|
|
201
|
+
|
|
202
|
+
- uniformNode.name = name ? name : 'NodeBuffer_' + uniformNode.id;
|
|
203
|
+
+ // The base builder supplies a unique name within this program. A process-wide
|
|
204
|
+
+ // allocation id changes otherwise identical shader source for every buffer.
|
|
205
|
+
|
|
206
|
+
} else {
|
|
207
|
+
|
|
208
|
+
@@ -83814,6 +83867,14 @@ class WebGPUBackend extends Backend {
|
|
109
209
|
*/
|
|
110
210
|
this.compatibilityMode = null;
|
|
111
211
|
|
|
@@ -120,7 +220,7 @@ index 2e490796..325b497a 100644
|
|
|
120
220
|
/**
|
|
121
221
|
* A reference to the device.
|
|
122
222
|
*
|
|
123
|
-
@@ -83926,7 +
|
|
223
|
+
@@ -83926,7 +83987,9 @@ class WebGPUBackend extends Backend {
|
|
124
224
|
xrCompatible: renderer.xr.enabled
|
|
125
225
|
};
|
|
126
226
|
|
|
@@ -132,7 +232,7 @@ index 2e490796..325b497a 100644
|
|
|
132
232
|
if ( adapter === null ) {
|
|
133
233
|
|
|
134
234
|
diff --git a/src/nodes/accessors/Batch.js b/src/nodes/accessors/Batch.js
|
|
135
|
-
index
|
|
235
|
+
index ab62e98dadab2539d7e1d663fd42267bbc8ebcaa..2cf4bb6039853d5c922e89041419250b243694ef 100644
|
|
136
236
|
--- a/src/nodes/accessors/Batch.js
|
|
137
237
|
+++ b/src/nodes/accessors/Batch.js
|
|
138
238
|
@@ -1,6 +1,6 @@
|
|
@@ -178,7 +278,7 @@ index ab62e98d..2cf4bb60 100644
|
|
|
178
278
|
-
|
|
179
279
|
-
|
|
180
280
|
diff --git a/src/nodes/accessors/Instance.js b/src/nodes/accessors/Instance.js
|
|
181
|
-
index
|
|
281
|
+
index b89834b9166c75fd946ade24db265bf8c367d244..c7b7bdd7a31d852a581d4bc75a1e1afc1d214e5e 100644
|
|
182
282
|
--- a/src/nodes/accessors/Instance.js
|
|
183
283
|
+++ b/src/nodes/accessors/Instance.js
|
|
184
284
|
@@ -220,7 +220,8 @@ export const instance = /*@__PURE__*/ Fn( ( [ matrices, colors = null ], builder
|
|
@@ -192,7 +292,7 @@ index b89834b9..c7b7bdd7 100644
|
|
|
192
292
|
} );
|
|
193
293
|
|
|
194
294
|
diff --git a/src/nodes/accessors/Skinning.js b/src/nodes/accessors/Skinning.js
|
|
195
|
-
index
|
|
295
|
+
index 2356aed8656dceb1ea74b73ea2e739371a960484..aaeea633a8ece291be5faf868e312867ddad01b6 100644
|
|
196
296
|
--- a/src/nodes/accessors/Skinning.js
|
|
197
297
|
+++ b/src/nodes/accessors/Skinning.js
|
|
198
298
|
@@ -149,7 +149,8 @@ export const skinning = /*@__PURE__*/ Fn( ( [ skinnedMesh ], builder ) => {
|
|
@@ -216,7 +316,7 @@ index 2356aed8..aaeea633 100644
|
|
|
216
316
|
}
|
|
217
317
|
|
|
218
318
|
diff --git a/src/nodes/accessors/VelocityNode.js b/src/nodes/accessors/VelocityNode.js
|
|
219
|
-
index
|
|
319
|
+
index 373548b0b587e7da3ef0993bfe316af420e7f66c..c48002abbce20ee8b29908cf314d7b03afe0db03 100644
|
|
220
320
|
--- a/src/nodes/accessors/VelocityNode.js
|
|
221
321
|
+++ b/src/nodes/accessors/VelocityNode.js
|
|
222
322
|
@@ -106,7 +106,8 @@ class VelocityNode extends TempNode {
|
|
@@ -230,7 +330,7 @@ index 373548b0..c48002ab 100644
|
|
|
230
330
|
//
|
|
231
331
|
|
|
232
332
|
diff --git a/src/nodes/core/NodeBuilder.js b/src/nodes/core/NodeBuilder.js
|
|
233
|
-
index
|
|
333
|
+
index be00b81ef36e1af83b0066603e616f8f1f81a115..1990da8fe8f1f29a7b53030756dfd966d8de3cb6 100644
|
|
234
334
|
--- a/src/nodes/core/NodeBuilder.js
|
|
235
335
|
+++ b/src/nodes/core/NodeBuilder.js
|
|
236
336
|
@@ -3450,7 +3450,7 @@ class NodeBuilder {
|
|
@@ -243,7 +343,7 @@ index be00b81e..1990da8f 100644
|
|
|
243
343
|
}
|
|
244
344
|
|
|
245
345
|
diff --git a/src/objects/BatchedMesh.js b/src/objects/BatchedMesh.js
|
|
246
|
-
index
|
|
346
|
+
index 2ef7cb257438826059eb2408b3756c4891f06a10..35cc0ffe83a69ea9fa5fca6dec2d820ca193f88d 100644
|
|
247
347
|
--- a/src/objects/BatchedMesh.js
|
|
248
348
|
+++ b/src/objects/BatchedMesh.js
|
|
249
349
|
@@ -181,6 +181,8 @@ function copyArrayContents( src, target ) {
|
|
@@ -255,8 +355,129 @@ index 2ef7cb25..35cc0ffe 100644
|
|
|
255
355
|
/**
|
|
256
356
|
* Constructs a new batched mesh.
|
|
257
357
|
*
|
|
358
|
+
diff --git a/src/renderers/common/Bindings.js b/src/renderers/common/Bindings.js
|
|
359
|
+
index 5f3d213a016b37db3b2e5d022da3dd0234d86f6d..7a9a168f29773f37ef9a51ef9f07ade9f3ea95ef 100644
|
|
360
|
+
--- a/src/renderers/common/Bindings.js
|
|
361
|
+
+++ b/src/renderers/common/Bindings.js
|
|
362
|
+
@@ -206,10 +206,14 @@ class Bindings extends DataMap {
|
|
363
|
+
|
|
364
|
+
} else if ( binding.isSampledTexture ) {
|
|
365
|
+
|
|
366
|
+
+ // Async node compilation can yield while a viewport node changes targets.
|
|
367
|
+
+ // Refresh its reference before creating the first GPU binding, not after.
|
|
368
|
+
+ binding.update();
|
|
369
|
+
this.textures.updateTexture( binding.texture );
|
|
370
|
+
|
|
371
|
+
} else if ( binding.isSampler ) {
|
|
372
|
+
|
|
373
|
+
+ binding.update();
|
|
374
|
+
this.textures.updateSampler( binding );
|
|
375
|
+
|
|
376
|
+
} else if ( binding.isStorageBuffer ) {
|
|
377
|
+
diff --git a/src/renderers/common/RenderObject.js b/src/renderers/common/RenderObject.js
|
|
378
|
+
index c900fa4c9654f8cf3512e96de52a2c1c83e189a6..d0a87eabffc8f667ebe9b2f0eccda0ddf3309652 100644
|
|
379
|
+
--- a/src/renderers/common/RenderObject.js
|
|
380
|
+
+++ b/src/renderers/common/RenderObject.js
|
|
381
|
+
@@ -299,6 +299,18 @@ class RenderObject {
|
|
382
|
+
*/
|
|
383
|
+
this._sourceMaterial = renderer._currentSourceMaterial;
|
|
384
|
+
|
|
385
|
+
+ /**
|
|
386
|
+
+ * The version of `_sourceMaterial` this render object was last validated against.
|
|
387
|
+
+ *
|
|
388
|
+
+ * An override material is shared by every object in the pass and is used as a per-object
|
|
389
|
+
+ * scratchpad, so its own `version` moves for reasons that have nothing to do with this
|
|
390
|
+
+ * render object. The source material's version is the signal that does.
|
|
391
|
+
+ *
|
|
392
|
+
+ * @type {number}
|
|
393
|
+
+ * @default 0
|
|
394
|
+
+ */
|
|
395
|
+
+ this.sourceVersion = ( this._sourceMaterial === null || this._sourceMaterial === undefined ) ? 0 : this._sourceMaterial.version;
|
|
396
|
+
+
|
|
397
|
+
/**
|
|
398
|
+
* An event listener which is defined by `RenderObjects`. It performs
|
|
399
|
+
* clean up tasks when `dispose()` on this render object.
|
|
400
|
+
diff --git a/src/renderers/common/RenderObjects.js b/src/renderers/common/RenderObjects.js
|
|
401
|
+
index 676cc2bd4169427ed420f2467b1e6684bd2aeebc..0fa661db793548254a15ff43f39207abcc43ef49 100644
|
|
402
|
+
--- a/src/renderers/common/RenderObjects.js
|
|
403
|
+
+++ b/src/renderers/common/RenderObjects.js
|
|
404
|
+
@@ -124,9 +124,18 @@ class RenderObjects {
|
|
405
|
+
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
- if ( renderObject.version !== material.version || renderObject.needsUpdate ) {
|
|
409
|
+
+ // When the renderer is drawing through an override material, that material is shared by
|
|
410
|
+
+ // the whole pass and mutated per object, so its `version` alone answers the wrong
|
|
411
|
+
+ // question. The source material this object actually carries answers the right one.
|
|
412
|
+
|
|
413
|
+
- if ( renderObject.initialCacheKey !== renderObject.getCacheKey() ) {
|
|
414
|
+
+ const sourceMaterial = this.renderer._currentSourceMaterial;
|
|
415
|
+
+ const sourceTracked = sourceMaterial !== null && sourceMaterial !== undefined;
|
|
416
|
+
+ const sourceReplaced = sourceTracked && renderObject._sourceMaterial !== sourceMaterial;
|
|
417
|
+
+ const sourceChanged = sourceTracked && ( sourceReplaced || renderObject.sourceVersion !== sourceMaterial.version );
|
|
418
|
+
+
|
|
419
|
+
+ if ( renderObject.version !== material.version || sourceChanged || renderObject.needsUpdate ) {
|
|
420
|
+
+
|
|
421
|
+
+ if ( sourceReplaced || renderObject.initialCacheKey !== renderObject.getCacheKey() ) {
|
|
422
|
+
|
|
423
|
+
renderObject.dispose();
|
|
424
|
+
|
|
425
|
+
@@ -136,6 +145,8 @@ class RenderObjects {
|
|
426
|
+
|
|
427
|
+
renderObject.version = material.version;
|
|
428
|
+
|
|
429
|
+
+ if ( sourceTracked ) renderObject.sourceVersion = sourceMaterial.version;
|
|
430
|
+
+
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
}
|
|
434
|
+
diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js
|
|
435
|
+
index 8dfc2ae055ad7b9d4c00e91f7d52a39e747caebe..e9d27b3ddad3b1f080a717d1f6683b60afd2b259 100644
|
|
436
|
+
--- a/src/renderers/common/Renderer.js
|
|
437
|
+
+++ b/src/renderers/common/Renderer.js
|
|
438
|
+
@@ -3550,6 +3550,7 @@ class Renderer {
|
|
439
|
+
let materialDisplacementMap;
|
|
440
|
+
let materialDisplacementScale;
|
|
441
|
+
let materialDisplacementBias;
|
|
442
|
+
+ let materialAlphaTest;
|
|
443
|
+
|
|
444
|
+
const previousSourceMaterial = this._currentSourceMaterial;
|
|
445
|
+
|
|
446
|
+
@@ -3575,6 +3576,7 @@ class Renderer {
|
|
447
|
+
materialDisplacementMap = overrideMaterial.displacementMap;
|
|
448
|
+
materialDisplacementScale = overrideMaterial.displacementScale;
|
|
449
|
+
materialDisplacementBias = overrideMaterial.displacementBias;
|
|
450
|
+
+ materialAlphaTest = overrideMaterial.alphaTest;
|
|
451
|
+
|
|
452
|
+
if ( material.positionNode && material.positionNode.isNode ) {
|
|
453
|
+
|
|
454
|
+
@@ -3582,7 +3584,15 @@ class Renderer {
|
|
455
|
+
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
- overrideMaterial.alphaTest = material.alphaTest;
|
|
459
|
+
+ // `Material.alphaTest`'s setter bumps `version` whenever alpha-test-ness flips, and this
|
|
460
|
+
+ // override material is shared by every caster in the pass. A scene that mixes alpha-tested
|
|
461
|
+
+ // casters with opaque ones therefore moves that shared version several times per frame,
|
|
462
|
+
+ // and every OTHER render object drawn with it then fails the version gate in
|
|
463
|
+
+ // `RenderObjects.get` and recomputes a full material cache key that always matches.
|
|
464
|
+
+ // Writing the backing field leaves the `alphaTest` getter — and so the cache key — still
|
|
465
|
+
+ // reporting this caster's value, while each render object tracks the staleness signal
|
|
466
|
+
+ // that belongs to it: its own source material's version.
|
|
467
|
+
+ overrideMaterial._alphaTest = material.alphaTest;
|
|
468
|
+
overrideMaterial.alphaMap = material.alphaMap;
|
|
469
|
+
overrideMaterial.displacementMap = material.displacementMap;
|
|
470
|
+
overrideMaterial.displacementScale = material.displacementScale;
|
|
471
|
+
@@ -3644,6 +3654,7 @@ class Renderer {
|
|
472
|
+
scene.overrideMaterial.displacementMap = materialDisplacementMap;
|
|
473
|
+
scene.overrideMaterial.displacementScale = materialDisplacementScale;
|
|
474
|
+
scene.overrideMaterial.displacementBias = materialDisplacementBias;
|
|
475
|
+
+ scene.overrideMaterial._alphaTest = materialAlphaTest;
|
|
476
|
+
|
|
477
|
+
}
|
|
478
|
+
|
|
258
479
|
diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js
|
|
259
|
-
index
|
|
480
|
+
index 06e203693ed9b168a2bddf773cd8ce8dc23196e4..37b5e0be09967e8acbd38e570b71eb201fc9bad7 100644
|
|
260
481
|
--- a/src/renderers/webgpu/WebGPUBackend.js
|
|
261
482
|
+++ b/src/renderers/webgpu/WebGPUBackend.js
|
|
262
483
|
@@ -102,6 +102,14 @@ class WebGPUBackend extends Backend {
|
|
@@ -285,3 +506,17 @@ index 06e20369..37b5e0be 100644
|
|
|
285
506
|
|
|
286
507
|
if ( adapter === null ) {
|
|
287
508
|
|
|
509
|
+
diff --git a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js
|
|
510
|
+
index be07536301cd47ef2544e4a54f6abe17407f52af..6144b98ddeb5d53c5cf280bb51401385a4427547 100644
|
|
511
|
+
--- a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js
|
|
512
|
+
+++ b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js
|
|
513
|
+
@@ -1323,7 +1323,8 @@ class WGSLNodeBuilder extends NodeBuilder {
|
|
514
|
+
|
|
515
|
+
uniformGPU = buffer;
|
|
516
|
+
|
|
517
|
+
- uniformNode.name = name ? name : 'NodeBuffer_' + uniformNode.id;
|
|
518
|
+
+ // The base builder supplies a unique name within this program. A process-wide
|
|
519
|
+
+ // allocation id changes otherwise identical shader source for every buffer.
|
|
520
|
+
|
|
521
|
+
} else {
|
|
522
|
+
|
package/scripts/ensure-mcp.mjs
CHANGED
|
@@ -3,31 +3,38 @@ import path from "node:path";
|
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
// Installing ThreeNative wires its MCP servers into the project. An agent authoring a game here
|
|
5
5
|
// should not have to be told the asset, sculpt and capability tools exist, or run a setup command
|
|
6
|
-
// to get them.
|
|
7
|
-
// Set THREENATIVE_SKIP_MCP_SETUP=1 to opt out.
|
|
8
|
-
import {
|
|
6
|
+
// to get them. Every host that reads a project-scoped MCP config gets one — `MCP_HOSTS` is the
|
|
7
|
+
// list. Set THREENATIVE_SKIP_MCP_SETUP=1 to opt out.
|
|
8
|
+
import { MCP_HOSTS, ensureHostMcpConfigs, installTarget } from "../mcp/install.mjs";
|
|
9
|
+
|
|
10
|
+
const LABELS = new Map(MCP_HOSTS.map((host) => [host.id, host.label]));
|
|
9
11
|
|
|
10
12
|
export function runMcpSetup(environment = process.env, cwd = process.cwd()) {
|
|
11
13
|
const skip = environment.THREENATIVE_SKIP_MCP_SETUP;
|
|
12
14
|
if (skip === "1" || skip === "true") return;
|
|
13
|
-
// Never fail an install over this. A game that cannot write
|
|
15
|
+
// Never fail an install over this. A game that cannot write its MCP configs still runs.
|
|
14
16
|
try {
|
|
15
17
|
const target = installTarget(environment, cwd);
|
|
16
18
|
if (target === undefined) return;
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
process.stdout.write(
|
|
19
|
+
const outcomes = ensureHostMcpConfigs(target);
|
|
20
|
+
const wired = [...outcomes]
|
|
21
|
+
.filter(([, outcome]) => outcome === "created" || outcome === "updated")
|
|
22
|
+
.map(([id]) => LABELS.get(id) ?? id);
|
|
23
|
+
if (wired.length > 0) {
|
|
24
|
+
process.stdout.write(
|
|
25
|
+
`threenative: wired the ThreeNative MCP servers for ${wired.join(", ")}\n`,
|
|
26
|
+
);
|
|
23
27
|
}
|
|
24
|
-
|
|
28
|
+
const unreadable = [...outcomes]
|
|
29
|
+
.filter(([, outcome]) => outcome === "unreadable")
|
|
30
|
+
.map(([id]) => LABELS.get(id) ?? id);
|
|
31
|
+
if (unreadable.length > 0) {
|
|
25
32
|
process.stdout.write(
|
|
26
|
-
`threenative: ${
|
|
33
|
+
`threenative: left ${unreadable.join(", ")} alone; its config is not valid JSON\n`,
|
|
27
34
|
);
|
|
28
35
|
}
|
|
29
36
|
} catch (error) {
|
|
30
|
-
process.stdout.write(`threenative: could not wire
|
|
37
|
+
process.stdout.write(`threenative: could not wire the MCP configs (${error})\n`);
|
|
31
38
|
}
|
|
32
39
|
}
|
|
33
40
|
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { copyFileSync, existsSync } from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
const coreRoot = path.resolve(fileURLToPath(import.meta.url), "..", "..");
|
|
7
|
-
const source = path.resolve(coreRoot, "..", "engine-mcp", "dist", "index.js");
|
|
8
|
-
const target = path.join(coreRoot, "mcp", "engine-server.mjs");
|
|
9
|
-
|
|
10
|
-
if (!existsSync(source)) {
|
|
11
|
-
throw new Error(
|
|
12
|
-
`TN_ENGINE_MCP_BUNDLE: build threenative-engine-mcp before core; missing ${source}`,
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
copyFileSync(source, target);
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// Single owner of the core version (PRD-181): package.json is the source of truth and
|
|
2
|
-
// src/version.ts is generated from it and checked in. A hand-edited constant goes stale the
|
|
3
|
-
// moment the manifest bumps; the pinning spec turns that red.
|
|
4
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
-
|
|
6
|
-
const manifest = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
7
|
-
if (typeof manifest.version !== "string" || manifest.version.length === 0) {
|
|
8
|
-
throw new Error("generate-version: package.json has no version");
|
|
9
|
-
}
|
|
10
|
-
await writeFile(
|
|
11
|
-
new URL("../src/version.ts", import.meta.url),
|
|
12
|
-
`// Generated by scripts/generate-version.mjs - do not edit. Source: package.json.\nexport const CORE_VERSION = ${JSON.stringify(manifest.version)};\n`,
|
|
13
|
-
);
|