@vgai/engine 0.5.42 → 0.5.45

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 (114) hide show
  1. package/dist/adapter/adapter-module.d.ts +28 -29
  2. package/dist/adapter/adapter-module.d.ts.map +1 -1
  3. package/dist/adapter/adapter-module.js +11 -34
  4. package/dist/adapter/authoring-seam-contract.d.ts +5 -0
  5. package/dist/adapter/authoring-seam-contract.d.ts.map +1 -1
  6. package/dist/adapter/authoring-seam-contract.js +1 -0
  7. package/dist/adapter/authoring.d.ts +16 -0
  8. package/dist/adapter/authoring.d.ts.map +1 -1
  9. package/dist/adapter/finders/finder-result.d.ts +2 -2
  10. package/dist/adapter/finders/finder-result.d.ts.map +1 -1
  11. package/dist/adapter/finders/index.d.ts +15 -2
  12. package/dist/adapter/finders/index.d.ts.map +1 -1
  13. package/dist/adapter/finders/index.js +38 -10
  14. package/dist/adapter/finders/prefabs-from-stories.d.ts.map +1 -1
  15. package/dist/adapter/finders/registry.d.ts +45 -0
  16. package/dist/adapter/finders/registry.d.ts.map +1 -0
  17. package/dist/adapter/finders/registry.js +56 -0
  18. package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
  19. package/dist/adapter/ingest/scene-capture.js +50 -20
  20. package/dist/adapter/system-adapter.d.ts +3 -1
  21. package/dist/adapter/system-adapter.d.ts.map +1 -1
  22. package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
  23. package/dist/canvas-react/pixi-react-root-factory.js +21 -11
  24. package/dist/data/vite-plugin-data.d.ts +1 -1
  25. package/dist/data/vite-plugin-data.d.ts.map +1 -1
  26. package/dist/data/vite-plugin-data.js +2 -2
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1 -1
  30. package/dist/input/host-pointer.d.ts +18 -0
  31. package/dist/input/host-pointer.d.ts.map +1 -1
  32. package/dist/input/host-pointer.js +54 -5
  33. package/dist/input/input-manager.d.ts +83 -1
  34. package/dist/input/input-manager.d.ts.map +1 -1
  35. package/dist/input/input-manager.js +357 -8
  36. package/dist/manifest/configuration-kinds.d.ts +97 -0
  37. package/dist/manifest/configuration-kinds.d.ts.map +1 -0
  38. package/dist/manifest/configuration-kinds.js +148 -0
  39. package/dist/manifest/index.d.ts +2 -1
  40. package/dist/manifest/index.d.ts.map +1 -1
  41. package/dist/manifest/index.js +4 -3
  42. package/dist/manifest/kind-modules.d.ts +18 -0
  43. package/dist/manifest/kind-modules.d.ts.map +1 -0
  44. package/dist/manifest/kind-modules.js +58 -0
  45. package/dist/manifest/load-file.d.ts +3 -3
  46. package/dist/manifest/load-file.d.ts.map +1 -1
  47. package/dist/manifest/load-file.js +4 -4
  48. package/dist/manifest/load.d.ts +17 -12
  49. package/dist/manifest/load.d.ts.map +1 -1
  50. package/dist/manifest/load.js +29 -19
  51. package/dist/manifest/runtime-environment.d.ts +11 -0
  52. package/dist/manifest/runtime-environment.d.ts.map +1 -0
  53. package/dist/manifest/runtime-environment.js +44 -0
  54. package/dist/manifest/schema.d.ts +10 -9
  55. package/dist/manifest/schema.d.ts.map +1 -1
  56. package/dist/manifest/schema.js +32 -27
  57. package/dist/react/use-data.d.ts +1 -1
  58. package/dist/react/use-data.js +1 -1
  59. package/dist/render/matcap-texture.d.ts +5 -0
  60. package/dist/render/matcap-texture.d.ts.map +1 -0
  61. package/dist/render/matcap-texture.js +82 -0
  62. package/dist/render/soft-particle-depth.d.ts +14 -8
  63. package/dist/render/soft-particle-depth.d.ts.map +1 -1
  64. package/dist/render/soft-particle-depth.js +37 -14
  65. package/dist/render/viewport-shading.d.ts +11 -1
  66. package/dist/render/viewport-shading.d.ts.map +1 -1
  67. package/dist/render/viewport-shading.js +25 -0
  68. package/dist/runtime/create-runtime.js +6 -5
  69. package/dist/runtime/mount-manifest.d.ts.map +1 -1
  70. package/dist/runtime/mount-manifest.js +11 -2
  71. package/dist/settings/schema.d.ts +99 -0
  72. package/dist/settings/schema.d.ts.map +1 -0
  73. package/dist/settings/schema.js +110 -0
  74. package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
  75. package/dist/world3d-react/r3f-root-factory.js +34 -36
  76. package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -1
  77. package/dist/world3d-react/rapier-physics-bridge.js +21 -6
  78. package/dist-config/config.js +1 -1
  79. package/dist-config/config.js.map +2 -2
  80. package/package.json +2 -1
  81. package/schemas/engine-capabilities.json +4 -4
  82. package/schemas/vgai-project.schema.json +41 -36
  83. package/schemas/vgai-settings.schema.json +112 -0
  84. package/src/adapter/adapter-module.ts +33 -51
  85. package/src/adapter/authoring-seam-contract.ts +1 -0
  86. package/src/adapter/authoring.ts +16 -0
  87. package/src/adapter/finders/finder-result.ts +2 -2
  88. package/src/adapter/finders/index.ts +68 -12
  89. package/src/adapter/finders/prefabs-from-stories.ts +2 -2
  90. package/src/adapter/finders/registry.ts +95 -0
  91. package/src/adapter/finders/scenes-from-entrypoint-selection.ts +4 -4
  92. package/src/adapter/ingest/scene-capture.ts +48 -19
  93. package/src/adapter/system-adapter.ts +3 -1
  94. package/src/canvas-react/pixi-react-root-factory.tsx +21 -11
  95. package/src/data/vite-plugin-data.ts +4 -2
  96. package/src/index.ts +1 -1
  97. package/src/input/host-pointer.ts +84 -5
  98. package/src/input/input-manager.ts +404 -7
  99. package/src/manifest/configuration-kinds.ts +241 -0
  100. package/src/manifest/index.ts +4 -3
  101. package/src/manifest/kind-modules.ts +58 -0
  102. package/src/manifest/load-file.ts +11 -5
  103. package/src/manifest/load.ts +53 -22
  104. package/src/manifest/runtime-environment.ts +48 -0
  105. package/src/manifest/schema.ts +39 -33
  106. package/src/react/use-data.ts +1 -1
  107. package/src/render/matcap-texture.ts +92 -0
  108. package/src/render/soft-particle-depth.ts +43 -14
  109. package/src/render/viewport-shading.ts +33 -1
  110. package/src/runtime/create-runtime.ts +6 -5
  111. package/src/runtime/mount-manifest.ts +13 -2
  112. package/src/settings/schema.ts +127 -0
  113. package/src/world3d-react/r3f-root-factory.tsx +45 -39
  114. package/src/world3d-react/rapier-physics-bridge.tsx +22 -8
@@ -48,7 +48,7 @@ export declare function useData<T>(handle: DataHandle<T>): T;
48
48
  * runtime state belongs in your game's own store, never written back into
49
49
  * data files. Outside a running editor there is no `/__editor` server, so
50
50
  * this rejects with a teaching error (a shipped game could never reach the
51
- * route anyway — builds strip `src/tools/` entirely, §4).
51
+ * route anyway — builds strip `src/contributions/` and `src/tools/` entirely, §4).
52
52
  *
53
53
  * The body is PARSED VALUES, never serialized text: the dev server's fold
54
54
  * (`packages/editor/server/data-file-serialize.ts`) owns key order and the
@@ -50,7 +50,7 @@ export function useData(handle) {
50
50
  * runtime state belongs in your game's own store, never written back into
51
51
  * data files. Outside a running editor there is no `/__editor` server, so
52
52
  * this rejects with a teaching error (a shipped game could never reach the
53
- * route anyway — builds strip `src/tools/` entirely, §4).
53
+ * route anyway — builds strip `src/contributions/` and `src/tools/` entirely, §4).
54
54
  *
55
55
  * The body is PARSED VALUES, never serialized text: the dev server's fold
56
56
  * (`packages/editor/server/data-file-serialize.ts`) owns key order and the
@@ -0,0 +1,5 @@
1
+ import * as THREE from 'three';
2
+ export declare function drawNeutralMatcap(size?: number): HTMLCanvasElement;
3
+ /** The drawn sphere as a ready-to-sample texture. Callers own disposal. */
4
+ export declare function createNeutralMatcapTexture(size?: number): THREE.CanvasTexture;
5
+ //# sourceMappingURL=matcap-texture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcap-texture.d.ts","sourceRoot":"","sources":["../../src/render/matcap-texture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA8B/B,wBAAgB,iBAAiB,CAAC,IAAI,SAAO,GAAG,iBAAiB,CAkDhE;AAED,2EAA2E;AAC3E,wBAAgB,0BAA0B,CAAC,IAAI,SAAO,GAAG,KAAK,CAAC,aAAa,CAQ3E"}
@@ -0,0 +1,82 @@
1
+ import * as THREE from 'three';
2
+ /**
3
+ * The neutral matcap sphere, DRAWN rather than shipped.
4
+ *
5
+ * A matcap is just a picture of a lit sphere sampled by view-space normal, so
6
+ * there is nothing to vendor: the same three numbers a shader would use
7
+ * (key, fill, rim) evaluated once per texel produce the image. Keeping it
8
+ * procedural means the diagnostic look has no binary asset, no license, no
9
+ * provenance row, and no way to go missing from a build.
10
+ *
11
+ * The look is deliberately CLAY: one warm key from the upper left, a cool
12
+ * fill from the lower right, a tight rim, and a broad soft highlight — the
13
+ * neutral sculpting material whose whole job is to let form read without
14
+ * colour, texture or authored lighting getting a vote.
15
+ */
16
+ const SIZE = 256;
17
+ /** sRGB transfer curve — the canvas holds display-referred bytes. */
18
+ function encode(value) {
19
+ const c = Math.min(1, Math.max(0, value));
20
+ return c <= 0.0031308 ? c * 12.92 : 1.055 * c ** (1 / 2.4) - 0.055;
21
+ }
22
+ function normalize(x, y, z) {
23
+ const length = Math.hypot(x, y, z) || 1;
24
+ return [x / length, y / length, z / length];
25
+ }
26
+ export function drawNeutralMatcap(size = SIZE) {
27
+ const canvas = document.createElement('canvas');
28
+ canvas.width = size;
29
+ canvas.height = size;
30
+ const context = canvas.getContext('2d');
31
+ if (!context)
32
+ return canvas;
33
+ const image = context.createImageData(size, size);
34
+ const key = normalize(-0.45, 0.62, 0.64);
35
+ const fill = normalize(0.65, -0.35, 0.5);
36
+ // View direction is +Z for a matcap: the sphere is drawn facing the camera.
37
+ const half = normalize(key[0], key[1], key[2] + 1);
38
+ const base = [0.7, 0.69, 0.68];
39
+ const fillColor = [0.34, 0.4, 0.52];
40
+ const rimColor = [0.85, 0.88, 1];
41
+ for (let py = 0; py < size; py++) {
42
+ for (let px = 0; px < size; px++) {
43
+ const nx = (px + 0.5) / size / 0.5 - 1;
44
+ const ny = 1 - (py + 0.5) / size / 0.5;
45
+ const r2 = nx * nx + ny * ny;
46
+ // Outside the unit disc a matcap is never sampled by a facing surface,
47
+ // but bilinear filtering reaches one texel past the silhouette. Clamp
48
+ // to the rim normal so the edge stays the rim colour instead of
49
+ // bleeding whatever happened to be there.
50
+ const clamped = r2 > 1;
51
+ const scale = clamped ? 1 / Math.sqrt(r2) : 1;
52
+ const x = nx * scale;
53
+ const y = ny * scale;
54
+ const z = clamped ? 0 : Math.sqrt(Math.max(0, 1 - r2));
55
+ const diffuse = Math.max(0, x * key[0] + y * key[1] + z * key[2]);
56
+ const fillTerm = Math.max(0, x * fill[0] + y * fill[1] + z * fill[2]);
57
+ const specular = Math.max(0, x * half[0] + y * half[1] + z * half[2]) ** 42 * 0.5;
58
+ const rim = (1 - z) ** 3.2 * 0.4;
59
+ const offset = (py * size + px) * 4;
60
+ for (let channel = 0; channel < 3; channel++) {
61
+ const lit = base[channel] * (0.16 + 0.8 * diffuse) +
62
+ fillColor[channel] * 0.3 * fillTerm +
63
+ rimColor[channel] * rim +
64
+ specular;
65
+ image.data[offset + channel] = Math.round(encode(lit) * 255);
66
+ }
67
+ image.data[offset + 3] = 255;
68
+ }
69
+ }
70
+ context.putImageData(image, 0, 0);
71
+ return canvas;
72
+ }
73
+ /** The drawn sphere as a ready-to-sample texture. Callers own disposal. */
74
+ export function createNeutralMatcapTexture(size = SIZE) {
75
+ const texture = new THREE.CanvasTexture(drawNeutralMatcap(size));
76
+ texture.colorSpace = THREE.SRGBColorSpace;
77
+ texture.minFilter = THREE.LinearFilter;
78
+ texture.magFilter = THREE.LinearFilter;
79
+ texture.generateMipmaps = false;
80
+ texture.needsUpdate = true;
81
+ return texture;
82
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Scene DEPTH for three.quarks' soft particles the pass that makes
3
- * `ParticlesDescriptor.softParticles` a real effect instead of a footgun.
2
+ * Opaque scene DEPTH for native consumers, including three.quarks soft particles and translated
3
+ * Godot `hint_depth_texture` shader uniforms.
4
4
  *
5
5
  * ## What three.quarks needs, and what it does when nobody supplies it
6
6
  *
@@ -61,12 +61,10 @@
61
61
  * with the armed particle renderers and every transparent surface hidden and a
62
62
  * colour-write-disabled override material in place.
63
63
  *
64
- * That cost is paid ONLY while something is armed. {@link armSoftParticleDepth}
65
- * is called from `particles-factory.ts`'s `registerParticleSystem` when the
66
- * system it is registering asked for `softParticles`; with the registry empty
67
- * {@link SoftParticleDepthPass.render} returns before it touches the scene, so a
68
- * world with no soft particles pays one `Set.size` read per frame and nothing
69
- * else. Armed, per frame per camera it costs: one scene traverse (to find the
64
+ * That cost is paid ONLY while something is armed. Soft particles enter through
65
+ * {@link armSoftParticleDepth}; translated depth-texture shader programs enter through
66
+ * {@link armSceneDepthTexture}. With both registries empty {@link SoftParticleDepthPass.render}
67
+ * returns before it touches the scene. Armed, per frame per camera it costs: one scene traverse (to find the
70
68
  * transparent surfaces to hide), one extra geometry-only scene draw with shadow
71
69
  * updates suppressed, and a drawing-buffer-sized depth target
72
70
  * (`UnsignedIntType` depth + an unread RGBA8 colour attachment ≈ 8
@@ -97,6 +95,10 @@
97
95
  */
98
96
  import * as THREE from 'three';
99
97
  import type { BatchedRenderer } from 'three.quarks';
98
+ /** Any native material/program that samples Godot's opaque-pass depth texture. */
99
+ export interface SceneDepthTextureConsumer {
100
+ setDepthTexture(texture: THREE.Texture): void;
101
+ }
100
102
  /**
101
103
  * A 1x1 texture whose red channel is 1.0 — "the depth buffer says nothing is in
102
104
  * front of anything".
@@ -120,6 +122,10 @@ export declare function armSoftParticleDepth(renderer: BatchedRenderer): void;
120
122
  * depth nobody is refreshing.
121
123
  */
122
124
  export declare function disarmSoftParticleDepth(renderer: BatchedRenderer): void;
125
+ /** Retain one consumer of the same opaque scene-depth image Godot exposes as hint_depth_texture. */
126
+ export declare function armSceneDepthTexture(consumer: SceneDepthTextureConsumer): void;
127
+ /** Release one retained Godot depth-texture consumer without leaving a disposed target bound. */
128
+ export declare function disarmSceneDepthTexture(consumer: SceneDepthTextureConsumer): void;
123
129
  /** Test/diagnostic read of the registry — how many renderers a pass would consider. */
124
130
  export declare function armedSoftParticleRendererCount(): number;
125
131
  /** The subset of `THREE.WebGLRenderer` this pass drives, so a host can supply its own. */
@@ -1 +1 @@
1
- {"version":3,"file":"soft-particle-depth.d.ts","sourceRoot":"","sources":["../../src/render/soft-particle-depth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgGG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAepD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,IAAI,KAAK,CAAC,WAAW,CAOnD;AAED,8FAA8F;AAC9F,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAEpE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAQvE;AAED,uFAAuF;AACvF,wBAAgB,8BAA8B,IAAI,MAAM,CAEvD;AAED,0FAA0F;AAC1F,MAAM,WAAW,yBAAyB;IACxC,oBAAoB,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC3D,eAAe,IAAI,KAAK,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAClD,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,iBAAiB,GAAG,IAAI,GAAG,IAAI,CAAC;IAC9D,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;IAC1D,kGAAkG;IAClG,QAAQ,CAAC,SAAS,CAAC,EAAE;QAAE,UAAU,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE,CAAC;CACpE;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,EAAE,yBAAyB,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;IAC5F,OAAO,IAAI,IAAI,CAAC;CACjB;AA0CD,wBAAgB,2BAA2B,IAAI,qBAAqB,CA0GnE"}
1
+ {"version":3,"file":"soft-particle-depth.d.ts","sourceRoot":"","sources":["../../src/render/soft-particle-depth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8FG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAapD,kFAAkF;AAClF,MAAM,WAAW,yBAAyB;IACxC,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;CAC/C;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,IAAI,KAAK,CAAC,WAAW,CAOnD;AAED,8FAA8F;AAC9F,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAEpE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAQvE;AAED,oGAAoG;AACpG,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI,CAG9E;AAED,iGAAiG;AACjG,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI,CAQjF;AAED,uFAAuF;AACvF,wBAAgB,8BAA8B,IAAI,MAAM,CAEvD;AAED,0FAA0F;AAC1F,MAAM,WAAW,yBAAyB;IACxC,oBAAoB,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC3D,eAAe,IAAI,KAAK,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAClD,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,iBAAiB,GAAG,IAAI,GAAG,IAAI,CAAC;IAC9D,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;IAC1D,kGAAkG;IAClG,QAAQ,CAAC,SAAS,CAAC,EAAE;QAAE,UAAU,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE,CAAC;CACpE;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,EAAE,yBAAyB,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;IAC5F,OAAO,IAAI,IAAI,CAAC;CACjB;AA0CD,wBAAgB,2BAA2B,IAAI,qBAAqB,CAiHnE"}
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Scene DEPTH for three.quarks' soft particles the pass that makes
3
- * `ParticlesDescriptor.softParticles` a real effect instead of a footgun.
2
+ * Opaque scene DEPTH for native consumers, including three.quarks soft particles and translated
3
+ * Godot `hint_depth_texture` shader uniforms.
4
4
  *
5
5
  * ## What three.quarks needs, and what it does when nobody supplies it
6
6
  *
@@ -61,12 +61,10 @@
61
61
  * with the armed particle renderers and every transparent surface hidden and a
62
62
  * colour-write-disabled override material in place.
63
63
  *
64
- * That cost is paid ONLY while something is armed. {@link armSoftParticleDepth}
65
- * is called from `particles-factory.ts`'s `registerParticleSystem` when the
66
- * system it is registering asked for `softParticles`; with the registry empty
67
- * {@link SoftParticleDepthPass.render} returns before it touches the scene, so a
68
- * world with no soft particles pays one `Set.size` read per frame and nothing
69
- * else. Armed, per frame per camera it costs: one scene traverse (to find the
64
+ * That cost is paid ONLY while something is armed. Soft particles enter through
65
+ * {@link armSoftParticleDepth}; translated depth-texture shader programs enter through
66
+ * {@link armSceneDepthTexture}. With both registries empty {@link SoftParticleDepthPass.render}
67
+ * returns before it touches the scene. Armed, per frame per camera it costs: one scene traverse (to find the
70
68
  * transparent surfaces to hide), one extra geometry-only scene draw with shadow
71
69
  * updates suppressed, and a drawing-buffer-sized depth target
72
70
  * (`UnsignedIntType` depth + an unread RGBA8 colour attachment ≈ 8
@@ -106,6 +104,7 @@ import * as THREE from 'three';
106
104
  * renderers actually parented under the scene it is rendering.
107
105
  */
108
106
  const armed = new Map();
107
+ const depthTextureConsumers = new Map();
109
108
  let farDepth;
110
109
  /**
111
110
  * A 1x1 texture whose red channel is 1.0 — "the depth buffer says nothing is in
@@ -148,6 +147,22 @@ export function disarmSoftParticleDepth(renderer) {
148
147
  return;
149
148
  renderer.setDepthTexture(farDepthTexture());
150
149
  }
150
+ /** Retain one consumer of the same opaque scene-depth image Godot exposes as hint_depth_texture. */
151
+ export function armSceneDepthTexture(consumer) {
152
+ depthTextureConsumers.set(consumer, (depthTextureConsumers.get(consumer) ?? 0) + 1);
153
+ consumer.setDepthTexture(farDepthTexture());
154
+ }
155
+ /** Release one retained Godot depth-texture consumer without leaving a disposed target bound. */
156
+ export function disarmSceneDepthTexture(consumer) {
157
+ const remaining = (depthTextureConsumers.get(consumer) ?? 0) - 1;
158
+ if (remaining > 0) {
159
+ depthTextureConsumers.set(consumer, remaining);
160
+ return;
161
+ }
162
+ if (!depthTextureConsumers.delete(consumer))
163
+ return;
164
+ consumer.setDepthTexture(farDepthTexture());
165
+ }
151
166
  /** Test/diagnostic read of the registry — how many renderers a pass would consider. */
152
167
  export function armedSoftParticleRendererCount() {
153
168
  return armed.size;
@@ -210,14 +225,14 @@ export function createSoftParticleDepthPass() {
210
225
  };
211
226
  return {
212
227
  render(renderer, scene, camera) {
213
- if (armed.size === 0)
228
+ if (armed.size === 0 && depthTextureConsumers.size === 0)
214
229
  return;
215
230
  const consumers = [];
216
231
  for (const candidate of armed.keys()) {
217
232
  if (isUnder(candidate, scene))
218
233
  consumers.push(candidate);
219
234
  }
220
- if (consumers.length === 0)
235
+ if (consumers.length === 0 && depthTextureConsumers.size === 0)
221
236
  return;
222
237
  // See REQUIRED_RENDERER_MEMBERS: a renderer that cannot swap render
223
238
  // targets cannot run a prepass, and the frame is worth more than the
@@ -227,9 +242,9 @@ export function createSoftParticleDepthPass() {
227
242
  if (!warnedIncapable) {
228
243
  warnedIncapable = true;
229
244
  // biome-ignore lint/suspicious/noConsole: deliberate loud degrade — the documented alternative to throwing out of the frame
230
- console.warn(`soft-particle depth prepass skipped: this renderer implements none of ${missing.join(', ')}. ` +
231
- `${String(consumers.length)} particle batch(es) keep the inert far-depth default, so they ` +
232
- 'draw unfaded the same pixels as a batch that never declared a proximity fade. A real ' +
245
+ console.warn(`opaque scene-depth prepass skipped: this renderer is missing ${missing.join(', ')}. ` +
246
+ `${String(consumers.length)} particle batch(es) and ${String(depthTextureConsumers.size)} ` +
247
+ 'shader consumer(s) keep the inert far-depth default. A real ' +
233
248
  'WebGLRenderer has these; a design-time settle stand-in does not.');
234
249
  }
235
250
  return;
@@ -284,14 +299,22 @@ export function createSoftParticleDepthPass() {
284
299
  object.visible = true;
285
300
  hidden.length = 0;
286
301
  }
302
+ const renderedDepth = rt.depthTexture;
303
+ if (renderedDepth === null) {
304
+ throw new Error('opaque scene-depth prepass lost its authored depth texture.');
305
+ }
287
306
  for (const consumer of consumers)
288
- consumer.setDepthTexture(rt.depthTexture);
307
+ consumer.setDepthTexture(renderedDepth);
308
+ for (const consumer of depthTextureConsumers.keys())
309
+ consumer.setDepthTexture(renderedDepth);
289
310
  },
290
311
  dispose() {
291
312
  // Every consumer this pass fed now points at a texture that is about to
292
313
  // go away. Hand them the inert default back rather than a disposed one.
293
314
  for (const candidate of armed.keys())
294
315
  candidate.setDepthTexture(farDepthTexture());
316
+ for (const consumer of depthTextureConsumers.keys())
317
+ consumer.setDepthTexture(farDepthTexture());
295
318
  target?.dispose();
296
319
  target = null;
297
320
  override.dispose();
@@ -1,6 +1,6 @@
1
1
  import * as THREE from 'three';
2
2
  /** Temporary developer-facing shading modes. These never become scene data. */
3
- export type ViewportShadingMode = 'solid' | 'clay' | 'unlit' | 'wireframe' | 'normals' | 'overdraw';
3
+ export type ViewportShadingMode = 'solid' | 'clay' | 'unlit' | 'wireframe' | 'matcap' | 'normals' | 'overdraw';
4
4
  /**
5
5
  * Applies a render-only material view and restores every native material in a
6
6
  * `finally` block. Gameplay and authoring therefore always observe the real
@@ -9,6 +9,16 @@ export type ViewportShadingMode = 'solid' | 'clay' | 'unlit' | 'wireframe' | 'no
9
9
  export declare class ViewportShadingRenderer {
10
10
  private readonly _derived;
11
11
  private readonly _normals;
12
+ /**
13
+ * The clay/sculpt look. Built on FIRST USE, not at construction: the matcap
14
+ * sphere is drawn into a 2D canvas, and every ViewportShadingRenderer that
15
+ * only ever draws `solid` would otherwise pay for a texture nobody samples.
16
+ * `toneMapped: false` for the same reason the overdraw material sets it —
17
+ * a diagnostic look must read exactly as authored, not as the document's
18
+ * exposure and tone curve happen to grade it.
19
+ */
20
+ private _matcap;
21
+ private _matcapTexture;
12
22
  private readonly _overdraw;
13
23
  render(scene: THREE.Scene, mode: ViewportShadingMode, draw: () => void, include?: (mesh: THREE.Mesh) => boolean): void;
14
24
  dispose(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"viewport-shading.d.ts","sourceRoot":"","sources":["../../src/render/viewport-shading.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,+EAA+E;AAC/E,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,WAAW,GAAG,SAAS,GAAG,UAAU,CAAC;AAqBpG;;;;GAIG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA2C;IACpE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CASvB;IAEH,MAAM,CACJ,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,IAAI,EAAE,mBAAmB,EACzB,IAAI,EAAE,MAAM,IAAI,EAChB,OAAO,GAAE,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,OAAoB,GAClD,IAAI;IA0BP,OAAO,IAAI,IAAI;IAWf,OAAO,CAAC,YAAY;CA6CrB"}
1
+ {"version":3,"file":"viewport-shading.d.ts","sourceRoot":"","sources":["../../src/render/viewport-shading.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,+EAA+E;AAC/E,MAAM,MAAM,mBAAmB,GAC3B,OAAO,GACP,MAAM,GACN,OAAO,GACP,WAAW,GACX,QAAQ,GACR,SAAS,GACT,UAAU,CAAC;AAqBf;;;;GAIG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA2C;IACpE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO,CAAyC;IACxD,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CASvB;IAEH,MAAM,CACJ,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,IAAI,EAAE,mBAAmB,EACzB,IAAI,EAAE,MAAM,IAAI,EAChB,OAAO,GAAE,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,OAAoB,GAClD,IAAI;IA0BP,OAAO,IAAI,IAAI;IAef,OAAO,CAAC,YAAY;CAuDrB"}
@@ -1,4 +1,5 @@
1
1
  import * as THREE from 'three';
2
+ import { createNeutralMatcapTexture } from './matcap-texture';
2
3
  function materialColor(material) {
3
4
  const color = material.color;
4
5
  return color?.clone() ?? new THREE.Color(0xbec7d1);
@@ -14,6 +15,16 @@ function textureOf(material, key) {
14
15
  export class ViewportShadingRenderer {
15
16
  _derived = new Map();
16
17
  _normals = new THREE.MeshNormalMaterial();
18
+ /**
19
+ * The clay/sculpt look. Built on FIRST USE, not at construction: the matcap
20
+ * sphere is drawn into a 2D canvas, and every ViewportShadingRenderer that
21
+ * only ever draws `solid` would otherwise pay for a texture nobody samples.
22
+ * `toneMapped: false` for the same reason the overdraw material sets it —
23
+ * a diagnostic look must read exactly as authored, not as the document's
24
+ * exposure and tone curve happen to grade it.
25
+ */
26
+ _matcap = null;
27
+ _matcapTexture = null;
17
28
  _overdraw = new THREE.MeshBasicMaterial({
18
29
  color: 0xffffff,
19
30
  transparent: true,
@@ -56,12 +67,26 @@ export class ViewportShadingRenderer {
56
67
  this._derived.clear();
57
68
  this._normals.dispose();
58
69
  this._overdraw.dispose();
70
+ this._matcap?.dispose();
71
+ this._matcapTexture?.dispose();
72
+ this._matcap = null;
73
+ this._matcapTexture = null;
59
74
  }
60
75
  _materialFor(material, mode) {
61
76
  if (mode === 'normals')
62
77
  return this._normals;
63
78
  if (mode === 'overdraw')
64
79
  return this._overdraw;
80
+ if (mode === 'matcap') {
81
+ if (!this._matcap) {
82
+ this._matcapTexture = createNeutralMatcapTexture();
83
+ this._matcap = new THREE.MeshMatcapMaterial({
84
+ matcap: this._matcapTexture,
85
+ toneMapped: false,
86
+ });
87
+ }
88
+ return this._matcap;
89
+ }
65
90
  let pair = this._derived.get(material);
66
91
  if (!pair) {
67
92
  const common = {
@@ -112,14 +112,15 @@ export async function createGameRuntime(config) {
112
112
  // Universal root host
113
113
  // ---------------------------------------------------------------------------
114
114
  /** A stand-in `THREE.WebGLRenderer` for headless (`headless:true`) three
115
- * roots: a headless mount never calls a render-phase method on
116
- * `host.renderer` at all, so
117
- * this only needs to satisfy the handful of calls THIS file itself makes
115
+ * roots: draw submission is inert while native scene/component evaluation
116
+ * still runs. This satisfies Fiber's custom-renderer gate and the calls
118
117
  * (`setPixelRatio`/`setClearColor`/`setSize` at mount, `dispose`/
119
118
  * `forceContextLoss` at teardown) — never a real GL call. Node conformance
120
119
  * tests only; never used when `headless` is left `false`/absent. */
121
- function createHeadlessRendererStub() {
120
+ function createHeadlessRendererStub(canvas) {
122
121
  return {
122
+ domElement: canvas,
123
+ render() { },
123
124
  setPixelRatio() { },
124
125
  setClearColor() { },
125
126
  setSize() { },
@@ -174,7 +175,7 @@ function disposeMountedRoot(entry, container) {
174
175
  async function mountOneThreeRoot(spec, ctx) {
175
176
  const { game, canvas, w, h, dpr, isBottom, headless, assets, antialias } = ctx;
176
177
  const renderer = headless
177
- ? createHeadlessRendererStub()
178
+ ? createHeadlessRendererStub(canvas)
178
179
  : createHostRenderer(canvas, w, h,
179
180
  // The manifest's `rendering.antialias` reaches the WebGL context here and NOWHERE else:
180
181
  // `createHostRenderer` passes it straight to the constructor, and the sample count is
@@ -1 +1 @@
1
- {"version":3,"file":"mount-manifest.d.ts","sourceRoot":"","sources":["../../src/runtime/mount-manifest.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mount-manifest.d.ts","sourceRoot":"","sources":["../../src/runtime/mount-manifest.ts"],"names":[],"mappings":"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"}
@@ -76,6 +76,15 @@ function hasRealWindow() {
76
76
  function looksAlreadyResolved(value) {
77
77
  if (typeof value !== 'object' || value === null)
78
78
  return false;
79
+ // The loader's own output field is the surest mark: a raw manifest never
80
+ // carries it (the schema is strict). Without this, a RESOLVED manifest
81
+ // with `roots: []` — a models project, nothing to play — fell through to
82
+ // `loadGameManifest`, whose strict parse refused THIS key ("Unrecognized
83
+ // key: deferredConfigurationKinds") instead of saying "no roots"
84
+ // (blind lantern round 21, 2026-09-06, sent there by the never-played nag).
85
+ if (Array.isArray(value.deferredConfigurationKinds)) {
86
+ return true;
87
+ }
79
88
  const roots = value.roots;
80
89
  if (!Array.isArray(roots) || roots.length === 0)
81
90
  return false;
@@ -97,7 +106,7 @@ function looksAlreadyResolved(value) {
97
106
  export function resolveManifest(raw) {
98
107
  if (looksAlreadyResolved(raw))
99
108
  return raw;
100
- return loadGameManifest(raw);
109
+ return loadGameManifest(raw, { configurationKinds: 'defer' });
101
110
  }
102
111
  // ---------------------------------------------------------------------------
103
112
  // Per-kind entry -> adapter resolution (mirrors `resolveRootBinding`'s
@@ -265,7 +274,7 @@ export async function mountManifestRoots(opts) {
265
274
  // `manifest/load.ts`) is `{ room, module } | undefined`; its mere presence
266
275
  // is the "this project declares a room" signal, independent of
267
276
  // whether/when the game actually joins it.
268
- if (manifest.server) {
277
+ if (manifest.configurations.some((configuration) => configuration.kind === 'process')) {
269
278
  debugRegistry.setRoomDeclared(true);
270
279
  }
271
280
  // Defect 5 fix: `maybeInstallDebugBridge` adds `window` listeners and
@@ -0,0 +1,99 @@
1
+ /**
2
+ * THE SETTINGS DOCUMENT — the Zod truth for the two shared settings layers
3
+ * (ARCHITECTURE-CORE §Editor chrome, "Settings have four layers with named
4
+ * homes"):
5
+ *
6
+ * ~/.vgai/settings.json the USER layer — one person, every project
7
+ * <project>/.vgai/settings.json the PROJECT layer — one project, committed,
8
+ * every person who opens it
9
+ *
10
+ * Both files carry the same shape; the project layer overrides the user layer
11
+ * key by key (`mergeEditorSettings`). Neither layer holds authored content —
12
+ * an unreadable file is reported by name and falls back to defaults, never
13
+ * migrated. The generated JSON Schema (`npm run generate-schema` →
14
+ * `packages/engine/schemas/vgai-settings.schema.json`) is what an editor
15
+ * autocompletes against when a person edits either file by hand.
16
+ *
17
+ * Every field here has a runtime reader in the editor's settings store
18
+ * consumers (`theme-preference.ts`, `workspace-regions.ts`,
19
+ * `keymap-presets.ts`, `workspace-presets.ts`, `device-preview.ts`); a field
20
+ * with no reader does not get declared (CLAUDE.md, "Do not author schema
21
+ * fields that have no runtime reader").
22
+ */
23
+ import { z } from 'zod';
24
+ export declare const ChromeRegionsSettingsSchema: z.ZodObject<{
25
+ tabs: z.ZodOptional<z.ZodEnum<{
26
+ hidden: "hidden";
27
+ shown: "shown";
28
+ }>>;
29
+ header: z.ZodOptional<z.ZodEnum<{
30
+ hidden: "hidden";
31
+ shown: "shown";
32
+ }>>;
33
+ shelf: z.ZodOptional<z.ZodEnum<{
34
+ hidden: "hidden";
35
+ shown: "shown";
36
+ }>>;
37
+ inspector: z.ZodOptional<z.ZodEnum<{
38
+ properties: "properties";
39
+ column: "column";
40
+ }>>;
41
+ drawer: z.ZodOptional<z.ZodEnum<{
42
+ hidden: "hidden";
43
+ shown: "shown";
44
+ }>>;
45
+ }, z.core.$strict>;
46
+ export declare const EditorSettingsSchema: z.ZodObject<{
47
+ $schema: z.ZodOptional<z.ZodString>;
48
+ appearance: z.ZodOptional<z.ZodObject<{
49
+ palette: z.ZodOptional<z.ZodString>;
50
+ material: z.ZodOptional<z.ZodString>;
51
+ regions: z.ZodOptional<z.ZodObject<{
52
+ tabs: z.ZodOptional<z.ZodEnum<{
53
+ hidden: "hidden";
54
+ shown: "shown";
55
+ }>>;
56
+ header: z.ZodOptional<z.ZodEnum<{
57
+ hidden: "hidden";
58
+ shown: "shown";
59
+ }>>;
60
+ shelf: z.ZodOptional<z.ZodEnum<{
61
+ hidden: "hidden";
62
+ shown: "shown";
63
+ }>>;
64
+ inspector: z.ZodOptional<z.ZodEnum<{
65
+ properties: "properties";
66
+ column: "column";
67
+ }>>;
68
+ drawer: z.ZodOptional<z.ZodEnum<{
69
+ hidden: "hidden";
70
+ shown: "shown";
71
+ }>>;
72
+ }, z.core.$strict>>;
73
+ }, z.core.$strict>>;
74
+ keymap: z.ZodOptional<z.ZodString>;
75
+ workspace: z.ZodOptional<z.ZodObject<{
76
+ default: z.ZodOptional<z.ZodString>;
77
+ }, z.core.$strict>>;
78
+ devicePreview: z.ZodOptional<z.ZodObject<{
79
+ preset: z.ZodOptional<z.ZodString>;
80
+ touch: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
81
+ }, z.core.$strict>>;
82
+ }, z.core.$strict>;
83
+ export type EditorSettings = z.infer<typeof EditorSettingsSchema>;
84
+ export type ChromeRegionsSettings = z.infer<typeof ChromeRegionsSettingsSchema>;
85
+ /** Which layer a file is; the file paths are the server's. */
86
+ export type SettingsLayer = 'user' | 'project';
87
+ /** Deep-merge plain objects; anything else (primitives, arrays, null)
88
+ * replaces. `merge(user, project)` is the effective document. */
89
+ export declare function mergeEditorSettings(base: EditorSettings, over: EditorSettings): EditorSettings;
90
+ /** Validate a raw document. The result carries the parsed settings or the
91
+ * issues, spelled for a person who has the file open. */
92
+ export declare function parseEditorSettings(raw: unknown): {
93
+ settings: EditorSettings;
94
+ issues: [];
95
+ } | {
96
+ settings: null;
97
+ issues: string[];
98
+ };
99
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/settings/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;kBAY0B,CAAC;AAEnE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA6CsD,CAAC;AAExF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,CAAC;AAM/C;kEACkE;AAClE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,GAAG,cAAc,CAY9F;AAED;0DAC0D;AAC1D,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,OAAO,GACX;IAAE,QAAQ,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,EAAE,CAAA;CAAE,GAAG;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CASjF"}