incanto 0.1.0

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 (138) hide show
  1. package/LICENSE +30 -0
  2. package/README.md +36 -0
  3. package/THIRD-PARTY-NOTICES.md +88 -0
  4. package/assets/audio/attacked.mp3 +0 -0
  5. package/assets/audio/explosion.mp3 +0 -0
  6. package/assets/audio/gold_loot.mp3 +0 -0
  7. package/assets/audio/heal.mp3 +0 -0
  8. package/assets/audio/hit_metal_bang.mp3 +0 -0
  9. package/assets/audio/ice_spear.mp3 +0 -0
  10. package/assets/audio/monster_died.mp3 +0 -0
  11. package/assets/audio/slash.mp3 +0 -0
  12. package/assets/audio/smite.mp3 +0 -0
  13. package/assets/audio/spells_cast.mp3 +0 -0
  14. package/assets/audio/ui_click.wav +0 -0
  15. package/assets/audio/walk.mp3 +0 -0
  16. package/assets/catalog.json +390 -0
  17. package/assets/characters/2dbasic.json +41 -0
  18. package/assets/characters/2dbasic.png +0 -0
  19. package/assets/characters/ghost.json +46 -0
  20. package/assets/characters/ghost.png +0 -0
  21. package/assets/characters/goblin.json +40 -0
  22. package/assets/characters/goblin.png +0 -0
  23. package/assets/characters/medieval-knight.json +41 -0
  24. package/assets/characters/medieval-knight.png +0 -0
  25. package/assets/effects/swoosh.png +0 -0
  26. package/assets/items/box.png +0 -0
  27. package/assets/items/buff_potion.png +0 -0
  28. package/assets/items/coin.png +0 -0
  29. package/assets/items/gem.png +0 -0
  30. package/assets/items/gold.png +0 -0
  31. package/assets/items/hp_potion.png +0 -0
  32. package/assets/items/locked_item_box.png +0 -0
  33. package/assets/items/map.png +0 -0
  34. package/assets/items/resurrection_potion.png +0 -0
  35. package/assets/items/super_box.png +0 -0
  36. package/assets/items/trap.png +0 -0
  37. package/assets/tiles/floor00.jpg +0 -0
  38. package/assets/tiles/minecraft-tiles.png +0 -0
  39. package/assets/tiles/wall00.jpg +0 -0
  40. package/assets/vegetation/ash_color.png +0 -0
  41. package/assets/vegetation/aspen_color.png +0 -0
  42. package/assets/vegetation/bark/birch_color_1k.jpg +0 -0
  43. package/assets/vegetation/bark/birch_normal_1k.jpg +0 -0
  44. package/assets/vegetation/bark/birch_roughness_1k.jpg +0 -0
  45. package/assets/vegetation/bark/oak_color_1k.jpg +0 -0
  46. package/assets/vegetation/bark/oak_normal_1k.jpg +0 -0
  47. package/assets/vegetation/bark/oak_roughness_1k.jpg +0 -0
  48. package/assets/vegetation/bark/pine_color_1k.jpg +0 -0
  49. package/assets/vegetation/bark/pine_normal_1k.jpg +0 -0
  50. package/assets/vegetation/bark/pine_roughness_1k.jpg +0 -0
  51. package/assets/vegetation/ground/dirt_color.jpg +0 -0
  52. package/assets/vegetation/ground/dirt_normal.jpg +0 -0
  53. package/assets/vegetation/ground/grass.jpg +0 -0
  54. package/assets/vegetation/oak_color.png +0 -0
  55. package/assets/vegetation/pine_color.png +0 -0
  56. package/bin/incanto-assets.mjs +107 -0
  57. package/bin/incanto-check.mjs +107 -0
  58. package/bin/incanto-editor.mjs +343 -0
  59. package/bin/incanto-env.mjs +144 -0
  60. package/bin/incanto-model.mjs +296 -0
  61. package/bin/incanto-play.mjs +219 -0
  62. package/bin/incanto-skills.mjs +71 -0
  63. package/dist/2d.d.ts +642 -0
  64. package/dist/2d.js +44 -0
  65. package/dist/3d.d.ts +1860 -0
  66. package/dist/3d.js +5 -0
  67. package/dist/agent8-DzU2fFyH.js +129 -0
  68. package/dist/audio-player-DqUR3XFs.d.ts +110 -0
  69. package/dist/behavior-BAQq7HGM.d.ts +851 -0
  70. package/dist/create-game-BdjpTHrW.js +1725 -0
  71. package/dist/create-game-CZHROKcT.js +527 -0
  72. package/dist/debug-draw-CZmOYjL2.js +13 -0
  73. package/dist/debug.d.ts +66 -0
  74. package/dist/debug.js +658 -0
  75. package/dist/duplicate-DP2WPYom.js +22 -0
  76. package/dist/env.d.ts +430 -0
  77. package/dist/env.js +3152 -0
  78. package/dist/errors-BMFaY68Q.d.ts +33 -0
  79. package/dist/errors-BpWbnbb_.js +13 -0
  80. package/dist/gameplay-Ccruc3Wd.js +1501 -0
  81. package/dist/gameplay.d.ts +543 -0
  82. package/dist/gameplay.js +2 -0
  83. package/dist/heightmap-CroQPEER.js +185 -0
  84. package/dist/index.d.ts +305 -0
  85. package/dist/index.js +62 -0
  86. package/dist/json-BLk7H2Qa.js +30 -0
  87. package/dist/loader-CGs_G-r0.js +919 -0
  88. package/dist/loader-Mo0KghCv.d.ts +41 -0
  89. package/dist/net.d.ts +427 -0
  90. package/dist/net.js +772 -0
  91. package/dist/noise-CGUMx44x.js +82 -0
  92. package/dist/particle-sim-CbN4YUuH.d.ts +63 -0
  93. package/dist/particle-sim-DYuSUxvK.js +1319 -0
  94. package/dist/physics-2d-KuMWPTf6.js +288 -0
  95. package/dist/physics-3d-Dl67vOLT.js +434 -0
  96. package/dist/react.d.ts +65 -0
  97. package/dist/react.js +209 -0
  98. package/dist/register-BuUV1_KB.js +561 -0
  99. package/dist/register-CNlYAS1_.js +10634 -0
  100. package/dist/register-DPEV9_9t.js +851 -0
  101. package/dist/register-Dasmnurl.js +374 -0
  102. package/dist/registry-BVJ2HbCn.js +132 -0
  103. package/dist/rng-DP-SR7eg.js +38 -0
  104. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  105. package/dist/schema-CcoWb32N.d.ts +104 -0
  106. package/dist/test.d.ts +158 -0
  107. package/dist/test.js +275 -0
  108. package/dist/touch-031PxtCR.js +208 -0
  109. package/dist/vite.d.ts +26 -0
  110. package/dist/vite.js +57 -0
  111. package/editor/assets/GameServer-C56iOUgF.js +1 -0
  112. package/editor/assets/agent8-Bp7QFI7v.js +1 -0
  113. package/editor/assets/index-DF3tMeKJ.css +1 -0
  114. package/editor/assets/index-Dl2pjA8e.js +7365 -0
  115. package/editor/assets/rapier-CEuLKeCu.js +1 -0
  116. package/editor/assets/rapier-DE6a0vmv.js +1 -0
  117. package/editor/index.html +169 -0
  118. package/package.json +97 -0
  119. package/schemas/scene.schema.json +4254 -0
  120. package/skills/README.md +9 -0
  121. package/skills/incanto-3d-character.md +229 -0
  122. package/skills/incanto-3d-models.md +151 -0
  123. package/skills/incanto-assets.md +118 -0
  124. package/skills/incanto-audio.md +309 -0
  125. package/skills/incanto-behaviors-and-scripts.md +169 -0
  126. package/skills/incanto-building-2d-games.md +242 -0
  127. package/skills/incanto-building-3d-games.md +245 -0
  128. package/skills/incanto-editor.md +163 -0
  129. package/skills/incanto-environment.md +743 -0
  130. package/skills/incanto-gameplay-behaviors.md +707 -0
  131. package/skills/incanto-multiplayer.md +264 -0
  132. package/skills/incanto-node-reference.md +797 -0
  133. package/skills/incanto-physics-and-input.md +164 -0
  134. package/skills/incanto-scene-json-authoring.md +325 -0
  135. package/skills/incanto-verifying-your-game.md +191 -0
  136. package/skills/incanto-web-integration.md +96 -0
  137. package/templates/agent8-server.js +84 -0
  138. package/templates/agent8-server.ts +138 -0
package/dist/debug.js ADDED
@@ -0,0 +1,658 @@
1
+ import { t as jsonClone } from "./json-BLk7H2Qa.js";
2
+ import { s as mergeStaticProps } from "./registry-BVJ2HbCn.js";
3
+ //#region src/debug/panel.ts
4
+ /** Keep a panel rect on screen and above the minimum usable size. */
5
+ function clampPanelRect(x, y, w, h, viewW, viewH, minW = 180, minH = 120) {
6
+ const cw = Math.max(minW, w);
7
+ const ch = Math.max(minH, h);
8
+ return {
9
+ x: Math.min(Math.max(0, x), Math.max(0, viewW - cw)),
10
+ y: Math.min(Math.max(0, y), Math.max(0, viewH - ch)),
11
+ w: cw,
12
+ h: ch
13
+ };
14
+ }
15
+ function applyStyle(el, rules) {
16
+ for (const [key, value] of Object.entries(rules)) el.style[key] = value;
17
+ }
18
+ const PANEL_BG = "rgba(18, 20, 26, 0.92)";
19
+ const PANEL_BORDER = "1px solid rgba(255,255,255,0.14)";
20
+ const FONT = "12px ui-monospace, SFMono-Regular, Menlo, monospace";
21
+ /**
22
+ * A draggable, resizable, closable window: title bar (drag) + body (content)
23
+ * + corner handle (resize) + × (close). Pure inline styles, zero CSS files.
24
+ */
25
+ var FloatingPanel = class {
26
+ host;
27
+ el;
28
+ body;
29
+ /** Assigned by the overlay — fires when the × is clicked. */
30
+ onClose = () => {};
31
+ x;
32
+ y;
33
+ w;
34
+ h;
35
+ constructor(doc, host, title, rect) {
36
+ this.host = host;
37
+ this.x = rect.x;
38
+ this.y = rect.y;
39
+ this.w = rect.w;
40
+ this.h = rect.h;
41
+ this.el = doc.createElement("div");
42
+ applyStyle(this.el, {
43
+ position: "absolute",
44
+ background: PANEL_BG,
45
+ border: PANEL_BORDER,
46
+ borderRadius: "8px",
47
+ color: "rgba(255,255,255,0.88)",
48
+ font: FONT,
49
+ display: "flex",
50
+ flexDirection: "column",
51
+ overflow: "hidden",
52
+ zIndex: "40",
53
+ pointerEvents: "auto",
54
+ boxShadow: "0 8px 28px rgba(0,0,0,0.45)"
55
+ });
56
+ const header = doc.createElement("div");
57
+ header.textContent = title;
58
+ applyStyle(header, {
59
+ padding: "6px 28px 6px 10px",
60
+ background: "rgba(255,255,255,0.07)",
61
+ cursor: "move",
62
+ userSelect: "none",
63
+ touchAction: "none",
64
+ fontWeight: "700"
65
+ });
66
+ this.el.appendChild(header);
67
+ const close = doc.createElement("div");
68
+ close.textContent = "×";
69
+ close.title = "close";
70
+ applyStyle(close, {
71
+ position: "absolute",
72
+ top: "2px",
73
+ right: "8px",
74
+ cursor: "pointer",
75
+ fontSize: "16px",
76
+ lineHeight: "20px",
77
+ opacity: "0.7"
78
+ });
79
+ close.addEventListener("click", () => this.onClose());
80
+ this.el.appendChild(close);
81
+ this.body = doc.createElement("div");
82
+ applyStyle(this.body, {
83
+ flex: "1",
84
+ overflow: "auto",
85
+ padding: "8px 10px"
86
+ });
87
+ this.el.appendChild(this.body);
88
+ const handle = doc.createElement("div");
89
+ handle.textContent = "◢";
90
+ applyStyle(handle, {
91
+ position: "absolute",
92
+ right: "2px",
93
+ bottom: "0",
94
+ cursor: "nwse-resize",
95
+ opacity: "0.5",
96
+ userSelect: "none",
97
+ touchAction: "none"
98
+ });
99
+ this.el.appendChild(handle);
100
+ this.wireDrag(header, (dx, dy) => {
101
+ this.x += dx;
102
+ this.y += dy;
103
+ this.layout();
104
+ });
105
+ this.wireDrag(handle, (dx, dy) => {
106
+ this.w += dx;
107
+ this.h += dy;
108
+ this.layout();
109
+ });
110
+ this.layout();
111
+ host.appendChild(this.el);
112
+ }
113
+ remove() {
114
+ this.el.remove();
115
+ }
116
+ layout() {
117
+ const view = this.host.getBoundingClientRect();
118
+ const rect = clampPanelRect(this.x, this.y, this.w, this.h, view.width, view.height);
119
+ this.x = rect.x;
120
+ this.y = rect.y;
121
+ this.w = rect.w;
122
+ this.h = rect.h;
123
+ applyStyle(this.el, {
124
+ left: `${rect.x}px`,
125
+ top: `${rect.y}px`,
126
+ width: `${rect.w}px`,
127
+ height: `${rect.h}px`
128
+ });
129
+ }
130
+ wireDrag(grip, move) {
131
+ let active = null;
132
+ let lastX = 0;
133
+ let lastY = 0;
134
+ grip.addEventListener("pointerdown", (e) => {
135
+ active = e.pointerId;
136
+ lastX = e.clientX;
137
+ lastY = e.clientY;
138
+ grip.setPointerCapture?.(e.pointerId);
139
+ });
140
+ grip.addEventListener("pointermove", (e) => {
141
+ if (e.pointerId !== active) return;
142
+ move(e.clientX - lastX, e.clientY - lastY);
143
+ lastX = e.clientX;
144
+ lastY = e.clientY;
145
+ });
146
+ const end = (e) => {
147
+ if (e.pointerId === active) active = null;
148
+ };
149
+ grip.addEventListener("pointerup", end);
150
+ grip.addEventListener("pointercancel", end);
151
+ }
152
+ };
153
+ //#endregion
154
+ //#region src/debug/index.ts
155
+ /** Attach the overlay; null when no DOM is available (headless). */
156
+ function attachDebugOverlay(engine, opts = {}) {
157
+ const doc = opts.doc ?? (typeof document !== "undefined" ? document : null);
158
+ if (!doc) return null;
159
+ const container = opts.container ?? (typeof document !== "undefined" ? document.body : null);
160
+ if (!container || typeof container.appendChild !== "function") return null;
161
+ return new DebugOverlay(engine, container, doc, opts.statsSource);
162
+ }
163
+ const MAX_LOG_ROWS = 300;
164
+ const CONSOLE_LEVELS = [
165
+ "log",
166
+ "info",
167
+ "warn",
168
+ "error",
169
+ "debug"
170
+ ];
171
+ var DebugOverlay = class {
172
+ engine;
173
+ container;
174
+ doc;
175
+ statsSource;
176
+ panels = /* @__PURE__ */ new Map();
177
+ cleanups = [];
178
+ menuButton;
179
+ dropdown = null;
180
+ selected = null;
181
+ statsChip = null;
182
+ logRows = [];
183
+ levelEnabled = {
184
+ debug: true,
185
+ info: true,
186
+ warn: true,
187
+ error: true
188
+ };
189
+ consoleCapture = false;
190
+ consolePatched = [];
191
+ frame = 0;
192
+ editing = 0;
193
+ constructor(engine, container, doc, statsSource) {
194
+ this.engine = engine;
195
+ this.container = container;
196
+ this.doc = doc;
197
+ this.statsSource = statsSource;
198
+ this.menuButton = doc.createElement("div");
199
+ this.menuButton.textContent = "☰ debug";
200
+ applyStyle(this.menuButton, {
201
+ position: "absolute",
202
+ top: "8px",
203
+ left: "8px",
204
+ padding: "4px 10px",
205
+ background: "rgba(18,20,26,0.85)",
206
+ border: "1px solid rgba(255,255,255,0.18)",
207
+ borderRadius: "6px",
208
+ color: "rgba(255,255,255,0.85)",
209
+ font: "12px ui-monospace, Menlo, monospace",
210
+ cursor: "pointer",
211
+ userSelect: "none",
212
+ zIndex: "50",
213
+ pointerEvents: "auto"
214
+ });
215
+ this.menuButton.addEventListener("click", () => this.toggleDropdown());
216
+ if (!container.style.position) container.style.position = "relative";
217
+ container.appendChild(this.menuButton);
218
+ this.cleanups.push(engine.log.added.connect((entry) => {
219
+ this.pushLog({
220
+ level: entry.level,
221
+ source: "engine",
222
+ text: entry.parts.map(stringify).join(" ")
223
+ });
224
+ }));
225
+ this.cleanups.push(engine.sceneChanged.connect(() => {
226
+ this.selected = null;
227
+ this.renderExplorer();
228
+ this.renderInspector();
229
+ }));
230
+ this.cleanups.push(engine.updated.connect(() => {
231
+ this.frame += 1;
232
+ if (this.frame % 30 !== 0) return;
233
+ this.renderExplorer();
234
+ this.renderStats();
235
+ if (this.editing === 0) this.renderInspector();
236
+ }));
237
+ }
238
+ isOpen(id) {
239
+ return id === "stats" ? this.statsChip !== null : this.panels.has(id);
240
+ }
241
+ open(id) {
242
+ if (id === "stats") {
243
+ this.openStatsChip();
244
+ return;
245
+ }
246
+ if (this.panels.has(id)) return;
247
+ const panel = new FloatingPanel(this.doc, this.container, {
248
+ explorer: "Explorer",
249
+ inspector: "Inspector",
250
+ logs: "Logs"
251
+ }[id], {
252
+ explorer: {
253
+ x: 12,
254
+ y: 44,
255
+ w: 240,
256
+ h: 320
257
+ },
258
+ inspector: {
259
+ x: 264,
260
+ y: 44,
261
+ w: 280,
262
+ h: 320
263
+ },
264
+ logs: {
265
+ x: 12,
266
+ y: 380,
267
+ w: 532,
268
+ h: 200
269
+ }
270
+ }[id]);
271
+ panel.onClose = () => this.close(id);
272
+ this.panels.set(id, panel);
273
+ if (id === "explorer") this.renderExplorer();
274
+ if (id === "inspector") this.renderInspector();
275
+ if (id === "logs") this.renderLogs();
276
+ }
277
+ close(id) {
278
+ if (id === "stats") {
279
+ this.statsChip?.remove();
280
+ this.statsChip = null;
281
+ return;
282
+ }
283
+ const panel = this.panels.get(id);
284
+ if (!panel) return;
285
+ panel.remove();
286
+ this.panels.delete(id);
287
+ }
288
+ toggle(id) {
289
+ if (this.isOpen(id)) this.close(id);
290
+ else this.open(id);
291
+ }
292
+ setLevelEnabled(level, on) {
293
+ this.levelEnabled[level] = on;
294
+ this.renderLogs();
295
+ }
296
+ /**
297
+ * Patch console.* into the Logs panel (restored on disable/dispose).
298
+ * Standard monkey-patch hazard: a tool that patches console AFTER capture
299
+ * enables will be clobbered by our restore — enable capture last.
300
+ */
301
+ setConsoleCapture(on) {
302
+ if (on === this.consoleCapture) return;
303
+ this.consoleCapture = on;
304
+ if (on) {
305
+ this.renderLogs();
306
+ const host = console;
307
+ for (const method of CONSOLE_LEVELS) {
308
+ const original = host[method];
309
+ host[method] = (...parts) => {
310
+ original.apply(console, parts);
311
+ const level = method === "log" ? "info" : method;
312
+ this.pushLog({
313
+ level,
314
+ source: "console",
315
+ text: parts.map(stringify).join(" ")
316
+ });
317
+ };
318
+ this.consolePatched.push(() => {
319
+ host[method] = original;
320
+ });
321
+ }
322
+ } else {
323
+ for (const restore of this.consolePatched) restore();
324
+ this.consolePatched = [];
325
+ this.renderLogs();
326
+ }
327
+ }
328
+ dispose() {
329
+ this.setConsoleCapture(false);
330
+ for (const cleanup of this.cleanups) cleanup();
331
+ for (const id of [...this.panels.keys()]) this.close(id);
332
+ this.close("stats");
333
+ this.dropdown?.remove();
334
+ this.dropdown = null;
335
+ this.menuButton.remove();
336
+ }
337
+ toggleDropdown() {
338
+ if (this.dropdown) {
339
+ this.dropdown.remove();
340
+ this.dropdown = null;
341
+ return;
342
+ }
343
+ const menu = this.doc.createElement("div");
344
+ applyStyle(menu, {
345
+ position: "absolute",
346
+ top: "34px",
347
+ left: "8px",
348
+ background: "rgba(18,20,26,0.95)",
349
+ border: "1px solid rgba(255,255,255,0.18)",
350
+ borderRadius: "6px",
351
+ font: "12px ui-monospace, Menlo, monospace",
352
+ color: "rgba(255,255,255,0.85)",
353
+ zIndex: "60",
354
+ pointerEvents: "auto",
355
+ overflow: "hidden"
356
+ });
357
+ for (const [id, label] of [
358
+ ["explorer", "Explorer"],
359
+ ["inspector", "Inspector"],
360
+ ["logs", "Logs"],
361
+ ["stats", "Stats"]
362
+ ]) {
363
+ const item = this.doc.createElement("div");
364
+ item.textContent = `${this.isOpen(id) ? "✓ " : ""}${label}`;
365
+ applyStyle(item, {
366
+ padding: "6px 14px",
367
+ cursor: "pointer",
368
+ userSelect: "none"
369
+ });
370
+ item.addEventListener("click", () => {
371
+ this.toggle(id);
372
+ this.dropdown?.remove();
373
+ this.dropdown = null;
374
+ });
375
+ menu.appendChild(item);
376
+ }
377
+ this.container.appendChild(menu);
378
+ this.dropdown = menu;
379
+ }
380
+ openStatsChip() {
381
+ if (this.statsChip) return;
382
+ const chip = this.doc.createElement("div");
383
+ applyStyle(chip, {
384
+ position: "absolute",
385
+ top: "8px",
386
+ right: "8px",
387
+ padding: "4px 10px",
388
+ background: "rgba(18,20,26,0.85)",
389
+ border: "1px solid rgba(255,255,255,0.18)",
390
+ borderRadius: "6px",
391
+ color: "rgba(255,255,255,0.85)",
392
+ font: "12px ui-monospace, Menlo, monospace",
393
+ textAlign: "right",
394
+ whiteSpace: "pre",
395
+ userSelect: "none",
396
+ pointerEvents: "none",
397
+ zIndex: "70"
398
+ });
399
+ this.container.appendChild(chip);
400
+ this.statsChip = chip;
401
+ this.renderStats();
402
+ }
403
+ renderStats() {
404
+ const chip = this.statsChip;
405
+ if (!chip) return;
406
+ const stats = this.engine.stats();
407
+ const gpu = this.statsSource?.() ?? {};
408
+ const line2 = [
409
+ `nodes ${stats.nodes}`,
410
+ ...gpu.triangles !== void 0 ? [`tris ${compactCount(gpu.triangles)}`] : [],
411
+ ...gpu.drawCalls !== void 0 ? [`calls ${gpu.drawCalls}`] : []
412
+ ].join(" · ");
413
+ chip.textContent = `${Math.round(stats.fps)} fps · ${stats.frameMs.toFixed(1)} ms\n${line2}`;
414
+ }
415
+ renderExplorer() {
416
+ const panel = this.panels.get("explorer");
417
+ if (!panel) return;
418
+ clear(panel.body);
419
+ const root = this.engine.scene?.root;
420
+ if (!root) return;
421
+ const renderNode = (node, depth) => {
422
+ const ctor = node.constructor;
423
+ const row = this.doc.createElement("div");
424
+ row.textContent = `${" ".repeat(depth)}${node.name} (${ctor.typeName})`;
425
+ applyStyle(row, {
426
+ whiteSpace: "pre",
427
+ cursor: "pointer",
428
+ padding: "1px 2px",
429
+ background: node === this.selected ? "rgba(110,160,255,0.25)" : "transparent"
430
+ });
431
+ row.addEventListener("click", () => {
432
+ this.selected = node;
433
+ this.open("inspector");
434
+ this.renderExplorer();
435
+ this.renderInspector();
436
+ });
437
+ panel.body.appendChild(row);
438
+ for (const child of node.children) renderNode(child, depth + 1);
439
+ };
440
+ renderNode(root, 0);
441
+ }
442
+ renderInspector() {
443
+ const panel = this.panels.get("inspector");
444
+ if (!panel) return;
445
+ clear(panel.body);
446
+ let node = this.selected;
447
+ if (node && node.tree === null) {
448
+ this.selected = null;
449
+ node = null;
450
+ }
451
+ if (!node) {
452
+ const hint = this.doc.createElement("div");
453
+ hint.textContent = "select a node in the Explorer";
454
+ applyStyle(hint, { opacity: "0.6" });
455
+ panel.body.appendChild(hint);
456
+ return;
457
+ }
458
+ const ctor = node.constructor;
459
+ const header = this.doc.createElement("div");
460
+ header.textContent = `${node.getPath()} · ${ctor.typeName}${node.uid ? ` · ${node.uid}` : ""}`;
461
+ applyStyle(header, {
462
+ fontWeight: "700",
463
+ marginBottom: "6px",
464
+ whiteSpace: "pre-wrap"
465
+ });
466
+ panel.body.appendChild(header);
467
+ if (node.groups.size > 0) {
468
+ const groups = this.doc.createElement("div");
469
+ groups.textContent = `groups: ${[...node.groups].join(", ")}`;
470
+ applyStyle(groups, {
471
+ opacity: "0.7",
472
+ marginBottom: "6px"
473
+ });
474
+ panel.body.appendChild(groups);
475
+ }
476
+ const schema = mergeStaticProps(ctor);
477
+ for (const key of Object.keys(schema)) this.renderPropRow(panel.body, node, key, schema[key]?.options);
478
+ }
479
+ renderPropRow(body, node, key, schemaOptions) {
480
+ const record = node;
481
+ const value = record[key];
482
+ const row = this.doc.createElement("div");
483
+ applyStyle(row, {
484
+ display: "flex",
485
+ gap: "6px",
486
+ alignItems: "center",
487
+ margin: "2px 0"
488
+ });
489
+ const label = this.doc.createElement("div");
490
+ label.textContent = key;
491
+ applyStyle(label, {
492
+ minWidth: "84px",
493
+ opacity: "0.75"
494
+ });
495
+ row.appendChild(label);
496
+ const numberInput = (current, commit) => {
497
+ const input = this.doc.createElement("input");
498
+ input.type = "number";
499
+ input.value = String(current);
500
+ applyStyle(input, inputStyle("70px"));
501
+ input.addEventListener("change", () => {
502
+ const next = Number(input.value);
503
+ if (Number.isFinite(next)) commit(next);
504
+ else input.value = String(node[key]);
505
+ });
506
+ return input;
507
+ };
508
+ if (typeof value === "number") row.appendChild(numberInput(value, (next) => {
509
+ record[key] = next;
510
+ }));
511
+ else if (typeof value === "boolean") {
512
+ const input = this.doc.createElement("input");
513
+ input.type = "checkbox";
514
+ input.checked = value;
515
+ this.trackEditing(input);
516
+ input.addEventListener("change", () => {
517
+ record[key] = input.checked;
518
+ });
519
+ row.appendChild(input);
520
+ } else if (typeof value === "string" && schemaOptions && schemaOptions.length > 0) {
521
+ const select = this.doc.createElement("select");
522
+ for (const option of schemaOptions.includes(value) ? schemaOptions : [value, ...schemaOptions]) {
523
+ const el = this.doc.createElement("option");
524
+ el.value = option;
525
+ el.textContent = option;
526
+ if (option === value) el.selected = true;
527
+ select.appendChild(el);
528
+ }
529
+ select.value = value;
530
+ this.trackEditing(select);
531
+ select.addEventListener("change", () => {
532
+ record[key] = select.value;
533
+ });
534
+ applyStyle(select, inputStyle("110px"));
535
+ row.appendChild(select);
536
+ } else if (typeof value === "string") {
537
+ const input = this.doc.createElement("input");
538
+ input.type = "text";
539
+ input.value = value;
540
+ this.trackEditing(input);
541
+ applyStyle(input, inputStyle("140px"));
542
+ input.addEventListener("change", () => {
543
+ record[key] = input.value;
544
+ });
545
+ row.appendChild(input);
546
+ } else if (Array.isArray(value) && value.every((v) => typeof v === "number")) for (let i = 0; i < value.length; i++) row.appendChild(numberInput(value[i], (next) => {
547
+ const arr = [...record[key]];
548
+ arr[i] = next;
549
+ record[key] = arr;
550
+ }));
551
+ else {
552
+ const readout = this.doc.createElement("div");
553
+ readout.textContent = JSON.stringify(jsonClone(value));
554
+ applyStyle(readout, {
555
+ opacity: "0.65",
556
+ overflow: "hidden",
557
+ textOverflow: "ellipsis"
558
+ });
559
+ row.appendChild(readout);
560
+ }
561
+ body.appendChild(row);
562
+ }
563
+ trackEditing(input) {
564
+ input.addEventListener("focus", () => {
565
+ this.editing += 1;
566
+ });
567
+ input.addEventListener("blur", () => {
568
+ this.editing = Math.max(0, this.editing - 1);
569
+ });
570
+ }
571
+ pushLog(row) {
572
+ this.logRows.push(row);
573
+ if (this.logRows.length > MAX_LOG_ROWS) this.logRows.splice(0, this.logRows.length - MAX_LOG_ROWS);
574
+ this.renderLogs();
575
+ }
576
+ renderLogs() {
577
+ const panel = this.panels.get("logs");
578
+ if (!panel) return;
579
+ clear(panel.body);
580
+ const bar = this.doc.createElement("div");
581
+ applyStyle(bar, {
582
+ display: "flex",
583
+ gap: "8px",
584
+ marginBottom: "4px",
585
+ flexWrap: "wrap"
586
+ });
587
+ for (const level of [
588
+ "debug",
589
+ "info",
590
+ "warn",
591
+ "error"
592
+ ]) {
593
+ const chip = this.doc.createElement("div");
594
+ chip.textContent = `${this.levelEnabled[level] ? "✓" : "·"}${level}`;
595
+ applyStyle(chip, {
596
+ cursor: "pointer",
597
+ opacity: this.levelEnabled[level] ? "1" : "0.45"
598
+ });
599
+ chip.addEventListener("click", () => this.setLevelEnabled(level, !this.levelEnabled[level]));
600
+ bar.appendChild(chip);
601
+ }
602
+ const consoleChip = this.doc.createElement("div");
603
+ consoleChip.textContent = `${this.consoleCapture ? "✓" : "·"}console`;
604
+ applyStyle(consoleChip, {
605
+ cursor: "pointer",
606
+ marginLeft: "auto"
607
+ });
608
+ consoleChip.addEventListener("click", () => this.setConsoleCapture(!this.consoleCapture));
609
+ bar.appendChild(consoleChip);
610
+ panel.body.appendChild(bar);
611
+ const colors = {
612
+ debug: "rgba(255,255,255,0.5)",
613
+ info: "rgba(255,255,255,0.85)",
614
+ warn: "#ffc861",
615
+ error: "#ff6b6b"
616
+ };
617
+ for (const row of this.logRows) {
618
+ if (!this.levelEnabled[row.level]) continue;
619
+ const line = this.doc.createElement("div");
620
+ line.textContent = `[${row.source === "console" ? "console" : row.level}] ${row.text}`;
621
+ applyStyle(line, {
622
+ color: colors[row.level],
623
+ whiteSpace: "pre-wrap"
624
+ });
625
+ panel.body.appendChild(line);
626
+ }
627
+ }
628
+ };
629
+ function clear(el) {
630
+ for (const child of [...el.children]) child.remove();
631
+ }
632
+ /** 18200 → '18.2k', 2_400_000 → '2.4M' — triangle counts stay chip-sized. */
633
+ function compactCount(n) {
634
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
635
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
636
+ return String(n);
637
+ }
638
+ function stringify(part) {
639
+ if (typeof part === "string") return part;
640
+ try {
641
+ return JSON.stringify(part);
642
+ } catch {
643
+ return String(part);
644
+ }
645
+ }
646
+ function inputStyle(width) {
647
+ return {
648
+ width,
649
+ background: "rgba(255,255,255,0.08)",
650
+ border: "1px solid rgba(255,255,255,0.2)",
651
+ borderRadius: "4px",
652
+ color: "inherit",
653
+ font: "inherit",
654
+ padding: "2px 4px"
655
+ };
656
+ }
657
+ //#endregion
658
+ export { DebugOverlay, attachDebugOverlay };
@@ -0,0 +1,22 @@
1
+ import { i as serializeNode, t as buildNodeJson } from "./loader-CGs_G-r0.js";
2
+ //#region src/core/scene/duplicate.ts
3
+ function stripUids(json) {
4
+ const { uid: _uid, ...rest } = json;
5
+ return {
6
+ ...rest,
7
+ children: json.children?.map(stripUids)
8
+ };
9
+ }
10
+ /**
11
+ * Deep-clone a node subtree (detached — add it wherever you like; sibling
12
+ * auto-rename applies on attach). Implemented as serialize → rebuild so a
13
+ * duplicate is exactly what would survive a save/load round-trip — EXCEPT
14
+ * uids: clones are new entities and get a fresh identity (no uid), so
15
+ * duplicating a uid'd template can never mint colliding ids. Names ARE
16
+ * kept — rename clones you need to look up individually.
17
+ */
18
+ function duplicateNode(node) {
19
+ return buildNodeJson(stripUids(serializeNode(node)));
20
+ }
21
+ //#endregion
22
+ export { duplicateNode as t };