@holoscript/core 8.0.13 → 8.0.14

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 (159) hide show
  1. package/README.md +14 -0
  2. package/bin/holo-decision.mjs +133 -0
  3. package/dist/{GLTFPipeline-L76YTXKA.js → GLTFPipeline-6LS3UDYX.js} +6 -6
  4. package/dist/{GLTFPipeline-ZWJN7GI6.cjs → GLTFPipeline-VTI6PRRQ.cjs} +10 -10
  5. package/dist/{HoloCompositionParser-P4Y3CXKQ.js → HoloCompositionParser-GILABNN3.js} +4 -4
  6. package/dist/{HoloCompositionParser-6GW2BZID.cjs → HoloCompositionParser-ZVRYVEA5.cjs} +11 -11
  7. package/dist/{HoloScriptPlusParser-RFNHRMPE.cjs → HoloScriptPlusParser-WTPRPRPR.cjs} +7 -7
  8. package/dist/{HoloScriptPlusParser-HQV6IJAH.js → HoloScriptPlusParser-ZP53ECSL.js} +4 -4
  9. package/dist/{SceneIRCompiler-KWUFHZ4O.cjs → SceneIRCompiler-7KQ5NKAU.cjs} +11 -11
  10. package/dist/{SceneIRCompiler-63KA3M2F.js → SceneIRCompiler-VX75RB6H.js} +6 -6
  11. package/dist/{chunk-EM2OGPYE.js → chunk-2GKF7JQ6.js} +3 -3
  12. package/dist/{chunk-HVX7QQYK.js → chunk-3KHRZTNW.js} +3 -3
  13. package/dist/{chunk-UTUD6GJI.js → chunk-42Z3SVHQ.js} +3 -3
  14. package/dist/{chunk-A4OOLBZD.js → chunk-47XZ62KM.js} +5 -5
  15. package/dist/{chunk-GGR7QUKK.cjs → chunk-4CRMVEON.cjs} +37 -37
  16. package/dist/{chunk-QBY7LRN2.js → chunk-4NEPHK2K.js} +3 -3
  17. package/dist/{chunk-65S62L4D.cjs → chunk-5B2AJKA5.cjs} +20 -20
  18. package/dist/{chunk-PJFTTSKE.js → chunk-5T6BWIB7.js} +4 -4
  19. package/dist/{chunk-KFVNCW4Q.js → chunk-62OYZT6Q.js} +3 -3
  20. package/dist/{chunk-Y57AVOM2.cjs → chunk-645EWDZW.cjs} +4 -4
  21. package/dist/{chunk-BP4KTMLY.js → chunk-6FCNMTL5.js} +4 -4
  22. package/dist/{chunk-77QI552M.js → chunk-73EDVY5D.js} +3 -3
  23. package/dist/{chunk-7IDJDMPU.cjs → chunk-7FYSSI3C.cjs} +27 -2
  24. package/dist/{chunk-WNYXT4TE.cjs → chunk-7ITJP33Q.cjs} +3 -3
  25. package/dist/chunk-7ZKQ32XK.js +1571 -0
  26. package/dist/{chunk-P7JEKJTZ.cjs → chunk-B6JHTTMR.cjs} +25 -14
  27. package/dist/{chunk-FG6PYGIZ.js → chunk-BLCWQ225.js} +4 -4
  28. package/dist/{chunk-FRSKLX6I.js → chunk-C73GVAYC.js} +10 -7
  29. package/dist/{chunk-PXVAPGTR.cjs → chunk-CESMPUEZ.cjs} +3774 -1138
  30. package/dist/{chunk-SQXAZHDG.js → chunk-CHIZSMWD.js} +3 -3
  31. package/dist/{chunk-G35CAQ7I.cjs → chunk-D44CEFY4.cjs} +5 -5
  32. package/dist/{chunk-TC7JEO6R.cjs → chunk-ERYKKPHW.cjs} +711 -52
  33. package/dist/{chunk-DJ5BNRIS.js → chunk-EYHPTJ3F.js} +3675 -1046
  34. package/dist/{chunk-J3UHV5T4.cjs → chunk-FGXJPJ3W.cjs} +4 -4
  35. package/dist/{chunk-IJU6AS67.cjs → chunk-FHR2AVMA.cjs} +23 -23
  36. package/dist/{chunk-LMB555QB.cjs → chunk-FOM36K5I.cjs} +18 -18
  37. package/dist/{chunk-ZRPALBQB.cjs → chunk-GGH3ZYDB.cjs} +5 -5
  38. package/dist/{chunk-2BYAUHBO.js → chunk-GI5YYZH6.js} +3 -3
  39. package/dist/{chunk-VTEXJOSD.cjs → chunk-GYJLP4BO.cjs} +30 -30
  40. package/dist/{chunk-OC6GV5QH.cjs → chunk-HCONR4J6.cjs} +38 -38
  41. package/dist/{chunk-B3CWTCK2.js → chunk-IA7FCSRY.js} +27 -2
  42. package/dist/{chunk-5FYMCVBY.js → chunk-IXFNA5TT.js} +4 -4
  43. package/dist/{chunk-G6KACNMO.js → chunk-JCVSCKUH.js} +4 -4
  44. package/dist/{chunk-FHVPN7BN.js → chunk-JQCXLKAU.js} +3 -3
  45. package/dist/{chunk-F3YT6GUF.cjs → chunk-KLM6QD6D.cjs} +14 -14
  46. package/dist/{chunk-UGQ3QNV6.js → chunk-KOGPVAP6.js} +24 -8
  47. package/dist/{chunk-CKV5NYAE.js → chunk-KYJD2UMV.js} +4 -4
  48. package/dist/{chunk-LB3ZZMKT.cjs → chunk-L4DBUX3N.cjs} +10 -10
  49. package/dist/{chunk-RXQNRZ5F.js → chunk-LGRKBTPM.js} +3 -3
  50. package/dist/{chunk-PDXX5UI4.cjs → chunk-LQNL3IYV.cjs} +38 -38
  51. package/dist/{chunk-WWNCM473.js → chunk-M7XR3F53.js} +4 -4
  52. package/dist/{chunk-IFW5IW4L.cjs → chunk-ML4V5PVU.cjs} +670 -627
  53. package/dist/{chunk-Q4K2GEMW.cjs → chunk-N7IYRAFG.cjs} +13 -10
  54. package/dist/chunk-N7MIIDLI.cjs +1605 -0
  55. package/dist/{chunk-G6EKZHXY.js → chunk-NWKARJM2.js} +4 -4
  56. package/dist/{chunk-VVAAZITA.cjs → chunk-OOP4S3GU.cjs} +5 -5
  57. package/dist/{chunk-U3D4SYQ5.js → chunk-ORBVCOTC.js} +3 -3
  58. package/dist/{chunk-RRQIAJLQ.cjs → chunk-OSKGCXAQ.cjs} +5 -5
  59. package/dist/{chunk-QEDMOGF3.cjs → chunk-P65OIMJT.cjs} +7 -7
  60. package/dist/{chunk-QM7FJB5W.js → chunk-PSWCJQS4.js} +4 -4
  61. package/dist/{chunk-RZ2EPXO3.cjs → chunk-PZ46WRHX.cjs} +8 -8
  62. package/dist/{chunk-AATYYLYK.js → chunk-Q4J2K7FI.js} +16 -5
  63. package/dist/{chunk-ONPVM4IV.js → chunk-QLPRI3QW.js} +698 -45
  64. package/dist/{chunk-WTH23ISI.js → chunk-R5VKLUMR.js} +4 -4
  65. package/dist/{chunk-NQN5LFN4.cjs → chunk-REN3LHO6.cjs} +19 -19
  66. package/dist/{chunk-JEJ7TT54.js → chunk-RVN7OPCP.js} +4 -4
  67. package/dist/{chunk-ZK7KX5CD.cjs → chunk-SE2Y5BSL.cjs} +6 -6
  68. package/dist/{chunk-2X3IPCBK.cjs → chunk-SKVBUGMC.cjs} +7 -7
  69. package/dist/{chunk-GZPUA2TL.cjs → chunk-SNMQUWIO.cjs} +10 -10
  70. package/dist/{chunk-OIBRWLPY.cjs → chunk-TKDDIE3T.cjs} +54 -3
  71. package/dist/{chunk-SQAAWO4V.cjs → chunk-TMNAKNV2.cjs} +5 -5
  72. package/dist/{chunk-5LVFTGKH.js → chunk-TZQKJOPC.js} +3 -3
  73. package/dist/{chunk-TSTFYGGX.js → chunk-UKEK2NZJ.js} +3 -3
  74. package/dist/{chunk-3IUHTGDD.cjs → chunk-VA7THINF.cjs} +38 -38
  75. package/dist/{chunk-Z557DVYN.js → chunk-VMCR5QVI.js} +4 -4
  76. package/dist/{chunk-IS7NTKY5.cjs → chunk-VTFLKSJL.cjs} +14 -14
  77. package/dist/{chunk-RVNXBF2M.js → chunk-VXHB6ZHY.js} +4 -4
  78. package/dist/{chunk-S4O7CFHQ.cjs → chunk-VZI7S7P6.cjs} +7 -7
  79. package/dist/{chunk-BQJ3YBF3.js → chunk-W4AJMMQR.js} +667 -628
  80. package/dist/{chunk-2KKOYZYI.cjs → chunk-XZC75AJ4.cjs} +5 -5
  81. package/dist/{chunk-FWCHPCD4.js → chunk-Y2OCKXVD.js} +4 -4
  82. package/dist/{chunk-XF63CZ7E.cjs → chunk-YBM34FUJ.cjs} +6 -6
  83. package/dist/{chunk-DUG3OQAS.js → chunk-YDSNDCV3.js} +3 -3
  84. package/dist/{chunk-566ZB4LZ.cjs → chunk-YM7UUYR5.cjs} +5 -5
  85. package/dist/{chunk-BCP4E7MD.cjs → chunk-YMI3ED3B.cjs} +26 -10
  86. package/dist/{chunk-CP7V3ATL.js → chunk-Z7NUL73U.js} +51 -4
  87. package/dist/cli/holoscript-runner.cjs +10 -10
  88. package/dist/cli/holoscript-runner.js +3 -3
  89. package/dist/compiler/agent-inference.cjs +8 -8
  90. package/dist/compiler/agent-inference.js +4 -4
  91. package/dist/compiler/android-xr.cjs +7 -7
  92. package/dist/compiler/android-xr.js +5 -5
  93. package/dist/compiler/android.cjs +6 -6
  94. package/dist/compiler/android.js +4 -4
  95. package/dist/compiler/context.cjs +6 -6
  96. package/dist/compiler/context.js +3 -3
  97. package/dist/compiler/daimon-seed.cjs +9 -9
  98. package/dist/compiler/daimon-seed.js +4 -4
  99. package/dist/compiler/domain-block-utils.cjs +165 -165
  100. package/dist/compiler/domain-block-utils.js +4 -4
  101. package/dist/compiler/dtdl.cjs +7 -7
  102. package/dist/compiler/dtdl.js +4 -4
  103. package/dist/compiler/gltf-pipeline.cjs +8 -8
  104. package/dist/compiler/gltf-pipeline.js +1 -1
  105. package/dist/compiler/godot.cjs +6 -6
  106. package/dist/compiler/godot.js +5 -5
  107. package/dist/compiler/incremental.cjs +8 -8
  108. package/dist/compiler/incremental.js +3 -3
  109. package/dist/compiler/index.cjs +871 -207
  110. package/dist/compiler/index.d.ts +21 -1
  111. package/dist/compiler/index.js +696 -60
  112. package/dist/compiler/ios.cjs +6 -6
  113. package/dist/compiler/ios.js +4 -4
  114. package/dist/compiler/llm-provider-capabilities.cjs +6 -6
  115. package/dist/compiler/llm-provider-capabilities.js +3 -3
  116. package/dist/compiler/openxr.cjs +6 -6
  117. package/dist/compiler/openxr.js +5 -5
  118. package/dist/compiler/sdf.cjs +7 -7
  119. package/dist/compiler/sdf.js +5 -5
  120. package/dist/compiler/state.cjs +6 -6
  121. package/dist/compiler/state.js +4 -4
  122. package/dist/compiler/trait-composition.cjs +7 -7
  123. package/dist/compiler/trait-composition.js +3 -3
  124. package/dist/compiler/unity.cjs +6 -6
  125. package/dist/compiler/unity.js +5 -5
  126. package/dist/compiler/unreal.cjs +7 -7
  127. package/dist/compiler/unreal.js +5 -5
  128. package/dist/compiler/urdf.cjs +14 -14
  129. package/dist/compiler/urdf.js +5 -5
  130. package/dist/compiler/usd-physics.cjs +7 -7
  131. package/dist/compiler/usd-physics.js +4 -4
  132. package/dist/compiler/visionos.cjs +6 -6
  133. package/dist/compiler/visionos.js +5 -5
  134. package/dist/compiler/vrchat.cjs +7 -7
  135. package/dist/compiler/vrchat.js +5 -5
  136. package/dist/compiler/wasm.cjs +10 -10
  137. package/dist/compiler/wasm.js +5 -5
  138. package/dist/compiler/webgpu.cjs +6 -6
  139. package/dist/compiler/webgpu.js +5 -5
  140. package/dist/entries/scripting.cjs +5 -5
  141. package/dist/entries/scripting.js +1 -1
  142. package/dist/evolution/index.cjs +17 -17
  143. package/dist/evolution/index.js +5 -5
  144. package/dist/index.cjs +915 -704
  145. package/dist/index.d.ts +70 -5
  146. package/dist/index.js +191 -108
  147. package/dist/parser.cjs +18 -18
  148. package/dist/parser.js +4 -4
  149. package/dist/reconstruction/index.cjs +127 -38
  150. package/dist/reconstruction/index.d.ts +159 -0
  151. package/dist/reconstruction/index.js +11 -2
  152. package/dist/runtime.d.ts +1 -0
  153. package/dist/traitDocs/traitDocs.cjs +8 -8
  154. package/dist/traitDocs/traitDocs.js +2 -2
  155. package/dist/traits/index.cjs +566 -471
  156. package/dist/traits/index.js +93 -11
  157. package/package.json +20 -16
  158. package/dist/chunk-DVHZD5HV.cjs +0 -639
  159. package/dist/chunk-G72754QB.js +0 -625
@@ -1,8 +1,200 @@
1
- import { init_DomainBlockCompilerMixin, datavizToWebGPU, compileDataVizBlock, compileMaterialBlock, materialToWebGPU, compileParticleBlock, compilePostProcessingBlock, compileProceduralBlock } from './chunk-5LVFTGKH.js';
2
- import { init_CompilerBase, CompilerBase } from './chunk-DUG3OQAS.js';
3
- import { __export, __esm } from './chunk-W4GABFCR.js';
1
+ import { init_DomainBlockCompilerMixin, datavizToWebGPU, compileDataVizBlock, compileMaterialBlock, materialToWebGPU, compileParticleBlock, compilePostProcessingBlock, compileProceduralBlock } from './chunk-TZQKJOPC.js';
2
+ import { init_CompilerBase, CompilerBase } from './chunk-YDSNDCV3.js';
3
+ import { __esm, __export } from './chunk-W4GABFCR.js';
4
4
  import { ANSCapabilityPath } from '@holoscript/core-types/ans';
5
5
 
6
+ // src/compiler/render-modules/geometry-registry.ts
7
+ function resolveGeometry(meshType) {
8
+ const key = String(meshType ?? "").trim().toLowerCase();
9
+ const kind = ALIAS[key] ?? "box";
10
+ return PRIMITIVE[kind];
11
+ }
12
+ var PRIMITIVE, ALIAS;
13
+ var init_geometry_registry = __esm({
14
+ "src/compiler/render-modules/geometry-registry.ts"() {
15
+ PRIMITIVE = {
16
+ box: { kind: "box", webgpuVertexFn: "generateCubeVertices(1.0)" },
17
+ sphere: { kind: "sphere", webgpuVertexFn: "generateSphereVertices(0.5, 16, 16)" },
18
+ plane: { kind: "plane", webgpuVertexFn: "generatePlaneVertices(1.0, 1.0)" },
19
+ cylinder: { kind: "cylinder", webgpuVertexFn: "generateCylinderVertices(0.5, 1.0, 16)" },
20
+ cone: { kind: "cone", webgpuVertexFn: "generateConeVertices(0.5, 1.0, 16)" },
21
+ torus: { kind: "torus", webgpuVertexFn: "generateTorusVertices(0.5, 0.15, 16, 32)" }
22
+ };
23
+ ALIAS = {
24
+ // box family (panels, rounded boxes read as boxes)
25
+ cube: "box",
26
+ box: "box",
27
+ rounded_panel: "box",
28
+ rounded_box: "box",
29
+ panel: "box",
30
+ card: "box",
31
+ // sphere family
32
+ sphere: "sphere",
33
+ orb: "sphere",
34
+ ball: "sphere",
35
+ bubble: "sphere",
36
+ procedural_upper_body: "sphere",
37
+ avatar: "sphere",
38
+ // plane / surface family
39
+ plane: "plane",
40
+ quad: "plane",
41
+ surface: "plane",
42
+ full_screen_fluid_plane: "plane",
43
+ fluid_plane: "plane",
44
+ water: "plane",
45
+ // cylinder family
46
+ cylinder: "cylinder",
47
+ tube: "cylinder",
48
+ pillar: "cylinder",
49
+ capsule: "cylinder",
50
+ // cone family
51
+ cone: "cone",
52
+ pyramid: "cone",
53
+ spike: "cone",
54
+ crystal: "cone",
55
+ diamond: "cone",
56
+ gem: "cone",
57
+ // torus family
58
+ torus: "torus",
59
+ ring: "torus",
60
+ donut: "torus"
61
+ };
62
+ }
63
+ });
64
+
65
+ // src/compiler/render-modules/skybox-registry.ts
66
+ function hexToRgb(hex) {
67
+ const h = hex.replace("#", "");
68
+ return [
69
+ Number((parseInt(h.substring(0, 2), 16) / 255).toFixed(4)),
70
+ Number((parseInt(h.substring(2, 4), 16) / 255).toFixed(4)),
71
+ Number((parseInt(h.substring(4, 6), 16) / 255).toFixed(4))
72
+ ];
73
+ }
74
+ function resolveSkyboxColor(name) {
75
+ const key = String(name ?? "").trim().toLowerCase();
76
+ const hex = SKYBOX_HEX[key];
77
+ return hex ? hexToRgb(hex) : FALLBACK;
78
+ }
79
+ var SKYBOX_HEX, FALLBACK;
80
+ var init_skybox_registry = __esm({
81
+ "src/compiler/render-modules/skybox-registry.ts"() {
82
+ SKYBOX_HEX = {
83
+ // ── deep / space / void ─────────────────────────────────────────────
84
+ deep_machine_ocean: "#052430",
85
+ deep_space: "#05060d",
86
+ space: "#05060d",
87
+ nebula: "#0b0618",
88
+ night_sky: "#0b1020",
89
+ night_overcast: "#14161c",
90
+ void: "#050507",
91
+ midnight: "#070a12",
92
+ // ── water / underwater ──────────────────────────────────────────────
93
+ ocean: "#063240",
94
+ underwater: "#062a3a",
95
+ // ── studio / neutral (light backdrops kept light, just not pure white) ─
96
+ studio: "#c4c8cf",
97
+ neutral_studio: "#c4c8cf",
98
+ studio_soft: "#d2d5da",
99
+ hdr_studio: "#c9ccd2",
100
+ gallery: "#20222a",
101
+ indoor_museum: "#1c1e24",
102
+ interior_dark: "#14151a",
103
+ classroom: "#d2cfc6",
104
+ warehouse: "#3a3c40",
105
+ asset_pipeline_warehouse: "#3a3c40",
106
+ // ── sky / daylight ──────────────────────────────────────────────────
107
+ procedural_sky: "#8fbce6",
108
+ clear_sky: "#8fbce6",
109
+ daylight: "#9ec6ea",
110
+ fantasy_sky: "#2a3a6a",
111
+ sunset: "#46283a",
112
+ dusk: "#2e2740",
113
+ desert_dawn: "#caa079",
114
+ // ── tech / abstract ─────────────────────────────────────────────────
115
+ digital_grid: "#0a0f1e",
116
+ gradient: "#1a2030",
117
+ // ── environments / scenes ───────────────────────────────────────────
118
+ industrial: "#2a2d30",
119
+ urban_courtyard: "#3a3d42",
120
+ ancient_ruins: "#3a3428",
121
+ concert_venue: "#0e0a14",
122
+ lounge: "#1a1220",
123
+ farm: "#7fae5a",
124
+ vault_archive: "#1a140a",
125
+ passthrough: "#0c0c0e",
126
+ default: "#0a0e14"
127
+ };
128
+ FALLBACK = hexToRgb("#0a0e14");
129
+ }
130
+ });
131
+
132
+ // src/compiler/render-modules/geometry-purpose.ts
133
+ function coerceBool(v) {
134
+ if (v === true || v === "true") return true;
135
+ if (v === false || v === "false") return false;
136
+ return void 0;
137
+ }
138
+ function resolveGeometryRole(opts) {
139
+ let purpose = "render";
140
+ const p = String(opts.purpose ?? "").trim().toLowerCase();
141
+ if (p && ALL_PURPOSES.has(p)) {
142
+ purpose = p;
143
+ } else {
144
+ for (const t of opts.traitNames ?? []) {
145
+ const mapped = PURPOSE_TRAITS[String(t).trim().toLowerCase()];
146
+ if (mapped) {
147
+ purpose = mapped;
148
+ break;
149
+ }
150
+ }
151
+ }
152
+ const explicit = coerceBool(opts.visible);
153
+ const visible = explicit !== void 0 ? explicit : purpose === "render";
154
+ return { purpose, visible };
155
+ }
156
+ var INVISIBLE_PURPOSES, PURPOSE_TRAITS, ALL_PURPOSES;
157
+ var init_geometry_purpose = __esm({
158
+ "src/compiler/render-modules/geometry-purpose.ts"() {
159
+ INVISIBLE_PURPOSES = /* @__PURE__ */ new Set([
160
+ "collision",
161
+ "trigger",
162
+ "vfx_emitter",
163
+ "sim_domain",
164
+ "navmesh",
165
+ "occluder",
166
+ "audio_zone"
167
+ ]);
168
+ PURPOSE_TRAITS = {
169
+ collider: "collision",
170
+ collision: "collision",
171
+ physics_collider: "collision",
172
+ rigidbody_collider: "collision",
173
+ trigger: "trigger",
174
+ trigger_volume: "trigger",
175
+ interaction_volume: "trigger",
176
+ emitter: "vfx_emitter",
177
+ vfx_emitter: "vfx_emitter",
178
+ particle_emitter: "vfx_emitter",
179
+ spawn_volume: "vfx_emitter",
180
+ sim_domain: "sim_domain",
181
+ sim_volume: "sim_domain",
182
+ fluid_domain: "sim_domain",
183
+ field_region: "sim_domain",
184
+ navmesh: "navmesh",
185
+ nav_mesh: "navmesh",
186
+ occluder: "occluder",
187
+ occlusion: "occluder",
188
+ audio_zone: "audio_zone",
189
+ audio_volume: "audio_zone"
190
+ };
191
+ ALL_PURPOSES = /* @__PURE__ */ new Set([
192
+ "render",
193
+ ...Array.from(INVISIBLE_PURPOSES)
194
+ ]);
195
+ }
196
+ });
197
+
6
198
  // src/compiler/WebGPUCompiler.ts
7
199
  var WebGPUCompiler_exports = {};
8
200
  __export(WebGPUCompiler_exports, {
@@ -18,6 +210,9 @@ var WebGPUCompiler;
18
210
  var init_WebGPUCompiler = __esm({
19
211
  "src/compiler/WebGPUCompiler.ts"() {
20
212
  init_CompilerBase();
213
+ init_geometry_registry();
214
+ init_skybox_registry();
215
+ init_geometry_purpose();
21
216
  init_DomainBlockCompilerMixin();
22
217
  WebGPUCompiler = class extends CompilerBase {
23
218
  constructor(options = {}) {
@@ -26,6 +221,23 @@ var init_WebGPUCompiler = __esm({
26
221
  this.lines = [];
27
222
  this.indentLevel = 0;
28
223
  this.objectIndex = 0;
224
+ // Resolved once per compile() so findObjProp can fall back to template defaults.
225
+ this.templatesByName = /* @__PURE__ */ new Map();
226
+ // True when any object is a water/fluid surface — gates the animated water
227
+ // pipeline, its WGSL, the tessellated-plane helper, and the shared time uniform.
228
+ this.hasWater = false;
229
+ /** World facts collected at emission time, serialized as the HoloScene Manifest. */
230
+ this.manifestObjects = [];
231
+ /** Accumulated spatial-group translation for the objects currently being
232
+ * emitted — manifest positions are WORLD-space (render applies the same
233
+ * offsets via GroupXform; local coords would be a false fact for grouped
234
+ * scenes). */
235
+ this.manifestGroupOffset = [0, 0, 0];
236
+ // The named channels the water surface's clarity/ripple are bound to (from the
237
+ // .holo's water_clarity_binding / ripple_intensity_binding props). Host state
238
+ // (e.g. Brittney receipts) drives these BY NAME via hsSetChannel.
239
+ this.waterClarityChannel = "environmentWaterClarity";
240
+ this.waterRippleChannel = "environmentRippleIntensity";
29
241
  this.options = {
30
242
  entryPoint: options.entryPoint || "main",
31
243
  enableCompute: options.enableCompute ?? true,
@@ -42,6 +254,13 @@ var init_WebGPUCompiler = __esm({
42
254
  this.lines = [];
43
255
  this.indentLevel = 0;
44
256
  this.objectIndex = 0;
257
+ this.manifestObjects = [];
258
+ this.manifestGroupOffset = [0, 0, 0];
259
+ this.templatesByName = new Map(
260
+ (composition.templates ?? []).map((t) => [t.name, t])
261
+ );
262
+ this.hasWater = this.compositionHasWater(composition);
263
+ if (this.hasWater) this.resolveWaterChannels(composition);
45
264
  this.emit("// Auto-generated by HoloScript WebGPUCompiler");
46
265
  this.emit(
47
266
  `// Source: composition "${this.escapeStringValue(composition.name, "TypeScript")}"`
@@ -55,7 +274,27 @@ var init_WebGPUCompiler = __esm({
55
274
  this.emitGeometryHelpers();
56
275
  this.emitShaderSources();
57
276
  this.emitEnvironment(composition.environment ?? { type: "Environment", properties: [] });
58
- this.emitCamera(composition.camera);
277
+ this.emitCamera(composition);
278
+ this.emitSceneLights(composition);
279
+ if (this.hasWater) {
280
+ const cc = JSON.stringify(this.waterClarityChannel);
281
+ const rc = JSON.stringify(this.waterRippleChannel);
282
+ this.emit(`const hsChannels = { [${cc}]: 1.0, [${rc}]: 0.0 };`);
283
+ this.emit("let hsWaterAmp = 1.0;");
284
+ this.emit(
285
+ "function hsSetChannel(name, value) { if (name in hsChannels) hsChannels[name] = Math.max(0, Math.min(1, value)); return hsChannels[name]; }"
286
+ );
287
+ this.emit(
288
+ `function hsSetWater(o) { if (o) { if (o.clarity !== undefined) hsSetChannel(${cc}, o.clarity); if (o.ripple !== undefined) hsSetChannel(${rc}, o.ripple); if (o.amplitude !== undefined) hsWaterAmp = Math.max(0, o.amplitude); } return { clarity: hsChannels[${cc}], ripple: hsChannels[${rc}], amplitude: hsWaterAmp }; }`
289
+ );
290
+ this.emit(
291
+ 'if (typeof window !== "undefined") { window.hsSetChannel = hsSetChannel; window.hsSetWater = hsSetWater; }'
292
+ );
293
+ this.emit(
294
+ `const waterFrameU = createBuffer(device, new Float32Array([0,0,0,0, hsChannels[${cc}], hsChannels[${rc}], hsWaterAmp, 0]), GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST);`
295
+ );
296
+ this.emit("");
297
+ }
59
298
  this.emit("const holoGraphObjects = [];");
60
299
  this.emit("");
61
300
  if (composition.objects) {
@@ -64,9 +303,7 @@ var init_WebGPUCompiler = __esm({
64
303
  if (composition.spatialGroups) {
65
304
  for (const group of composition.spatialGroups) this.emitGroup(group);
66
305
  }
67
- if (composition.lights) {
68
- for (const light of composition.lights) this.emitLight(light);
69
- }
306
+ this.emitSceneManifest(composition);
70
307
  if (this.options.enableCompute) this.emitComputeShaders(composition);
71
308
  this.emitWebGPUDomainBlocks(composition);
72
309
  this.emitViewportRuntime();
@@ -350,6 +587,18 @@ var init_WebGPUCompiler = __esm({
350
587
  this.emit("return new Float32Array([-x,0,-z,0,1,0, x,0,-z,0,1,0, x,0,z,0,1,0, -x,0,-z,0,1,0, x,0,z,0,1,0, -x,0,z,0,1,0]);");
351
588
  this.dedent();
352
589
  this.emit("}");
590
+ if (this.hasWater) {
591
+ this.emit("function generateWaterPlaneVertices(w, h, n) {");
592
+ this.indent();
593
+ this.emit("const out = [], hw = w/2, hh = h/2, N = Math.max(2, n|0);");
594
+ this.emit("for (let iy=0; iy<N; iy++) { for (let ix=0; ix<N; ix++) {");
595
+ this.emit(" const x0=-hw+w*ix/N, x1=-hw+w*(ix+1)/N, y0=-hh+h*iy/N, y1=-hh+h*(iy+1)/N;");
596
+ this.emit(" const a=[x0,y0,0,0,0,1], b=[x1,y0,0,0,0,1], c=[x1,y1,0,0,0,1], d=[x0,y1,0,0,0,1];");
597
+ this.emit(" out.push(...a,...b,...c, ...a,...c,...d);");
598
+ this.emit("} } return new Float32Array(out);");
599
+ this.dedent();
600
+ this.emit("}");
601
+ }
353
602
  this.emit("function generateSphereVertices(radius, lat, lon) {");
354
603
  this.indent();
355
604
  this.emit("const rings = Math.max(3, lat | 0), segs = Math.max(3, lon | 0); const out = [];");
@@ -415,7 +664,7 @@ var init_WebGPUCompiler = __esm({
415
664
  const envData = {};
416
665
  for (const prop of env.properties) envData[prop.key] = prop.value;
417
666
  const bg = envData.background || envData.skybox || "#000000";
418
- const bgColor = this.parseColor(bg);
667
+ const bgColor = typeof bg === "string" && bg.startsWith("#") || Array.isArray(bg) ? this.parseColor(bg) : resolveSkyboxColor(bg);
419
668
  this.emit(
420
669
  `const clearColor: GPUColor = { r: ${bgColor[0]}, g: ${bgColor[1]}, b: ${bgColor[2]}, a: 1.0 };`
421
670
  );
@@ -442,6 +691,41 @@ var init_WebGPUCompiler = __esm({
442
691
  const isModel = meshType === "model" || !!modelSrc;
443
692
  const traits = obj.traits || [];
444
693
  this.emit(`// Object: ${this.escapeStringValue(obj.name, "TypeScript")}`);
694
+ const role = resolveGeometryRole({
695
+ purpose: this.findObjProp(obj, "purpose"),
696
+ visible: this.findObjProp(obj, "visible"),
697
+ traitNames: traits.map((t) => t.name)
698
+ });
699
+ if (!role.visible) {
700
+ const prim = resolveGeometry(meshType).kind;
701
+ const pos = this.findObjProp(obj, "position");
702
+ const [px, py, pz] = Array.isArray(pos) ? pos : [0, 0, 0];
703
+ this.emit(
704
+ `// Functional geometry \u2014 purpose="${role.purpose}", not rendered; primitive="${prim}" is shared with physics/vfx/sim consumers.`
705
+ );
706
+ this.emit(
707
+ `holoGraphObjects.push({ id: "${this.escapeStringValue(obj.name, "TypeScript")}", name: "${this.escapeStringValue(obj.name, "TypeScript")}", geometry: "${this.escapeStringValue(meshType, "TypeScript")}", primitive: "${prim}", purpose: "${role.purpose}", visible: false, traits: ${this.json(traits.map((t) => t.name))}, basePosition: [${px},${py},${pz}], position: [${px},${py},${pz}] });`
708
+ );
709
+ this.manifestObjects.push({
710
+ id: String(obj.name ?? ""),
711
+ geometry: meshType,
712
+ primitive: prim,
713
+ purpose: role.purpose,
714
+ visible: false,
715
+ traits: traits.map((t) => t.name),
716
+ affordances: this.extractAffordanceNames(obj),
717
+ position: [
718
+ px + this.manifestGroupOffset[0],
719
+ py + this.manifestGroupOffset[1],
720
+ pz + this.manifestGroupOffset[2]
721
+ ]
722
+ });
723
+ this.emit("");
724
+ if (obj.children) {
725
+ for (const child of obj.children) this.emitObject(child);
726
+ }
727
+ return;
728
+ }
445
729
  const isGaussianSplat = traits.some((t) => t.name === "gaussian_splat");
446
730
  const isPointCloud = traits.some((t) => t.name === "point_cloud");
447
731
  const isGpuParticle = traits.some((t) => isGpuParticleTraitName(t.name));
@@ -460,6 +744,8 @@ var init_WebGPUCompiler = __esm({
460
744
  this.emitPointCloudObject(v, obj);
461
745
  } else if (isGpuParticle) {
462
746
  this.emitGpuParticleObject(v, obj);
747
+ } else if (this.isWaterObject(obj)) {
748
+ this.emitWaterObject(v, obj);
463
749
  } else {
464
750
  this.emitMeshObject(v, obj, meshType);
465
751
  }
@@ -477,6 +763,203 @@ var init_WebGPUCompiler = __esm({
477
763
  for (const child of obj.children) this.emitObject(child);
478
764
  }
479
765
  }
766
+ // ─── Water / fluid surfaces ────────────────────────────────────────────────
767
+ /**
768
+ * Resolve which named channels the water surface's clarity/ripple are bound to,
769
+ * from the first water object's `water_clarity_binding` / `ripple_intensity_binding`
770
+ * props. These names match the .holo's environment state fields, so host state
771
+ * (Brittney receipts) driving those fields drives the water by name.
772
+ */
773
+ resolveWaterChannels(composition) {
774
+ const all = [...composition.objects ?? []];
775
+ for (const g of composition.spatialGroups ?? []) all.push(...g.objects ?? []);
776
+ const water = all.find((o) => this.isWaterObject(o));
777
+ if (!water) return;
778
+ const clarity = this.findObjProp(water, "water_clarity_binding");
779
+ const ripple = this.findObjProp(water, "ripple_intensity_binding");
780
+ if (typeof clarity === "string" && clarity.trim()) this.waterClarityChannel = clarity.trim();
781
+ if (typeof ripple === "string" && ripple.trim()) this.waterRippleChannel = ripple.trim();
782
+ }
783
+ /** True if any object (incl. nested groups/children) is a water/fluid surface. */
784
+ compositionHasWater(composition) {
785
+ const scan = (objs) => (objs ?? []).some((o) => this.isWaterObject(o) || scan(o.children));
786
+ if (scan(composition.objects)) return true;
787
+ const walk = (g) => scan(g.objects) || (g.groups ?? []).some(walk);
788
+ return (composition.spatialGroups ?? []).some(walk);
789
+ }
790
+ /**
791
+ * A water surface is a fluid-plane mesh (full_screen_fluid_plane / water / ...),
792
+ * or a water-ish MATERIAL on a plane-like geometry. It is NOT a water material on
793
+ * a sphere — emitWaterObject always renders a tessellated plane.
794
+ */
795
+ isWaterObject(obj) {
796
+ const mesh = String(this.findObjProp(obj, "mesh") ?? this.findObjProp(obj, "geometry") ?? "").trim().toLowerCase();
797
+ const material = String(this.findObjProp(obj, "material") ?? "").trim().toLowerCase();
798
+ const WATER_MESH = ["full_screen_fluid_plane", "fluid_plane", "water", "water_plane", "ocean"];
799
+ const WATER_MAT = ["liquid_glass", "water", "liquid", "fluid"];
800
+ if (WATER_MESH.includes(mesh)) return true;
801
+ return WATER_MAT.includes(material) && resolveGeometry(mesh).kind === "plane";
802
+ }
803
+ /**
804
+ * Emit an animated water surface: a tessellated plane driven by the shared
805
+ * per-frame time uniform through the WGSL_WATER shader (Gerstner-lite height
806
+ * waves + analytic normals + texture-free caustics + fresnel). Binding layout
807
+ * mirrors the mesh path (0=Model, 2=vpUniform) plus 1=WaterStatic, 3=waterFrameU.
808
+ */
809
+ emitWaterObject(v, obj) {
810
+ const pos = this.findObjProp(obj, "position");
811
+ const scale = this.findObjProp(obj, "scale") || this.findObjProp(obj, "size");
812
+ const [px, py, pz] = Array.isArray(pos) ? pos : [0, 0, 0];
813
+ const [sx, sy, sz] = Array.isArray(scale) ? scale : typeof scale === "number" ? [scale, scale, scale] : [1, 1, 1];
814
+ const deep = this.parseColor(this.findObjProp(obj, "color") ?? "#06131f");
815
+ const shallow = [
816
+ Math.min(1, deep[0] * 2.2 + 0.05),
817
+ Math.min(1, deep[1] * 2.2 + 0.2),
818
+ Math.min(1, deep[2] * 2 + 0.24)
819
+ ];
820
+ const amplitude = 0.18, choppiness = 0.4, clarity = 1, ripple = 0;
821
+ const L = [0.3714, 0.7428, 0.5571];
822
+ this.emit(`// Water surface: ${this.escapeStringValue(obj.name, "TypeScript")}`);
823
+ this.emit(`const ${v}Vertices = generateWaterPlaneVertices(1.0, 1.0, 48);`);
824
+ this.emit(`const ${v}VBO = createBuffer(device, ${v}Vertices, GPUBufferUsage.VERTEX);`);
825
+ this.emit(`const ${v}VertexCount = ${v}Vertices.length / 6;`);
826
+ this.emit(
827
+ `const ${v}Model = createBuffer(device, new Float32Array([${sx},0,0,0, 0,${sy},0,0, 0,0,${sz},0, ${px},${py},${pz},1]), GPUBufferUsage.UNIFORM);`
828
+ );
829
+ this.emit(
830
+ `const ${v}WaterStatic = createBuffer(device, new Float32Array([${amplitude},${choppiness},${clarity},${ripple}, ${deep[0]},${deep[1]},${deep[2]},0, ${shallow[0]},${shallow[1]},${shallow[2]},0, ${L[0]},${L[1]},${L[2]},0]), GPUBufferUsage.UNIFORM);`
831
+ );
832
+ this.emit(`const ${v}WaterPipeline = device.createRenderPipeline({`);
833
+ this.indent();
834
+ this.emit('layout: "auto",');
835
+ this.emit(
836
+ 'vertex: { module: device.createShaderModule({ code: WGSL_WATER }), entryPoint: "vs_water",'
837
+ );
838
+ this.emit(
839
+ ' buffers: [{ arrayStride: 24, attributes: [{ shaderLocation: 0, offset: 0, format: "float32x3" }, { shaderLocation: 1, offset: 12, format: "float32x3" }] }] },'
840
+ );
841
+ this.emit(
842
+ 'fragment: { module: device.createShaderModule({ code: WGSL_WATER }), entryPoint: "fs_water", targets: [{ format }] },'
843
+ );
844
+ this.emit('primitive: { topology: "triangle-list", cullMode: "none" },');
845
+ this.emit(
846
+ `depthStencil: { format: "depth24plus", depthWriteEnabled: true, depthCompare: "less" }, multisample: { count: MSAA_COUNT },`
847
+ );
848
+ this.dedent();
849
+ this.emit("});");
850
+ this.emit(
851
+ `const ${v}WaterBind = device.createBindGroup({ layout: ${v}WaterPipeline.getBindGroupLayout(0), entries: [`
852
+ );
853
+ this.emit(
854
+ ` { binding: 0, resource: { buffer: ${v}Model } }, { binding: 1, resource: { buffer: ${v}WaterStatic } }, { binding: 2, resource: { buffer: vpUniform } }, { binding: 3, resource: { buffer: waterFrameU } }] });`
855
+ );
856
+ }
857
+ /** Emit the shared WGSL_WATER module (vs_water + fs_water + noise helpers). */
858
+ emitWaterShaderSource() {
859
+ this.emit("const WGSL_WATER = /* wgsl */`");
860
+ this.emit("struct Model { m: mat4x4<f32> };");
861
+ this.emit("struct WStatic { p: vec4<f32>, deep: vec4<f32>, shallow: vec4<f32>, lightDir: vec4<f32> };");
862
+ this.emit("struct Cam { viewProj: mat4x4<f32> };");
863
+ this.emit("struct WFrame { tc: vec4<f32>, dyn: vec4<f32> };");
864
+ this.emit("@group(0) @binding(0) var<uniform> model: Model;");
865
+ this.emit("@group(0) @binding(1) var<uniform> ws: WStatic;");
866
+ this.emit("@group(0) @binding(2) var<uniform> cam: Cam;");
867
+ this.emit("@group(0) @binding(3) var<uniform> wf: WFrame;");
868
+ this.emit("struct VIn { @location(0) pos: vec3<f32>, @location(1) norm: vec3<f32> };");
869
+ this.emit("struct VOut { @builtin(position) clip: vec4<f32>, @location(0) wNorm: vec3<f32>, @location(1) wPos: vec3<f32> };");
870
+ this.emit("@vertex fn vs_water(in: VIn) -> VOut {");
871
+ this.emit(" var o: VOut;");
872
+ this.emit(" let sx = length(model.m[0].xyz); let sy = length(model.m[1].xyz); let sz = max(length(model.m[2].xyz), 1e-4);");
873
+ this.emit(" let wp = in.pos.xy * vec2<f32>(sx, sy);");
874
+ this.emit(" let t = wf.tc.x; let amp = ws.p.x * wf.dyn.z * (0.4 + wf.dyn.y);");
875
+ this.emit(" var h = 0.0; var dhdx = 0.0; var dhdy = 0.0;");
876
+ this.emit(" var dx = 0.0; var dy = 0.0; var k = 0.0; var spd = 0.0; var a = 0.0;");
877
+ this.emit(" for (var i = 0; i < 4; i = i + 1) {");
878
+ this.emit(" if (i == 0) { dx = 1.0; dy = 0.0; k = 0.7; spd = 0.6; a = 1.0; }");
879
+ this.emit(" else if (i == 1) { dx = 0.5; dy = 0.86; k = 1.2; spd = 0.9; a = 0.55; }");
880
+ this.emit(" else if (i == 2) { dx = -0.8; dy = 0.6; k = 1.9; spd = 1.15; a = 0.3; }");
881
+ this.emit(" else { dx = 0.2; dy = -0.98; k = 2.7; spd = 1.4; a = 0.18; }");
882
+ this.emit(" let d = normalize(vec2<f32>(dx, dy));");
883
+ this.emit(" let ph = k * dot(d, wp) + spd * t;");
884
+ this.emit(" h = h + a * amp * sin(ph);");
885
+ this.emit(" dhdx = dhdx + a * amp * k * d.x * cos(ph);");
886
+ this.emit(" dhdy = dhdy + a * amp * k * d.y * cos(ph);");
887
+ this.emit(" }");
888
+ this.emit(" var lp = in.pos; lp.z = lp.z + h / sz;");
889
+ this.emit(" let world = model.m * vec4<f32>(lp, 1.0);");
890
+ this.emit(" o.wPos = world.xyz;");
891
+ this.emit(" o.wNorm = normalize(vec3<f32>(-dhdx, -dhdy, 1.0));");
892
+ this.emit(" o.clip = cam.viewProj * world;");
893
+ this.emit(" return o;");
894
+ this.emit("}");
895
+ this.emit("fn hash21(p: vec2<f32>) -> f32 { let h = dot(p, vec2<f32>(127.1, 311.7)); return fract(sin(h) * 43758.5453); }");
896
+ this.emit("fn vnoise(p: vec2<f32>) -> f32 {");
897
+ this.emit(" let i = floor(p); let f = fract(p); let u = f * f * (3.0 - 2.0 * f);");
898
+ this.emit(" let a = hash21(i); let b = hash21(i + vec2<f32>(1.0, 0.0));");
899
+ this.emit(" let c = hash21(i + vec2<f32>(0.0, 1.0)); let dd = hash21(i + vec2<f32>(1.0, 1.0));");
900
+ this.emit(" return mix(mix(a, b, u.x), mix(c, dd, u.x), u.y);");
901
+ this.emit("}");
902
+ this.emit("struct FIn { @location(0) wNorm: vec3<f32>, @location(1) wPos: vec3<f32> };");
903
+ this.emit("@fragment fn fs_water(in: FIn) -> @location(0) vec4<f32> {");
904
+ this.emit(" let N = normalize(in.wNorm);");
905
+ this.emit(" let V = normalize(wf.tc.yzw - in.wPos);");
906
+ this.emit(" let L = normalize(ws.lightDir.xyz);");
907
+ this.emit(" let t = wf.tc.x;");
908
+ this.emit(" let ndv = max(dot(N, V), 0.0);");
909
+ this.emit(" let fres = 0.02 + 0.98 * pow(1.0 - ndv, 5.0);");
910
+ this.emit(" let tilt = clamp(1.0 - ndv, 0.0, 1.0);");
911
+ this.emit(" var col = mix(ws.deep.rgb, ws.shallow.rgb, clamp(tilt * 1.3, 0.0, 1.0));");
912
+ this.emit(" let p = in.wPos.xy;");
913
+ this.emit(" let n1 = vnoise(p * 0.9 + vec2<f32>(t * 0.15, t * 0.08));");
914
+ this.emit(" let n2 = vnoise(p * 1.7 - vec2<f32>(t * 0.11, t * 0.19));");
915
+ this.emit(" let caustic = pow(n1 * n2, 3.0);");
916
+ this.emit(" col = col + vec3<f32>(0.10, 0.18, 0.20) * caustic * wf.dyn.x;");
917
+ this.emit(" let H = normalize(L + V);");
918
+ this.emit(" let spec = pow(max(dot(N, H), 0.0), 200.0);");
919
+ this.emit(" col = col + vec3<f32>(0.85, 0.92, 1.0) * spec;");
920
+ this.emit(" let sky = vec3<f32>(0.10, 0.26, 0.36);");
921
+ this.emit(" col = mix(col, sky, fres * 0.3);");
922
+ this.emit(" return vec4<f32>(col, 1.0);");
923
+ this.emit("}`;");
924
+ this.emit("");
925
+ }
926
+ /** Tool-affordance names declared on an object's @tool traits (both trait
927
+ * config formats), for the scene manifest — the artifact-level answer to
928
+ * "which named actions does this entity offer" that the numeric registry
929
+ * cannot express. */
930
+ extractAffordanceNames(obj) {
931
+ const names = [];
932
+ for (const trait of obj.traits ?? []) {
933
+ const t = trait;
934
+ if (t.name !== "tool") continue;
935
+ const config = "config" in t && typeof t.config === "object" && t.config !== null ? t.config : t;
936
+ if (typeof config["name"] === "string" && config["name"] !== "tool") {
937
+ names.push(config["name"]);
938
+ } else {
939
+ names.push("unnamed_tool");
940
+ }
941
+ }
942
+ return names;
943
+ }
944
+ /**
945
+ * HoloScene Manifest — a machine-readable world-facts block INSIDE the
946
+ * delivered artifact (g9tk). Collected at EMISSION time (each emitObject/
947
+ * emitMeshObject records what it actually emitted), so the manifest describes
948
+ * the artifact, not the input. Strict JSON payload: independent consumers
949
+ * (perceiver derivations, tooling) JSON.parse it instead of regexing the
950
+ * imperative holoGraphObjects push literals.
951
+ */
952
+ emitSceneManifest(composition) {
953
+ const manifest = {
954
+ version: "holo-scene-manifest-v1",
955
+ source: String(composition.name ?? ""),
956
+ objects: this.manifestObjects
957
+ };
958
+ this.emit("// === HoloScene Manifest (machine-readable world facts; part of the delivered artifact) ===");
959
+ this.emit(`const holoSceneManifest = ${this.json(manifest)};`);
960
+ this.emit('if (typeof globalThis !== "undefined") { globalThis.holoSceneManifest = holoSceneManifest; }');
961
+ this.emit("");
962
+ }
480
963
  emitMeshObject(v, obj, meshType) {
481
964
  this.emit(`const ${v}Vertices = ${this.geometryVertexDataFn(meshType)};`);
482
965
  this.emit(`const ${v}VBO = createBuffer(device, ${v}Vertices, GPUBufferUsage.VERTEX);`);
@@ -493,14 +976,35 @@ var init_WebGPUCompiler = __esm({
493
976
  const [cr, cg, cb] = this.extractMaterialColor(material, color);
494
977
  const rough = this.extractMaterialProp(material, "roughness", 0.5);
495
978
  const metal = this.extractMaterialProp(material, "metalness", 0);
496
- const [er, eg, eb] = this.extractEmissive(material, obj);
497
- const emissiveStrength = this.extractMaterialProp(material, "emissive_intensity", 1);
979
+ let [er, eg, eb] = this.extractEmissive(material, obj);
980
+ let emissiveStrength = this.extractMaterialProp(material, "emissive_intensity", 1);
981
+ const glow = this.materialGlow(material);
982
+ if (glow > 0 && er === 0 && eg === 0 && eb === 0) {
983
+ er = cr;
984
+ eg = cg;
985
+ eb = cb;
986
+ emissiveStrength = glow;
987
+ }
498
988
  this.emit(
499
989
  `const ${v}Mat = createBuffer(device, new Float32Array([${cr},${cg},${cb},1.0, ${rough},${metal},${emissiveStrength},0, ${er},${eg},${eb},0]), GPUBufferUsage.UNIFORM);`
500
990
  );
501
991
  this.emit(
502
992
  `holoGraphObjects.push({ id: "${this.escapeStringValue(obj.name, "TypeScript")}", name: "${this.escapeStringValue(obj.name, "TypeScript")}", geometry: "${this.escapeStringValue(meshType, "TypeScript")}", traits: ${this.json((obj.traits || []).map((t) => t.name))}, properties: ${this.json(this.extractObjectProperties(obj))}, basePosition: [${px},${py},${pz}], position: [${px},${py},${pz}], baseScale: [${sx},${sy},${sz}], scale: [${sx},${sy},${sz}], baseColor: [${cr},${cg},${cb}], color: [${cr},${cg},${cb}], material: { roughness: ${rough}, metalness: ${metal}, emissiveStrength: ${emissiveStrength}, emissive: [${er},${eg},${eb}] }, modelBuffer: ${v}Model, matBuffer: ${v}Mat, visible: true });`
503
993
  );
994
+ this.manifestObjects.push({
995
+ id: String(obj.name ?? ""),
996
+ geometry: meshType,
997
+ visible: true,
998
+ traits: (obj.traits || []).map((t) => t.name),
999
+ affordances: this.extractAffordanceNames(obj),
1000
+ position: [
1001
+ px + this.manifestGroupOffset[0],
1002
+ py + this.manifestGroupOffset[1],
1003
+ pz + this.manifestGroupOffset[2]
1004
+ ],
1005
+ scale: [sx, sy, sz],
1006
+ color: [cr, cg, cb]
1007
+ });
504
1008
  this.emit(`const ${v}Pipeline = device.createRenderPipeline({`);
505
1009
  this.indent();
506
1010
  this.emit('layout: "auto",');
@@ -523,7 +1027,7 @@ var init_WebGPUCompiler = __esm({
523
1027
  `const ${v}Bind = device.createBindGroup({ layout: ${v}Pipeline.getBindGroupLayout(0), entries: [`
524
1028
  );
525
1029
  this.emit(
526
- ` { binding: 0, resource: { buffer: ${v}Model } }, { binding: 1, resource: { buffer: ${v}Mat } }, { binding: 2, resource: { buffer: vpUniform } }] });`
1030
+ ` { binding: 0, resource: { buffer: ${v}Model } }, { binding: 1, resource: { buffer: ${v}Mat } }, { binding: 2, resource: { buffer: vpUniform } }, { binding: 3, resource: { buffer: sceneLights } }] });`
527
1031
  );
528
1032
  }
529
1033
  emitGaussianSplatObject(v, obj) {
@@ -630,43 +1134,140 @@ var init_WebGPUCompiler = __esm({
630
1134
  this.emit(
631
1135
  `const ${v}GroupXform = createBuffer(device, new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, ${px},${py},${pz},1]), GPUBufferUsage.UNIFORM);`
632
1136
  );
1137
+ const saved = this.manifestGroupOffset;
1138
+ this.manifestGroupOffset = [saved[0] + px, saved[1] + py, saved[2] + pz];
633
1139
  for (const obj of group.objects) this.emitObject(obj);
634
1140
  if (group.groups) {
635
1141
  for (const sub of group.groups) this.emitGroup(sub);
636
1142
  }
1143
+ this.manifestGroupOffset = saved;
637
1144
  this.emit("");
638
1145
  }
639
1146
  // ─── Light ────────────────────────────────────────────────────────────────
640
- emitLight(light) {
641
- const v = this.sanitizeName(light.name);
642
- let color = [1, 1, 1], intensity = 1, pos = [0, 10, 0], dir = [0, -1, 0];
643
- for (const prop of light.properties) {
644
- if (prop.key === "color") color = this.parseColor(prop.value);
645
- else if (prop.key === "intensity") intensity = prop.value;
646
- else if (prop.key === "position" && Array.isArray(prop.value)) pos = prop.value;
647
- else if (prop.key === "direction" && Array.isArray(prop.value)) dir = prop.value;
1147
+ // Aggregate the scene's declared lights into ONE shared uniform buffer that every
1148
+ // mesh binds (binding 3). Shape math — color*intensity, type tag, world position,
1149
+ // travel direction is resolved here; the fragment shader consumes up to
1150
+ // MAX_LIGHTS. A light-less scene gets a synthesized key light so it stays lit.
1151
+ emitSceneLights(composition) {
1152
+ const MAX_LIGHTS = 4;
1153
+ const typeIndex = {
1154
+ directional: 0,
1155
+ point: 1,
1156
+ spot: 2,
1157
+ hemisphere: 3,
1158
+ ambient: 4,
1159
+ area: 5
1160
+ };
1161
+ const lights = (composition.lights ?? []).slice(0, MAX_LIGHTS).map((light) => {
1162
+ let color = [1, 1, 1], intensity = 1, pos = [0, 10, 0], dir = [0, -1, 0], hasDir = false;
1163
+ for (const prop of light.properties) {
1164
+ if (prop.key === "color") color = this.parseColor(prop.value);
1165
+ else if (prop.key === "intensity") intensity = prop.value;
1166
+ else if (prop.key === "position" && Array.isArray(prop.value)) pos = prop.value;
1167
+ else if (prop.key === "direction" && Array.isArray(prop.value)) {
1168
+ dir = prop.value;
1169
+ hasDir = true;
1170
+ }
1171
+ }
1172
+ const ti = typeIndex[light.lightType] ?? 0;
1173
+ if (ti === 0 && !hasDir && Array.isArray(pos)) dir = [-pos[0], -pos[1], -pos[2]];
1174
+ return { name: String(light.name ?? ""), kind: String(light.lightType ?? ""), color, intensity, pos, dir, ti };
1175
+ });
1176
+ if (lights.length === 0) {
1177
+ lights.push({ name: "(synthesized key light)", kind: "directional", color: [1, 1, 1], intensity: 1, pos: [1, 2, 1.5], dir: [-1, -2, -1.5], ti: 0 });
1178
+ }
1179
+ const segs = [`${lights.length},0,0,0`];
1180
+ for (let i = 0; i < MAX_LIGHTS; i++) {
1181
+ const l = lights[i];
1182
+ if (l) {
1183
+ segs.push(`${l.color[0] * l.intensity},${l.color[1] * l.intensity},${l.color[2] * l.intensity},${l.ti}`);
1184
+ segs.push(`${l.pos[0]},${l.pos[1]},${l.pos[2]},0`);
1185
+ segs.push(`${l.dir[0]},${l.dir[1]},${l.dir[2]},0`);
1186
+ } else {
1187
+ segs.push("0,0,0,0", "0,0,0,0", "0,0,0,0");
1188
+ }
1189
+ }
1190
+ this.emit("// === Scene Lights (shared uniform, bound by every mesh at binding 3) ===");
1191
+ for (let i = 0; i < lights.length; i++) {
1192
+ const safeName = lights[i].name.replace(/[\r\n*]+/g, " ");
1193
+ this.emit(`// Light[${i}]: "${safeName}" (${lights[i].kind}, intensity ${lights[i].intensity})`);
648
1194
  }
649
- const ti = { directional: 0, point: 1, spot: 2, hemisphere: 3, ambient: 4, area: 5 }[light.lightType] ?? 0;
650
- this.emit(
651
- `// Light: ${this.escapeStringValue(light.name, "TypeScript")} (${light.lightType})`
652
- );
653
1195
  this.emit(
654
- `const ${v}Light = createBuffer(device, new Float32Array([${color[0] * intensity},${color[1] * intensity},${color[2] * intensity},${ti}, ${pos[0]},${pos[1]},${pos[2]},0, ${dir[0]},${dir[1]},${dir[2]},0]), GPUBufferUsage.UNIFORM);`
1196
+ `const sceneLights = createBuffer(device, new Float32Array([${segs.join(", ")}]), GPUBufferUsage.UNIFORM);`
655
1197
  );
656
1198
  this.emit("");
657
1199
  }
658
1200
  // ─── Camera ───────────────────────────────────────────────────────────────
659
- emitCamera(cam) {
1201
+ // Compute a bounding sphere (center + radius) over every object's world position,
1202
+ // including spatial-group offsets and per-object scale. Used to auto-frame the
1203
+ // camera when the author declared no explicit position. Returns null for an
1204
+ // object-less scene (nothing to frame → keep the neutral default).
1205
+ sceneBoundsForCamera(composition) {
1206
+ const pts = [];
1207
+ const R_CAP = 3;
1208
+ const addObj = (obj, offset) => {
1209
+ const pos = this.findObjProp(obj, "position");
1210
+ const scale = this.findObjProp(obj, "scale") || this.findObjProp(obj, "size");
1211
+ const [px, py, pz] = Array.isArray(pos) ? pos : [0, 0, 0];
1212
+ const rawR = Array.isArray(scale) ? Math.max(...scale.map((n) => Math.abs(n))) : typeof scale === "number" ? Math.abs(scale) : 1;
1213
+ pts.push({ p: [px + offset[0], py + offset[1], pz + offset[2]], r: Math.min(rawR, R_CAP) });
1214
+ };
1215
+ for (const obj of composition.objects ?? []) addObj(obj, [0, 0, 0]);
1216
+ const walkGroup = (g, parent) => {
1217
+ const gp = g.properties?.find((p) => p.key === "position")?.value;
1218
+ const [gx, gy, gz] = Array.isArray(gp) ? gp : [0, 0, 0];
1219
+ const off = [parent[0] + gx, parent[1] + gy, parent[2] + gz];
1220
+ for (const obj of g.objects ?? []) addObj(obj, off);
1221
+ for (const sub of g.groups ?? []) walkGroup(sub, off);
1222
+ };
1223
+ for (const g of composition.spatialGroups ?? []) walkGroup(g, [0, 0, 0]);
1224
+ if (pts.length === 0) return null;
1225
+ const min = [Infinity, Infinity, Infinity];
1226
+ const max = [-Infinity, -Infinity, -Infinity];
1227
+ for (const { p, r } of pts) {
1228
+ for (let i = 0; i < 3; i++) {
1229
+ min[i] = Math.min(min[i], p[i] - r);
1230
+ max[i] = Math.max(max[i], p[i] + r);
1231
+ }
1232
+ }
1233
+ const center = [(min[0] + max[0]) / 2, (min[1] + max[1]) / 2, (min[2] + max[2]) / 2];
1234
+ let radius = 0;
1235
+ for (const { p, r } of pts) {
1236
+ radius = Math.max(radius, Math.hypot(p[0] - center[0], p[1] - center[1], p[2] - center[2]) + r);
1237
+ }
1238
+ return { center, radius: radius || 1 };
1239
+ }
1240
+ emitCamera(composition) {
1241
+ const cam = composition.camera;
660
1242
  this.emit("// === Camera ===");
661
- let fov = 60, near = 0.1, far = 1e3, pos = [4, 3, 6], lookAt = [0, 0, 0];
1243
+ let fov = 60, near = 0.1, far = 1e3, pos = [4, 3, 6], lookAt = [0, 0, 0], hasPos = false;
662
1244
  for (const prop of cam?.properties ?? []) {
663
1245
  if (prop.key === "fov" || prop.key === "field_of_view") fov = prop.value;
664
1246
  else if (prop.key === "near") near = prop.value;
665
1247
  else if (prop.key === "far") far = prop.value;
666
- else if (prop.key === "position" && Array.isArray(prop.value)) pos = prop.value;
667
- else if ((prop.key === "look_at" || prop.key === "lookAt") && Array.isArray(prop.value))
1248
+ else if (prop.key === "position" && Array.isArray(prop.value)) {
1249
+ pos = prop.value;
1250
+ hasPos = true;
1251
+ } else if ((prop.key === "look_at" || prop.key === "lookAt") && Array.isArray(prop.value))
668
1252
  lookAt = prop.value;
669
1253
  }
1254
+ if (!hasPos) {
1255
+ const b = this.sceneBoundsForCamera(composition);
1256
+ if (b) {
1257
+ const r4 = (n) => Number(n.toFixed(4));
1258
+ const fovRad = fov * Math.PI / 180;
1259
+ const dist = b.radius / Math.sin(fovRad / 2) * 1.1;
1260
+ const dl = Math.hypot(0.35, 0.45, 1);
1261
+ const dir = [0.35 / dl, 0.45 / dl, 1 / dl];
1262
+ pos = [
1263
+ r4(b.center[0] + dir[0] * dist),
1264
+ r4(b.center[1] + dir[1] * dist),
1265
+ r4(b.center[2] + dir[2] * dist)
1266
+ ];
1267
+ lookAt = [r4(b.center[0]), r4(b.center[1]), r4(b.center[2])];
1268
+ far = Math.max(far, r4(dist + b.radius * 2 + 10));
1269
+ }
1270
+ }
670
1271
  this.emit(`const cameraFov = ${fov} * Math.PI / 180;`);
671
1272
  this.emit(`const cameraNear = ${near}, cameraFar = ${far};`);
672
1273
  this.emit(`const cameraPos = new Float32Array([${pos.join(",")}]);`);
@@ -766,15 +1367,27 @@ var init_WebGPUCompiler = __esm({
766
1367
  this.emit("");
767
1368
  this.emit("const WGSL_FRAGMENT = /* wgsl */`");
768
1369
  this.emit("struct Mat { color: vec4<f32>, rm: vec4<f32>, emissive: vec4<f32> };");
1370
+ this.emit("struct Light { ct: vec4<f32>, posW: vec4<f32>, dirW: vec4<f32> };");
1371
+ this.emit("struct Lights { count: vec4<f32>, items: array<Light, 4> };");
769
1372
  this.emit("@group(0) @binding(1) var<uniform> mat: Mat;");
1373
+ this.emit("@group(0) @binding(3) var<uniform> lights: Lights;");
770
1374
  this.emit("struct FIn { @location(0) wNorm: vec3<f32>, @location(1) wPos: vec3<f32> };");
771
1375
  this.emit("@fragment fn fs_main(i: FIn) -> @location(0) vec4<f32> {");
772
- this.emit(" let N = normalize(i.wNorm); let L = normalize(vec3<f32>(1.0, 2.0, 1.5));");
773
- this.emit(" let d = max(dot(N, L), 0.0) * (1.0 - mat.rm[0] * 0.5);");
774
- this.emit(" let lit = mat.color.rgb * (0.15 + d);");
1376
+ this.emit(" let N = normalize(i.wNorm);");
1377
+ this.emit(" var acc = vec3<f32>(0.0, 0.0, 0.0);");
1378
+ this.emit(" let n = i32(lights.count.x);");
1379
+ this.emit(" for (var k = 0; k < n; k = k + 1) {");
1380
+ this.emit(" let lt = lights.items[k];");
1381
+ this.emit(" var L = normalize(-lt.dirW.xyz);");
1382
+ this.emit(" if (lt.ct.w > 0.5) { L = normalize(lt.posW.xyz - i.wPos); }");
1383
+ this.emit(" let d = max(dot(N, L), 0.0) * (1.0 - mat.rm[0] * 0.5);");
1384
+ this.emit(" acc = acc + lt.ct.rgb * d;");
1385
+ this.emit(" }");
1386
+ this.emit(" let lit = mat.color.rgb * (0.15 + acc);");
775
1387
  this.emit(" let emit = mat.emissive.rgb * mat.rm[2];");
776
1388
  this.emit(" return vec4<f32>(lit + emit, mat.color.a); }`;");
777
1389
  this.emit("");
1390
+ if (this.hasWater) this.emitWaterShaderSource();
778
1391
  this.emit("const WGSL_SPLAT = /* wgsl */`");
779
1392
  this.emit(
780
1393
  "struct Splat { center: vec3<f32>, opacity: f32, cov: array<f32,6>, sh: vec3<f32> };"
@@ -1009,6 +1622,11 @@ var init_WebGPUCompiler = __esm({
1009
1622
  this.emit("frameCount++;");
1010
1623
  this.emit("const time = (performance.now() - t0) / 1000.0;");
1011
1624
  this.emit("device.queue.writeBuffer(vpUniform, 0, buildViewProjection());");
1625
+ if (this.hasWater) {
1626
+ this.emit(
1627
+ `device.queue.writeBuffer(waterFrameU, 0, new Float32Array([time, cameraPos[0], cameraPos[1], cameraPos[2], hsChannels[${JSON.stringify(this.waterClarityChannel)}], hsChannels[${JSON.stringify(this.waterRippleChannel)}], hsWaterAmp, 0]));`
1628
+ );
1629
+ }
1012
1630
  this.emit("hsRenderLabels();");
1013
1631
  this.emit("const enc = device.createCommandEncoder();");
1014
1632
  const gpuObjs = (composition.objects || []).filter(
@@ -1065,6 +1683,17 @@ var init_WebGPUCompiler = __esm({
1065
1683
  emitDrawCall(obj) {
1066
1684
  const v = this.sanitizeName(obj.name);
1067
1685
  const traits = obj.traits || [];
1686
+ const role = resolveGeometryRole({
1687
+ purpose: this.findObjProp(obj, "purpose"),
1688
+ visible: this.findObjProp(obj, "visible"),
1689
+ traitNames: traits.map((t) => t.name)
1690
+ });
1691
+ if (!role.visible) {
1692
+ if (obj.children) {
1693
+ for (const child of obj.children) this.emitDrawCall(child);
1694
+ }
1695
+ return;
1696
+ }
1068
1697
  if (traits.some((t) => t.name === "gaussian_splat")) {
1069
1698
  this.emit(`rp.setPipeline(${v}SplatPipeline); rp.draw(4, ${v}SplatCount.value);`);
1070
1699
  } else if (traits.some((t) => t.name === "point_cloud")) {
@@ -1073,6 +1702,10 @@ var init_WebGPUCompiler = __esm({
1073
1702
  this.emit(
1074
1703
  `rp.setPipeline(${v}ParticlePipeline); rp.setVertexBuffer(0, ${v}ParticleBufA); rp.draw(${v}ParticleCount);`
1075
1704
  );
1705
+ } else if (this.isWaterObject(obj)) {
1706
+ this.emit(
1707
+ `rp.setPipeline(${v}WaterPipeline); rp.setBindGroup(0, ${v}WaterBind); rp.setVertexBuffer(0, ${v}VBO); rp.draw(${v}VertexCount);`
1708
+ );
1076
1709
  } else {
1077
1710
  this.emit(
1078
1711
  `rp.setPipeline(${v}Pipeline); rp.setBindGroup(0, ${v}Bind); rp.setVertexBuffer(0, ${v}VBO); rp.draw(${v}VertexCount);`
@@ -1150,7 +1783,14 @@ var init_WebGPUCompiler = __esm({
1150
1783
  return h >>> 0;
1151
1784
  }
1152
1785
  findObjProp(obj, key) {
1153
- return obj.properties?.find((p) => p.key === key)?.value;
1786
+ const own = obj.properties?.find((p) => p.key === key)?.value;
1787
+ if (own !== void 0) return own;
1788
+ if (obj.template) {
1789
+ const tpl = this.templatesByName.get(obj.template);
1790
+ const fromTemplate = tpl?.properties?.find((p) => p.key === key)?.value;
1791
+ if (fromTemplate !== void 0) return fromTemplate;
1792
+ }
1793
+ return void 0;
1154
1794
  }
1155
1795
  extractObjectProperties(obj) {
1156
1796
  const properties = {};
@@ -1202,21 +1842,34 @@ var init_WebGPUCompiler = __esm({
1202
1842
  return [0, 0, 0];
1203
1843
  }
1204
1844
  geometryVertexDataFn(meshType) {
1205
- const map = {
1206
- sphere: "generateSphereVertices(0.5, 16, 16)",
1207
- orb: "generateSphereVertices(0.5, 16, 16)",
1208
- plane: "generatePlaneVertices(1.0, 1.0)",
1209
- cylinder: "generateCylinderVertices(0.5, 1.0, 16)",
1210
- cone: "generateConeVertices(0.5, 1.0, 16)",
1211
- pyramid: "generateConeVertices(0.5, 1.0, 16)",
1212
- torus: "generateTorusVertices(0.5, 0.15, 16, 32)"
1213
- };
1214
- return map[meshType] || "generateCubeVertices(1.0)";
1845
+ return resolveGeometry(meshType).webgpuVertexFn;
1846
+ }
1847
+ materialName(material) {
1848
+ if (typeof material === "string") return material;
1849
+ if (material && typeof material === "object") {
1850
+ const m = material;
1851
+ if (typeof m.type === "string") return m.type;
1852
+ if (typeof m.name === "string") return m.name;
1853
+ }
1854
+ return void 0;
1855
+ }
1856
+ // Self-illumination strength for a named material when no explicit emissive is
1857
+ // authored. Holographic/neon/emissive surfaces glow in their own color; glass/gel
1858
+ // gets a soft sheen; everything else stays purely lit (0).
1859
+ materialGlow(material) {
1860
+ const name = this.materialName(material);
1861
+ if (!name) return 0;
1862
+ const n = name.toLowerCase();
1863
+ if (/hologram|holo|liquid_glass|liquid_hologram|neon|glow|emissive|aura|plasma|energy/.test(n)) {
1864
+ return 0.65;
1865
+ }
1866
+ if (/glass|crystal|gel|water/.test(n)) return 0.28;
1867
+ return 0;
1215
1868
  }
1216
1869
  };
1217
1870
  }
1218
1871
  });
1219
1872
 
1220
- export { WebGPUCompiler, WebGPUCompiler_exports, init_WebGPUCompiler };
1221
- //# sourceMappingURL=chunk-ONPVM4IV.js.map
1222
- //# sourceMappingURL=chunk-ONPVM4IV.js.map
1873
+ export { WebGPUCompiler, WebGPUCompiler_exports, init_WebGPUCompiler, init_geometry_purpose, init_geometry_registry, init_skybox_registry, resolveGeometry, resolveGeometryRole, resolveSkyboxColor };
1874
+ //# sourceMappingURL=chunk-QLPRI3QW.js.map
1875
+ //# sourceMappingURL=chunk-QLPRI3QW.js.map