@cimplify/cli 0.15.3 → 0.17.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 (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -2
  3. package/dist/add-C3DPTVH3.mjs +140 -0
  4. package/dist/{add-KRIIKAS4.mjs → add-JX2GXWXU.mjs} +4 -4
  5. package/dist/ai-HUZTVJIQ.mjs +17 -0
  6. package/dist/{assets-CWB72PZG.mjs → assets-MPESJSUV.mjs} +12 -7
  7. package/dist/{auth-step-up-Z6KOTTV7.mjs → auth-step-up-VJAFGSW6.mjs} +5 -5
  8. package/dist/build-E7RRQWVF.mjs +616 -0
  9. package/dist/{cancel-ZHVVILOU.mjs → cancel-UMKR274A.mjs} +4 -4
  10. package/dist/capture-TK4NIUYM.mjs +69 -0
  11. package/dist/chunk-25ZPFHLF.mjs +51 -0
  12. package/dist/{chunk-OLZKNUMK.mjs → chunk-3LJG22FS.mjs} +6 -6
  13. package/dist/chunk-454RQ6NR.mjs +10614 -0
  14. package/dist/{chunk-O2TO2J4I.mjs → chunk-6CQMRZ2W.mjs} +2 -2
  15. package/dist/chunk-6KKLF5KG.mjs +32 -0
  16. package/dist/chunk-74QZQSCL.mjs +84 -0
  17. package/dist/chunk-BGM7CH5G.mjs +81 -0
  18. package/dist/chunk-BTNVYAC4.mjs +5557 -0
  19. package/dist/{chunk-I2UP7YTG.mjs → chunk-ECGDRU56.mjs} +1 -1
  20. package/dist/{chunk-UTD34C2H.mjs → chunk-EOOOXVHR.mjs} +2 -4
  21. package/dist/chunk-EYEMPIMP.mjs +74 -0
  22. package/dist/{chunk-3RPR33MO.mjs → chunk-FV273ECI.mjs} +11 -7
  23. package/dist/{chunk-MLE4MVYC.mjs → chunk-GGPN4QYQ.mjs} +2 -2
  24. package/dist/chunk-HQPZQIWW.mjs +1456 -0
  25. package/dist/{chunk-3QUK5BPY.mjs → chunk-I7GEHLDD.mjs} +16 -1
  26. package/dist/chunk-ITDAICBY.mjs +363 -0
  27. package/dist/chunk-JKECUAAV.mjs +2169 -0
  28. package/dist/chunk-JNCEKW2Y.mjs +41 -0
  29. package/dist/chunk-JTYVGORV.mjs +101 -0
  30. package/dist/chunk-KQNZKV6E.mjs +535 -0
  31. package/dist/chunk-LJDIQ4PF.mjs +6463 -0
  32. package/dist/{chunk-2MKZKUVH.mjs → chunk-LLRUOIMU.mjs} +1 -2
  33. package/dist/chunk-MJMRIMR7.mjs +86 -0
  34. package/dist/chunk-N2WZJX5P.mjs +1989 -0
  35. package/dist/chunk-NEB372WD.mjs +1152 -0
  36. package/dist/chunk-OPX7KPOU.mjs +68 -0
  37. package/dist/chunk-PHUBMRAS.mjs +623 -0
  38. package/dist/chunk-PQBALZUH.mjs +1941 -0
  39. package/dist/chunk-PVW5D5AW.mjs +42 -0
  40. package/dist/{chunk-LZEG3FEY.mjs → chunk-QQWYQ2HS.mjs} +7 -7
  41. package/dist/chunk-QVR6V6QP.mjs +46 -0
  42. package/dist/chunk-SGT5IJVW.mjs +28 -0
  43. package/dist/chunk-SZXO6YSX.mjs +502 -0
  44. package/dist/chunk-THATLMEX.mjs +1227 -0
  45. package/dist/chunk-TYR4Q37L.mjs +391 -0
  46. package/dist/{chunk-DMLQBAGC.mjs → chunk-V3R2NYQR.mjs} +1 -1
  47. package/dist/chunk-VHK7WBO3.mjs +189 -0
  48. package/dist/{chunk-TH4XENKH.mjs → chunk-XASDB7YS.mjs} +1 -1
  49. package/dist/{chunk-NYXA6X56.mjs → chunk-XBQ4LO2N.mjs} +31 -11
  50. package/dist/chunk-XF3MGADK.mjs +2946 -0
  51. package/dist/{chunk-5KYR6WJB.mjs → chunk-XQAHREKO.mjs} +17 -1
  52. package/dist/chunk-XZG3DTXV.mjs +696 -0
  53. package/dist/chunk-Y4YGR2NY.mjs +52 -0
  54. package/dist/{chunk-LJEKKFC7.mjs → chunk-YS2JUNSQ.mjs} +19 -11
  55. package/dist/chunk-ZG7NJLXN.mjs +104 -0
  56. package/dist/create-3TGG7B3X.mjs +10 -0
  57. package/dist/credits-MEG7V5DI.mjs +33 -0
  58. package/dist/{deploy-STWZ3OSM.mjs → deploy-CNRORHJ7.mjs} +11 -11
  59. package/dist/dev-RXSBY7U4.mjs +8 -0
  60. package/dist/dev-YLYC6B7R.mjs +401 -0
  61. package/dist/dispatcher.mjs +131 -30
  62. package/dist/{doctor-3RW3OGZJ.mjs → doctor-XRTNG2IT.mjs} +13 -21
  63. package/dist/{domains-FTNK4IKY.mjs → domains-JIHFKDWR.mjs} +6 -6
  64. package/dist/engine-runtime-fetch-QVKJVC3W.mjs +10 -0
  65. package/dist/{env-TNCYUAMT.mjs → env-LJ3DCAA5.mjs} +7 -7
  66. package/dist/env-R72AHBT2.mjs +19 -0
  67. package/dist/explain-32RSXEXC.mjs +251 -0
  68. package/dist/inspect-PYLYN45K.mjs +86 -0
  69. package/dist/{inspect-X3M7WSTL.mjs → inspect-ZOLZHNSZ.mjs} +28 -13
  70. package/dist/introspect-27MMTACH.mjs +9 -0
  71. package/dist/kernel-IC4ZC54Z.mjs +662 -0
  72. package/dist/kernel-plugin.js +6862 -0
  73. package/dist/keys-WKOQ4KQE.mjs +71 -0
  74. package/dist/lines-I6ZB2IOU.mjs +56 -0
  75. package/dist/{link-76Z6V6CJ.mjs → link-TE2FUX74.mjs} +6 -6
  76. package/dist/{lint-DDZ7HUQN.mjs → lint-735IPMC6.mjs} +2 -2
  77. package/dist/{list-2MFOQKKQ.mjs → list-CHYY2P4T.mjs} +7 -11
  78. package/dist/lock-XVR3RRZ6.mjs +57 -0
  79. package/dist/{login-RLMDEWAL.mjs → login-LW7CHZDZ.mjs} +10 -8
  80. package/dist/{logout-Z57X6HPE.mjs → logout-6U6OP5DM.mjs} +2 -2
  81. package/dist/{logs-TNDM4RFE.mjs → logs-HKH4H6PT.mjs} +5 -5
  82. package/dist/logs-SCQC5HNO.mjs +44 -0
  83. package/dist/mcp-VRHQOEGG.mjs +1077 -0
  84. package/dist/{mock-EVER34IX.mjs → mock-5H3CVPHC.mjs} +2 -2
  85. package/dist/outdated-T7QW6F3Z.mjs +58 -0
  86. package/dist/port-24R7ZNP4.mjs +2408 -0
  87. package/dist/{projects-YL4T24VV.mjs → projects-HYITVEXS.mjs} +6 -6
  88. package/dist/publish-DZRR5A5X.mjs +20 -0
  89. package/dist/remix-4HVXRIEB.mjs +125 -0
  90. package/dist/repo-FG3CMZQS.mjs +9 -0
  91. package/dist/report-M7F4ZYP7.mjs +2 -0
  92. package/dist/rollback-H7Q4XYGX.mjs +43 -0
  93. package/dist/{rollback-47VQ4HJK.mjs → rollback-OM64O67F.mjs} +6 -6
  94. package/dist/scene-QTHT3ZPL.mjs +324 -0
  95. package/dist/{status-TWPOMWNA.mjs → status-5D4RR3MM.mjs} +5 -5
  96. package/dist/status-AKI6E3JK.mjs +18 -0
  97. package/dist/test-W6MURUGZ.mjs +233 -0
  98. package/dist/testers-EI32JB5F.mjs +49 -0
  99. package/dist/{unlink-H3BO3DOQ.mjs → unlink-BND47IWW.mjs} +2 -2
  100. package/dist/{update-FLY46XI3.mjs → update-XKKFREKH.mjs} +4 -4
  101. package/dist/{whoami-CQPPARRS.mjs → whoami-WHLGCVRC.mjs} +3 -3
  102. package/dist/workshop-F2T4IDOU.mjs +1703 -0
  103. package/package.json +11 -7
  104. package/templates/storefront-auto/app/products/[slug]/page.tsx +1 -1
  105. package/templates/storefront-auto/package.json +1 -1
  106. package/templates/storefront-bakery/package.json +1 -1
  107. package/templates/storefront-fashion/app/products/[slug]/page.tsx +1 -1
  108. package/templates/storefront-fashion/package.json +1 -1
  109. package/templates/storefront-grocery/package.json +1 -1
  110. package/templates/storefront-pharmacy/app/products/[slug]/page.tsx +1 -1
  111. package/templates/storefront-pharmacy/package.json +1 -1
  112. package/templates/storefront-restaurant/package.json +1 -1
  113. package/templates/storefront-retail/app/products/[slug]/page.tsx +1 -1
  114. package/templates/storefront-retail/package.json +1 -1
  115. package/templates/storefront-services/package.json +1 -1
  116. package/dist/chunk-ARIDYKLO.mjs +0 -6463
  117. package/dist/dev-7O65HY6C.mjs +0 -8
  118. package/dist/explain-DLRUUCNS.mjs +0 -251
  119. package/dist/introspect-RC265ACF.mjs +0 -9
  120. package/dist/repo-UUT3AZ3L.mjs +0 -9
@@ -0,0 +1,2408 @@
1
+ #!/usr/bin/env node
2
+ import { describeEvent, reportOutcome } from './chunk-OPX7KPOU.mjs';
3
+ import { askAuthoring, modelFromEnv } from './chunk-74QZQSCL.mjs';
4
+ import './chunk-BTNVYAC4.mjs';
5
+ import './chunk-VHK7WBO3.mjs';
6
+ import './chunk-454RQ6NR.mjs';
7
+ import './chunk-JKECUAAV.mjs';
8
+ import { titleFromName, materialize } from './chunk-ZG7NJLXN.mjs';
9
+ import './chunk-HQPZQIWW.mjs';
10
+ import './chunk-KQNZKV6E.mjs';
11
+ import './chunk-N2WZJX5P.mjs';
12
+ import './chunk-EYEMPIMP.mjs';
13
+ import './chunk-BGM7CH5G.mjs';
14
+ import './chunk-25ZPFHLF.mjs';
15
+ import './chunk-SGT5IJVW.mjs';
16
+ import { parseArgs, flagString, flagBool } from './chunk-XQAHREKO.mjs';
17
+ import './chunk-GGPN4QYQ.mjs';
18
+ import { CliError, CLI_ERROR_CODE, step, warn, success, info, dim, result } from './chunk-I7GEHLDD.mjs';
19
+ import { promises, existsSync } from 'fs';
20
+ import path from 'path';
21
+ import { THREE_LIGHTS, threejsPortRequest, THREE_CONTROLS, THREE_GEOMETRIES, THREE_MATERIALS, THREE_TEXTURE_SLOTS, THREE_SIDES, THREE_MATH_HELPERS, THREE_VECTOR_METHODS, countSourceLines } from '@cimplify/sdk/interactives/authoring';
22
+ import ts from 'typescript';
23
+
24
+ var THREE_MODULE = "three";
25
+ var THREE_ADDON_PREFIXES = ["three/addons/", "three/examples/jsm/"];
26
+ var DROP = "\0DROP\0";
27
+ var INDENT = " ";
28
+ var INFERENCE_PASSES = 4;
29
+ var CAST_SHADOW_FLAG = 1;
30
+ var RECEIVE_SHADOW_FLAG = 2;
31
+ var DEFAULT_FOV = "50";
32
+ var DEFAULT_NEAR = "0.1";
33
+ var DEFAULT_FAR = "2000";
34
+ var HEX_RADIX = 16;
35
+ var SHORT_HEX_LENGTH = 3;
36
+ var LONG_HEX_LENGTH = 6;
37
+ var COLOR_CHANNEL_MAX = 255;
38
+ var ROUGHNESS_DIGITS = 3;
39
+ var PHONG_SHININESS_DEFAULT = 30;
40
+ var SHININESS_OFFSET = 2;
41
+ var AXIS_NAMES = { x: "AXIS_X", y: "AXIS_Y", z: "AXIS_Z" };
42
+ var TRANSFORM_ATTRIBUTES = { position: "Position", scale: "Scale", rotation: "Rotation" };
43
+ var KEY_EVENTS = { keydown: "keydown", keypress: "keydown", keyup: "keyup" };
44
+ var POINTER_EVENTS = {
45
+ pointerdown: "pointerdown",
46
+ mousedown: "pointerdown",
47
+ touchstart: "pointerdown",
48
+ pointerup: "pointerup",
49
+ mouseup: "pointerup",
50
+ touchend: "pointerup",
51
+ click: "pointerup"
52
+ };
53
+ var DROPPED_EVENTS = /* @__PURE__ */ new Set(["resize", "orientationchange"]);
54
+ var LISTENER_TARGETS = /* @__PURE__ */ new Set(["window", "document"]);
55
+ var HUD_PROPERTIES = /* @__PURE__ */ new Set(["textContent", "innerText", "innerHTML"]);
56
+ var COLOR_PROPERTIES = /* @__PURE__ */ new Set(["color", "emissive", "sheenColor", "specularColor", "attenuationColor"]);
57
+ var ENTITY_CLASSES = /* @__PURE__ */ new Set(["Mesh", "Group", "Object3D", "PerspectiveCamera", "OrthographicCamera", ...Object.keys(THREE_LIGHTS)]);
58
+ var DROPPED_RENDERER_CLASSES = /* @__PURE__ */ new Set(["WebGLRenderer", "WebGPURenderer"]);
59
+ var CAMERA_DROPPED_MEMBERS = /* @__PURE__ */ new Set(["aspect", "updateProjectionMatrix"]);
60
+ var HANDLED_ENTITY_MEMBERS = /* @__PURE__ */ new Set(["material", "visible", "castShadow", "receiveShadow", "shadow", "target", "aspect", "updateProjectionMatrix", "updateMatrix", "updateMatrixWorld"]);
61
+ var MATRIX_UPDATES = /* @__PURE__ */ new Set(["updateMatrix", "updateMatrixWorld"]);
62
+ var ACTION_PASSTHROUGH = /* @__PURE__ */ new Set(["play", "stop", "fadeIn", "fadeOut", "reset", "setEffectiveTimeScale", "setEffectiveWeight", "isRunning"]);
63
+ var TONE_MAPPINGS = {
64
+ NoToneMapping: "none",
65
+ LinearToneMapping: "linear",
66
+ ReinhardToneMapping: "reinhard",
67
+ CineonToneMapping: "cineon",
68
+ ACESFilmicToneMapping: "aces",
69
+ AgXToneMapping: "agx",
70
+ NeutralToneMapping: "neutral"
71
+ };
72
+ var LOOP_MODES = { LoopOnce: "false", LoopRepeat: "true", LoopPingPong: "true" };
73
+ var NAMED_COLORS = {
74
+ black: 0,
75
+ white: 16777215,
76
+ red: 16711680,
77
+ green: 32768,
78
+ lime: 65280,
79
+ blue: 255,
80
+ yellow: 16776960,
81
+ cyan: 65535,
82
+ aqua: 65535,
83
+ magenta: 16711935,
84
+ fuchsia: 16711935,
85
+ orange: 16753920,
86
+ purple: 8388736,
87
+ pink: 16761035,
88
+ brown: 10824234,
89
+ gray: 8421504,
90
+ grey: 8421504,
91
+ silver: 12632256,
92
+ gold: 16766720,
93
+ navy: 128,
94
+ teal: 32896,
95
+ skyblue: 8900331,
96
+ maroon: 8388608,
97
+ olive: 8421376
98
+ };
99
+ var COMMON_MATERIAL_PROPERTIES = ["transparent", "opacity", "side", "depthTest", "depthWrite", "wireframe", "alphaTest", "vertexColors"];
100
+ var MATERIAL_PROPERTIES_BY_KIND = {
101
+ standard: /* @__PURE__ */ new Set([...COMMON_MATERIAL_PROPERTIES, "color", "roughness", "metalness", "emissive", "emissiveIntensity", "map", "normalMap", "roughnessMap", "metalnessMap", "emissiveMap", "aoMap", "flatShading"]),
102
+ physical: /* @__PURE__ */ new Set([
103
+ ...COMMON_MATERIAL_PROPERTIES,
104
+ "color",
105
+ "roughness",
106
+ "metalness",
107
+ "emissive",
108
+ "emissiveIntensity",
109
+ "map",
110
+ "normalMap",
111
+ "roughnessMap",
112
+ "metalnessMap",
113
+ "emissiveMap",
114
+ "aoMap",
115
+ "flatShading",
116
+ "clearcoat",
117
+ "clearcoatRoughness",
118
+ "transmission",
119
+ "ior",
120
+ "thickness",
121
+ "sheen",
122
+ "sheenColor",
123
+ "sheenRoughness",
124
+ "iridescence",
125
+ "anisotropy",
126
+ "attenuationColor",
127
+ "attenuationDistance",
128
+ "specularColor",
129
+ "specularIntensity"
130
+ ]),
131
+ unlit: /* @__PURE__ */ new Set([...COMMON_MATERIAL_PROPERTIES, "color", "map"]),
132
+ toon: /* @__PURE__ */ new Set([...COMMON_MATERIAL_PROPERTIES, "color", "map", "gradientMap", "normalMap", "emissive", "emissiveIntensity", "emissiveMap", "aoMap"]),
133
+ matcap: /* @__PURE__ */ new Set([...COMMON_MATERIAL_PROPERTIES, "color", "map", "matcap", "normalMap", "flatShading"])
134
+ };
135
+ var KEY_CODE = /^(Key[A-Z]|Digit[0-9]|Arrow(Up|Down|Left|Right)|Space|Enter|Escape|Tab|Backspace|Shift(Left|Right)|Control(Left|Right)|Alt(Left|Right)|Numpad[0-9]|F[1-9]|F1[0-2])$/;
136
+ var KEY_ALIASES = {
137
+ " ": "Space",
138
+ Shift: "ShiftLeft",
139
+ Control: "ControlLeft",
140
+ Alt: "AltLeft",
141
+ Esc: "Escape",
142
+ Up: "ArrowUp",
143
+ Down: "ArrowDown",
144
+ Left: "ArrowLeft",
145
+ Right: "ArrowRight"
146
+ };
147
+ var SINGLE_LETTER = /^[a-zA-Z]$/;
148
+ var SINGLE_DIGIT = /^[0-9]$/;
149
+ var HEX_COLOR = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
150
+ var SIMPLE_OPERAND = /^[A-Za-z_$][\w$]*$|^-?\d+(\.\d+)?$/;
151
+ var BLANK_RUNS = /\n{3,}/g;
152
+ var DROPPED_KINDS = /* @__PURE__ */ new Set(["scene", "renderer", "clock", "gltfLoader", "textureLoader"]);
153
+ var PRIMITIVE_KINDS = /* @__PURE__ */ new Set(["number", "string", "boolean"]);
154
+ var SCENE_LOCALS = /* @__PURE__ */ new Set(["game", "port"]);
155
+ var RESERVED_MEMBERS = /* @__PURE__ */ new Set(["name", "game", "port", "assets", "enter", "exit", "fixedUpdate", "update", "systems", "constructor"]);
156
+ var MEMBER_SUFFIX = "State";
157
+ var SIMPLE_REFERENCE = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/;
158
+ function codeOfKey(value) {
159
+ if (KEY_CODE.test(value)) return value;
160
+ const alias = KEY_ALIASES[value];
161
+ if (alias !== void 0) return alias;
162
+ if (SINGLE_LETTER.test(value)) return `Key${value.toUpperCase()}`;
163
+ if (SINGLE_DIGIT.test(value)) return `Digit${value}`;
164
+ return null;
165
+ }
166
+ function sceneClassName(sceneName) {
167
+ const words = sceneName.split(/[^A-Za-z0-9]+/).filter((w) => w.length > 0);
168
+ const base = words.map((w) => w[0].toUpperCase() + w.slice(1)).join("");
169
+ return `${/^[A-Za-z]/.test(base) ? base : `Ported${base}`}Scene`;
170
+ }
171
+ function colorLiteral(value) {
172
+ const named = NAMED_COLORS[value.toLowerCase()];
173
+ if (named !== void 0) return named;
174
+ const hex = HEX_COLOR.exec(value);
175
+ if (hex === null) return null;
176
+ const digits = hex[1];
177
+ const full = digits.length === SHORT_HEX_LENGTH ? digits.split("").map((d) => d + d).join("") : digits;
178
+ return full.length === LONG_HEX_LENGTH ? Number.parseInt(full, HEX_RADIX) : null;
179
+ }
180
+ function hexText(value) {
181
+ return `0x${value.toString(HEX_RADIX).padStart(LONG_HEX_LENGTH, "0")}`;
182
+ }
183
+ function elementOf(kind) {
184
+ if (kind.endsWith("[]") || kind.endsWith("{}")) return kind.slice(0, -2);
185
+ return "unknown";
186
+ }
187
+ function isArrayKind(kind) {
188
+ return kind.endsWith("[]");
189
+ }
190
+ function isRecordKind(kind) {
191
+ return kind.endsWith("{}");
192
+ }
193
+ function memoryHost(fileName, sourceFile) {
194
+ return {
195
+ getSourceFile: (name) => name === fileName ? sourceFile : void 0,
196
+ getDefaultLibFileName: () => "lib.d.ts",
197
+ writeFile: () => void 0,
198
+ getCurrentDirectory: () => "/",
199
+ getCanonicalFileName: (name) => name,
200
+ useCaseSensitiveFileNames: () => true,
201
+ getNewLine: () => "\n",
202
+ fileExists: (name) => name === fileName,
203
+ readFile: () => void 0
204
+ };
205
+ }
206
+ function scriptKind(path2) {
207
+ if (path2.endsWith(".ts")) return ts.ScriptKind.TS;
208
+ if (path2.endsWith(".tsx")) return ts.ScriptKind.TSX;
209
+ if (path2.endsWith(".jsx")) return ts.ScriptKind.JSX;
210
+ return ts.ScriptKind.JS;
211
+ }
212
+ function importsThree(source) {
213
+ return /from\s+["']three(["']|\/)/.test(source);
214
+ }
215
+ var ThreeModuleConverter = class {
216
+ constructor(path2, source, options) {
217
+ this.path = path2;
218
+ this.options = options;
219
+ this.text = source;
220
+ this.sf = ts.createSourceFile(path2, source, ts.ScriptTarget.Latest, true, scriptKind(path2));
221
+ const program = ts.createProgram({
222
+ rootNames: [path2],
223
+ options: { allowJs: true, noLib: true, noResolve: true, types: [], target: ts.ScriptTarget.Latest },
224
+ host: memoryHost(path2, this.sf)
225
+ });
226
+ this.checker = program.getTypeChecker();
227
+ }
228
+ path;
229
+ options;
230
+ sf;
231
+ checker;
232
+ text;
233
+ bindings = /* @__PURE__ */ new Map();
234
+ functions = /* @__PURE__ */ new Map();
235
+ threeNamespaces = /* @__PURE__ */ new Set();
236
+ threeNames = /* @__PURE__ */ new Map();
237
+ residueItems = [];
238
+ residueKeys = /* @__PURE__ */ new Set();
239
+ areaCounts = {};
240
+ gameImports = /* @__PURE__ */ new Set();
241
+ gameTypeImports = /* @__PURE__ */ new Set();
242
+ kitImports = /* @__PURE__ */ new Set(["ThreePort"]);
243
+ kitTypeImports = /* @__PURE__ */ new Set(["PortAssetReader"]);
244
+ assetPaths = /* @__PURE__ */ new Set();
245
+ features = /* @__PURE__ */ new Set();
246
+ keyCodes = /* @__PURE__ */ new Set();
247
+ keyRecords = /* @__PURE__ */ new Set();
248
+ hudSlots = /* @__PURE__ */ new Map();
249
+ foldedStatements = /* @__PURE__ */ new Set();
250
+ loopReachable = /* @__PURE__ */ new Set();
251
+ fields = /* @__PURE__ */ new Set();
252
+ methods = /* @__PURE__ */ new Set();
253
+ moduleConstants = /* @__PURE__ */ new Set();
254
+ memberNames = /* @__PURE__ */ new Map();
255
+ env = { background: null, fog: null, shadows: null, toneMapping: null, exposure: null };
256
+ loopFunction = null;
257
+ loopHasTime = false;
258
+ keyListeners = 0;
259
+ touchHandled = false;
260
+ setupDepth = 0;
261
+ convert() {
262
+ this.collectImports();
263
+ this.collectBindings();
264
+ for (let pass = 0; pass < INFERENCE_PASSES; pass++) this.inferPass(this.sf);
265
+ this.findLoop();
266
+ this.collectKeys();
267
+ this.collectHudSlots();
268
+ const scene = this.emitScene();
269
+ this.adviseTouch();
270
+ let converted = 0;
271
+ for (const count of Object.values(this.areaCounts)) converted += count ?? 0;
272
+ return {
273
+ scene,
274
+ residue: [...this.residueItems].sort((a, b) => a.line - b.line || a.column - b.column),
275
+ converted,
276
+ convertedByArea: { ...this.areaCounts },
277
+ assets: [...this.assetPaths].sort(),
278
+ engineFeatures: [...this.features].sort(),
279
+ loopFound: this.loopFunction !== null
280
+ };
281
+ }
282
+ count(area) {
283
+ this.areaCounts[area] = (this.areaCounts[area] ?? 0) + 1;
284
+ }
285
+ residue(node, kind, area, construct, hint) {
286
+ const start = node.getStart(this.sf);
287
+ const key = `${start}:${construct}`;
288
+ if (this.residueKeys.has(key)) return;
289
+ this.residueKeys.add(key);
290
+ const at = this.sf.getLineAndCharacterOfPosition(start);
291
+ this.residueItems.push({
292
+ id: `R${this.residueItems.length + 1}`,
293
+ kind,
294
+ area,
295
+ construct,
296
+ file: this.path,
297
+ line: at.line + 1,
298
+ column: at.character + 1,
299
+ source: node.getText(this.sf).slice(0, 240),
300
+ hint
301
+ });
302
+ }
303
+ symbolOf(node) {
304
+ return this.checker.getSymbolAtLocation(node) ?? null;
305
+ }
306
+ collectImports() {
307
+ for (const statement of this.sf.statements) {
308
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) continue;
309
+ const from = statement.moduleSpecifier.text;
310
+ const isThree = from === THREE_MODULE || THREE_ADDON_PREFIXES.some((p) => from.startsWith(p));
311
+ if (!isThree) continue;
312
+ const clause = statement.importClause;
313
+ if (clause === void 0) continue;
314
+ const bindings = clause.namedBindings;
315
+ if (bindings !== void 0 && ts.isNamespaceImport(bindings)) {
316
+ const symbol = this.symbolOf(bindings.name);
317
+ if (symbol !== null) this.threeNamespaces.add(symbol);
318
+ }
319
+ if (bindings !== void 0 && ts.isNamedImports(bindings)) {
320
+ for (const element of bindings.elements) {
321
+ const symbol = this.symbolOf(element.name);
322
+ if (symbol !== null) this.threeNames.set(symbol, (element.propertyName ?? element.name).text);
323
+ }
324
+ }
325
+ if (clause.name !== void 0) {
326
+ const symbol = this.symbolOf(clause.name);
327
+ if (symbol !== null) this.threeNamespaces.add(symbol);
328
+ }
329
+ }
330
+ }
331
+ threeName(expr) {
332
+ if (ts.isIdentifier(expr)) {
333
+ const symbol = this.symbolOf(expr);
334
+ return symbol === null ? null : this.threeNames.get(symbol) ?? null;
335
+ }
336
+ if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.expression)) {
337
+ const symbol = this.symbolOf(expr.expression);
338
+ if (symbol !== null && this.threeNamespaces.has(symbol)) return expr.name.text;
339
+ }
340
+ return null;
341
+ }
342
+ threeMember(expr) {
343
+ if (!ts.isPropertyAccessExpression(expr)) return null;
344
+ const owner = this.threeName(expr.expression);
345
+ return owner === null ? null : [owner, expr.name.text];
346
+ }
347
+ isThreeRoot(expr) {
348
+ if (!ts.isIdentifier(expr)) return false;
349
+ const symbol = this.symbolOf(expr);
350
+ return symbol !== null && (this.threeNamespaces.has(symbol) || this.threeNames.has(symbol));
351
+ }
352
+ binding(symbol, name) {
353
+ let found = this.bindings.get(symbol);
354
+ if (found === void 0) {
355
+ found = { symbol, name, kind: "unknown", shadows: 0, shadowMapSize: null, material: null, light: null, target: null, controlsClass: null, camera: null };
356
+ this.bindings.set(symbol, found);
357
+ }
358
+ return found;
359
+ }
360
+ bindingOf(expr) {
361
+ if (!ts.isIdentifier(expr)) return null;
362
+ const symbol = this.symbolOf(expr);
363
+ return symbol === null ? null : this.bindings.get(symbol) ?? null;
364
+ }
365
+ collectBindings() {
366
+ const visit = (node) => {
367
+ if ((ts.isVariableDeclaration(node) || ts.isParameter(node)) && ts.isIdentifier(node.name)) {
368
+ const symbol = this.symbolOf(node.name);
369
+ if (symbol !== null) this.binding(symbol, node.name.text);
370
+ }
371
+ if (ts.isFunctionDeclaration(node) && node.name !== void 0) {
372
+ const symbol = this.symbolOf(node.name);
373
+ if (symbol !== null) this.functions.set(symbol, { returnKind: "unknown" });
374
+ }
375
+ ts.forEachChild(node, visit);
376
+ };
377
+ visit(this.sf);
378
+ }
379
+ setKind(symbol, kind) {
380
+ if (symbol === null || kind === "unknown") return;
381
+ const binding = this.bindings.get(symbol);
382
+ if (binding === void 0) return;
383
+ if (binding.kind === "unknown" || binding.kind === "unknown[]" || binding.kind === "unknown{}") binding.kind = kind;
384
+ }
385
+ functionSymbolOf(callee) {
386
+ if (!ts.isIdentifier(callee)) return null;
387
+ const symbol = this.symbolOf(callee);
388
+ return symbol !== null && this.functions.has(symbol) ? symbol : null;
389
+ }
390
+ declarationOf(symbol) {
391
+ return symbol.valueDeclaration ?? symbol.declarations?.[0] ?? null;
392
+ }
393
+ paramSymbol(fn, index) {
394
+ if (!ts.isFunctionLike(fn)) return null;
395
+ const param = fn.parameters[index];
396
+ if (param === void 0 || !ts.isIdentifier(param.name)) return null;
397
+ return this.symbolOf(param.name);
398
+ }
399
+ callbackOf(arg) {
400
+ if (arg === void 0) return null;
401
+ if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) return arg;
402
+ if (ts.isIdentifier(arg)) {
403
+ const symbol = this.symbolOf(arg);
404
+ const decl = symbol === null ? null : this.declarationOf(symbol);
405
+ if (decl !== null && ts.isFunctionDeclaration(decl)) return decl;
406
+ }
407
+ return null;
408
+ }
409
+ inferPass(root) {
410
+ const visit = (node) => {
411
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer !== void 0) {
412
+ this.setKind(this.symbolOf(node.name), this.kindOf(node.initializer));
413
+ if (ts.isNewExpression(node.initializer)) this.recordConstruction(this.symbolOf(node.name), node.initializer);
414
+ }
415
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
416
+ if (ts.isIdentifier(node.left)) {
417
+ this.setKind(this.symbolOf(node.left), this.kindOf(node.right));
418
+ if (ts.isNewExpression(node.right)) this.recordConstruction(this.symbolOf(node.left), node.right);
419
+ }
420
+ if (ts.isElementAccessExpression(node.left) && ts.isIdentifier(node.left.expression)) {
421
+ const value = this.kindOf(node.right);
422
+ const symbol = this.symbolOf(node.left.expression);
423
+ if (value !== "unknown" && symbol !== null) {
424
+ const binding = this.bindings.get(symbol);
425
+ if (binding !== void 0 && binding.kind === "unknown{}") binding.kind = `${value}{}`;
426
+ if (binding !== void 0 && binding.kind === "unknown[]") binding.kind = `${value}[]`;
427
+ }
428
+ }
429
+ }
430
+ if (ts.isForOfStatement(node) && ts.isVariableDeclarationList(node.initializer)) {
431
+ const decl = node.initializer.declarations[0];
432
+ if (decl !== void 0 && ts.isIdentifier(decl.name)) this.setKind(this.symbolOf(decl.name), elementOf(this.kindOf(node.expression)));
433
+ }
434
+ if (ts.isCallExpression(node)) this.inferCall(node);
435
+ if (ts.isReturnStatement(node) && node.expression !== void 0) {
436
+ const fn = this.enclosingFunctionDeclaration(node);
437
+ if (fn !== null && fn.name !== void 0) {
438
+ const symbol = this.symbolOf(fn.name);
439
+ const info2 = symbol === null ? void 0 : this.functions.get(symbol);
440
+ const kind = this.kindOf(node.expression);
441
+ if (info2 !== void 0 && info2.returnKind === "unknown") info2.returnKind = kind;
442
+ }
443
+ }
444
+ ts.forEachChild(node, visit);
445
+ };
446
+ visit(root);
447
+ }
448
+ enclosingFunctionDeclaration(node) {
449
+ let current = node.parent;
450
+ while (current !== void 0) {
451
+ if (ts.isArrowFunction(current) || ts.isFunctionExpression(current)) return null;
452
+ if (ts.isFunctionDeclaration(current)) return current;
453
+ current = current.parent;
454
+ }
455
+ return null;
456
+ }
457
+ recordConstruction(symbol, expr) {
458
+ if (symbol === null) return;
459
+ const binding = this.bindings.get(symbol);
460
+ if (binding === void 0) return;
461
+ const name = this.threeName(expr.expression);
462
+ if (name === null) return;
463
+ const args = expr.arguments ?? [];
464
+ if (name === "Mesh" && args[1] !== void 0 && ts.isIdentifier(args[1])) binding.material = this.symbolOf(args[1]);
465
+ const light = THREE_LIGHTS[name];
466
+ if (light !== void 0) binding.light = light.light;
467
+ const controls = THREE_CONTROLS[name];
468
+ if (controls !== void 0) {
469
+ binding.controlsClass = controls;
470
+ binding.camera = args[0] ?? null;
471
+ }
472
+ }
473
+ inferCall(node) {
474
+ const fn = this.functionSymbolOf(node.expression);
475
+ if (fn !== null) {
476
+ const decl = this.declarationOf(fn);
477
+ if (decl !== null) node.arguments.forEach((arg, i) => this.setKind(this.paramSymbol(decl, i), this.kindOf(arg)));
478
+ }
479
+ if (!ts.isPropertyAccessExpression(node.expression)) return;
480
+ const method = node.expression.name.text;
481
+ const receiver = node.expression.expression;
482
+ const receiverKind = this.kindOf(receiver);
483
+ if (method === "push" && ts.isIdentifier(receiver)) {
484
+ const symbol = this.symbolOf(receiver);
485
+ const binding = symbol === null ? void 0 : this.bindings.get(symbol);
486
+ const kind = node.arguments[0] === void 0 ? "unknown" : this.kindOf(node.arguments[0]);
487
+ if (binding !== void 0 && (binding.kind === "unknown" || binding.kind === "unknown[]") && kind !== "unknown") binding.kind = `${kind}[]`;
488
+ }
489
+ if ((method === "forEach" || method === "map" || method === "filter" || method === "some" || method === "every") && isArrayKind(receiverKind)) {
490
+ this.setKind(this.paramSymbol(this.callbackOf(node.arguments[0]) ?? node, 0), elementOf(receiverKind));
491
+ }
492
+ if (method === "load" && receiverKind === "gltfLoader") this.setKind(this.paramSymbol(this.callbackOf(node.arguments[1]) ?? node, 0), "gltf");
493
+ if (method === "then" && ts.isCallExpression(receiver) && ts.isPropertyAccessExpression(receiver.expression) && receiver.expression.name.text === "loadAsync") {
494
+ if (this.kindOf(receiver.expression.expression) === "gltfLoader") this.setKind(this.paramSymbol(this.callbackOf(node.arguments[0]) ?? node, 0), "gltf");
495
+ }
496
+ if (method === "addEventListener" && node.arguments[0] !== void 0 && ts.isStringLiteralLike(node.arguments[0])) {
497
+ const type = node.arguments[0].text;
498
+ const handler = this.callbackOf(node.arguments[1]);
499
+ if (handler !== null && KEY_EVENTS[type] !== void 0) this.setKind(this.paramSymbol(handler, 0), "keyEvent");
500
+ if (handler !== null && POINTER_EVENTS[type] !== void 0) this.setKind(this.paramSymbol(handler, 0), "pointerEvent");
501
+ }
502
+ }
503
+ kindOf(expr) {
504
+ if (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr) || ts.isAsExpression(expr)) return this.kindOf(expr.expression);
505
+ if (ts.isNumericLiteral(expr)) return "number";
506
+ if (ts.isStringLiteralLike(expr) || ts.isTemplateExpression(expr)) return "string";
507
+ if (expr.kind === ts.SyntaxKind.TrueKeyword || expr.kind === ts.SyntaxKind.FalseKeyword) return "boolean";
508
+ if (ts.isArrayLiteralExpression(expr)) {
509
+ const first = expr.elements[0];
510
+ return first === void 0 ? "unknown[]" : `${this.kindOf(first)}[]`;
511
+ }
512
+ if (ts.isObjectLiteralExpression(expr)) return expr.properties.length === 0 ? "unknown{}" : "unknown";
513
+ if (ts.isPrefixUnaryExpression(expr)) return expr.operator === ts.SyntaxKind.ExclamationToken ? "boolean" : "number";
514
+ if (ts.isPostfixUnaryExpression(expr)) return "number";
515
+ if (ts.isConditionalExpression(expr)) {
516
+ const whenTrue = this.kindOf(expr.whenTrue);
517
+ return whenTrue !== "unknown" ? whenTrue : this.kindOf(expr.whenFalse);
518
+ }
519
+ if (ts.isBinaryExpression(expr)) return this.binaryKind(expr);
520
+ if (ts.isIdentifier(expr)) {
521
+ const binding = this.bindingOf(expr);
522
+ if (binding !== null) return binding.kind;
523
+ return this.isThreeRoot(expr) ? "three" : "unknown";
524
+ }
525
+ if (ts.isNewExpression(expr)) return this.newKind(expr);
526
+ if (ts.isElementAccessExpression(expr)) {
527
+ const owner = this.kindOf(expr.expression);
528
+ return isArrayKind(owner) || isRecordKind(owner) ? elementOf(owner) : "unknown";
529
+ }
530
+ if (ts.isPropertyAccessExpression(expr)) return this.propertyKind(expr);
531
+ if (ts.isCallExpression(expr)) return this.callKind(expr);
532
+ return "unknown";
533
+ }
534
+ binaryKind(expr) {
535
+ const op = expr.operatorToken.kind;
536
+ if (op === ts.SyntaxKind.EqualsEqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsEqualsToken || op === ts.SyntaxKind.EqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsToken || op === ts.SyntaxKind.LessThanToken || op === ts.SyntaxKind.GreaterThanToken || op === ts.SyntaxKind.LessThanEqualsToken || op === ts.SyntaxKind.GreaterThanEqualsToken) {
537
+ return "boolean";
538
+ }
539
+ if (op === ts.SyntaxKind.AmpersandAmpersandToken || op === ts.SyntaxKind.BarBarToken || op === ts.SyntaxKind.QuestionQuestionToken) {
540
+ const right = this.kindOf(expr.right);
541
+ return right !== "unknown" ? right : this.kindOf(expr.left);
542
+ }
543
+ if (op === ts.SyntaxKind.EqualsToken) return this.kindOf(expr.right);
544
+ if (op === ts.SyntaxKind.PlusToken) {
545
+ const left = this.kindOf(expr.left);
546
+ const right = this.kindOf(expr.right);
547
+ return left === "string" || right === "string" ? "string" : "number";
548
+ }
549
+ return "number";
550
+ }
551
+ newKind(expr) {
552
+ const name = this.threeName(expr.expression);
553
+ if (name === null) {
554
+ if (ts.isIdentifier(expr.expression) && expr.expression.text === "Array") return "unknown[]";
555
+ return "unknown";
556
+ }
557
+ if (ENTITY_CLASSES.has(name)) return "entity";
558
+ if (THREE_GEOMETRIES[name] !== void 0) return "geometry";
559
+ if (THREE_MATERIALS[name] !== void 0) return "material";
560
+ if (THREE_CONTROLS[name] !== void 0) return "controls";
561
+ switch (name) {
562
+ case "Scene":
563
+ return "scene";
564
+ case "Clock":
565
+ return "clock";
566
+ case "GLTFLoader":
567
+ return "gltfLoader";
568
+ case "TextureLoader":
569
+ return "textureLoader";
570
+ case "AnimationMixer":
571
+ return "mixer";
572
+ case "Vector3":
573
+ return "vector";
574
+ case "Quaternion":
575
+ return "quat";
576
+ case "Color":
577
+ return "number";
578
+ default:
579
+ return DROPPED_RENDERER_CLASSES.has(name) ? "renderer" : "unknown";
580
+ }
581
+ }
582
+ propertyKind(expr) {
583
+ const name = expr.name.text;
584
+ const owner = expr.expression;
585
+ if (ts.isIdentifier(owner) && owner.text === "Math") return "number";
586
+ const ownerKind = this.kindOf(owner);
587
+ if (ownerKind === "gltf" && name === "scene") return "entity";
588
+ if (ownerKind === "gltf" && name === "animations") return "string[]";
589
+ if (ownerKind === "renderer" && name === "domElement") return "domElement";
590
+ if (ownerKind === "keyEvent" && (name === "code" || name === "key")) return "string";
591
+ if (ownerKind === "pointerEvent") return "number";
592
+ if (ownerKind === "vector" && (name === "x" || name === "y" || name === "z")) return "number";
593
+ if (ownerKind === "clock" && name === "elapsedTime") return "number";
594
+ if (ownerKind === "string[]" && name === "length") return "number";
595
+ if (isArrayKind(ownerKind) && name === "length") return "number";
596
+ if (isRecordKind(ownerKind)) return elementOf(ownerKind);
597
+ if (ts.isPropertyAccessExpression(owner) && this.kindOf(owner.expression) === "entity" && TRANSFORM_ATTRIBUTES[owner.name.text] !== void 0 && AXIS_NAMES[name] !== void 0) return "number";
598
+ if (ownerKind === "entity" && name === "position") return "vector";
599
+ return "unknown";
600
+ }
601
+ callKind(expr) {
602
+ const fn = this.functionSymbolOf(expr.expression);
603
+ if (fn !== null) return this.functions.get(fn)?.returnKind ?? "unknown";
604
+ if (ts.isIdentifier(expr.expression) && (expr.expression.text === "Number" || expr.expression.text === "parseFloat" || expr.expression.text === "parseInt")) return "number";
605
+ if (ts.isIdentifier(expr.expression) && expr.expression.text === "String") return "string";
606
+ if (!ts.isPropertyAccessExpression(expr.expression)) return "unknown";
607
+ const method = expr.expression.name.text;
608
+ const receiver = expr.expression.expression;
609
+ if (ts.isIdentifier(receiver) && receiver.text === "Math") return "number";
610
+ if (ts.isIdentifier(receiver) && receiver.text === "document" && (method === "getElementById" || method === "querySelector")) return "hud";
611
+ const member = this.threeMember(expr.expression);
612
+ if (member !== null && member[0] === "MathUtils") return "number";
613
+ const receiverKind = this.kindOf(receiver);
614
+ if (receiverKind === "mixer" && method === "clipAction") return "action";
615
+ if (receiverKind === "textureLoader" && method === "load") return "texture";
616
+ if (ts.isNewExpression(receiver) && this.threeName(receiver.expression) === "TextureLoader" && method === "load") return "texture";
617
+ if (receiverKind === "clock") return "number";
618
+ if (receiverKind === "controls" && method === "getObject") return "entity";
619
+ if (receiverKind === "vector" && (method === "length" || method === "lengthSq" || method === "dot" || method === "distanceTo" || method === "distanceToSquared" || method === "angleTo")) return "number";
620
+ if (receiverKind === "vector") return "vector";
621
+ if (ts.isPropertyAccessExpression(receiver) && this.kindOf(receiver.expression) === "entity" && (method === "distanceTo" || method === "distanceToSquared")) return "number";
622
+ if (receiverKind === "string" || receiverKind === "number") return receiverKind;
623
+ return "unknown";
624
+ }
625
+ typeOf(kind, binding) {
626
+ if (isArrayKind(kind)) {
627
+ const element = this.typeOf(elementOf(kind), null);
628
+ return element === null ? null : `${element}[]`;
629
+ }
630
+ if (isRecordKind(kind)) {
631
+ const element = this.typeOf(elementOf(kind), null);
632
+ return element === null ? null : `Record<string, ${element}>`;
633
+ }
634
+ switch (kind) {
635
+ case "number":
636
+ case "string":
637
+ case "boolean":
638
+ return kind;
639
+ case "entity":
640
+ case "mixer":
641
+ this.gameTypeImports.add("Entity");
642
+ return "Entity";
643
+ case "geometry":
644
+ this.gameTypeImports.add("GeometryId");
645
+ return "GeometryId";
646
+ case "material":
647
+ this.gameTypeImports.add("MaterialId");
648
+ return "MaterialId";
649
+ case "texture":
650
+ this.gameTypeImports.add("TextureId");
651
+ return "Promise<TextureId>";
652
+ case "vector":
653
+ this.gameImports.add("Vec3");
654
+ return "Vec3";
655
+ case "quat":
656
+ this.gameImports.add("Quat");
657
+ return "Quat";
658
+ case "hud":
659
+ this.gameTypeImports.add("HudSlot");
660
+ return "HudSlot";
661
+ case "action":
662
+ this.kitTypeImports.add("PortClipAction");
663
+ return "PortClipAction";
664
+ case "gltf":
665
+ this.gameTypeImports.add("ModelInfo");
666
+ return "ModelInfo";
667
+ case "keyEvent":
668
+ this.kitTypeImports.add("PortKeyEvent");
669
+ return "PortKeyEvent";
670
+ case "pointerEvent":
671
+ this.kitTypeImports.add("PortPointerEvent");
672
+ return "PortPointerEvent";
673
+ case "controls":
674
+ if (binding?.controlsClass) {
675
+ this.kitImports.add(binding.controlsClass);
676
+ return binding.controlsClass;
677
+ }
678
+ return null;
679
+ default:
680
+ return null;
681
+ }
682
+ }
683
+ findLoop() {
684
+ const visit = (node) => {
685
+ if (ts.isCallExpression(node)) {
686
+ const callee = node.expression;
687
+ const isRaf = ts.isIdentifier(callee) && callee.text === "requestAnimationFrame";
688
+ const isRafOnWindow = ts.isPropertyAccessExpression(callee) && callee.name.text === "requestAnimationFrame";
689
+ const isLoop = ts.isPropertyAccessExpression(callee) && callee.name.text === "setAnimationLoop" && this.kindOf(callee.expression) === "renderer";
690
+ const arg = node.arguments[0];
691
+ if ((isRaf || isRafOnWindow || isLoop) && arg !== void 0 && ts.isIdentifier(arg)) {
692
+ const symbol = this.symbolOf(arg);
693
+ if (symbol !== null && this.functions.has(symbol) && this.loopFunction === null) this.loopFunction = symbol;
694
+ }
695
+ }
696
+ ts.forEachChild(node, visit);
697
+ };
698
+ visit(this.sf);
699
+ if (this.loopFunction === null) return;
700
+ const decl = this.declarationOf(this.loopFunction);
701
+ if (decl !== null && ts.isFunctionDeclaration(decl)) {
702
+ this.loopHasTime = decl.parameters.length > 0;
703
+ const time = this.paramSymbol(decl, 0);
704
+ if (time !== null) this.setKind(time, "number");
705
+ this.markReachable(decl);
706
+ }
707
+ }
708
+ markReachable(fn) {
709
+ if (this.loopReachable.has(fn)) return;
710
+ this.loopReachable.add(fn);
711
+ const visit = (node) => {
712
+ if (ts.isCallExpression(node)) {
713
+ const symbol = this.functionSymbolOf(node.expression);
714
+ const decl = symbol === null ? null : this.declarationOf(symbol);
715
+ if (decl !== null && ts.isFunctionDeclaration(decl)) this.markReachable(decl);
716
+ }
717
+ ts.forEachChild(node, visit);
718
+ };
719
+ if (fn.body !== void 0) visit(fn.body);
720
+ }
721
+ isKeyHandlerBody(node) {
722
+ let current = node;
723
+ while (current !== void 0) {
724
+ if (ts.isFunctionLike(current)) {
725
+ const param = current.parameters[0];
726
+ if (param !== void 0 && ts.isIdentifier(param.name)) {
727
+ const symbol = this.symbolOf(param.name);
728
+ if (symbol !== null && this.bindings.get(symbol)?.kind === "keyEvent") return true;
729
+ }
730
+ }
731
+ current = current.parent;
732
+ }
733
+ return false;
734
+ }
735
+ collectKeys() {
736
+ const visit = (node) => {
737
+ if (ts.isElementAccessExpression(node) && ts.isIdentifier(node.expression) && this.isKeyHandlerBody(node)) {
738
+ const index = node.argumentExpression;
739
+ if (ts.isPropertyAccessExpression(index) && this.kindOf(index.expression) === "keyEvent") {
740
+ const symbol = this.symbolOf(node.expression);
741
+ if (symbol !== null) this.keyRecords.add(symbol);
742
+ }
743
+ }
744
+ if (ts.isStringLiteralLike(node) && this.isKeyHandlerBody(node)) {
745
+ const code = codeOfKey(node.text);
746
+ if (code !== null && !ts.isCallExpression(node.parent)) this.keyCodes.add(code);
747
+ }
748
+ ts.forEachChild(node, visit);
749
+ };
750
+ visit(this.sf);
751
+ const records = (node) => {
752
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
753
+ const symbol = this.symbolOf(node.expression);
754
+ const code = codeOfKey(node.name.text);
755
+ if (symbol !== null && this.keyRecords.has(symbol) && code !== null) this.keyCodes.add(code);
756
+ }
757
+ if (ts.isElementAccessExpression(node) && ts.isIdentifier(node.expression) && ts.isStringLiteralLike(node.argumentExpression)) {
758
+ const symbol = this.symbolOf(node.expression);
759
+ const code = codeOfKey(node.argumentExpression.text);
760
+ if (symbol !== null && this.keyRecords.has(symbol) && code !== null) this.keyCodes.add(code);
761
+ }
762
+ ts.forEachChild(node, records);
763
+ };
764
+ records(this.sf);
765
+ for (const symbol of this.keyRecords) {
766
+ const binding = this.bindings.get(symbol);
767
+ if (binding !== void 0 && (binding.kind === "unknown{}" || binding.kind === "unknown")) binding.kind = "boolean{}";
768
+ }
769
+ }
770
+ collectHudSlots() {
771
+ const visit = (node) => {
772
+ if (ts.isCallExpression(node) && this.hudIdOf(node) !== null) {
773
+ const id = this.hudIdOf(node) ?? "";
774
+ const parent = node.parent;
775
+ const declared = ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name);
776
+ if (!declared && !this.hudSlots.has(id)) this.hudSlots.set(id, `hud${id.replace(/(^|[^A-Za-z0-9]+)([A-Za-z0-9])/g, (_m, _s, c) => c.toUpperCase())}`);
777
+ }
778
+ ts.forEachChild(node, visit);
779
+ };
780
+ visit(this.sf);
781
+ }
782
+ hudIdOf(node) {
783
+ if (!ts.isPropertyAccessExpression(node.expression)) return null;
784
+ const receiver = node.expression.expression;
785
+ if (!ts.isIdentifier(receiver) || receiver.text !== "document") return null;
786
+ const arg = node.arguments[0];
787
+ if (arg === void 0 || !ts.isStringLiteralLike(arg)) return null;
788
+ if (node.expression.name.text === "getElementById") return arg.text;
789
+ if (node.expression.name.text === "querySelector" && arg.text.startsWith("#")) return arg.text.slice(1);
790
+ return null;
791
+ }
792
+ adviseTouch() {
793
+ if (this.keyListeners === 0 || this.touchHandled) return;
794
+ const first = this.firstKeyListener();
795
+ if (first === null) return;
796
+ this.residue(
797
+ first,
798
+ "advisory",
799
+ "input",
800
+ "keyboard only input",
801
+ 'Phones have no keyboard: add a touch path. For moving a character use thirdPersonCharacter or firstPerson from the controls topic; otherwise define virtual bindings ({ device: "virtual", control }) next to the keys and draw them with TouchControls.'
802
+ );
803
+ }
804
+ firstKeyListener() {
805
+ let found = null;
806
+ const visit = (node) => {
807
+ if (found !== null) return;
808
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "addEventListener") {
809
+ const arg = node.arguments[0];
810
+ if (arg !== void 0 && ts.isStringLiteralLike(arg) && KEY_EVENTS[arg.text] !== void 0) found = node;
811
+ }
812
+ ts.forEachChild(node, visit);
813
+ };
814
+ visit(this.sf);
815
+ return found;
816
+ }
817
+ classify() {
818
+ for (const statement of this.sf.statements) {
819
+ if (ts.isFunctionDeclaration(statement) && statement.name !== void 0) {
820
+ const symbol = this.symbolOf(statement.name);
821
+ if (symbol !== null) this.claimMember(symbol, statement.name.text, this.methods);
822
+ continue;
823
+ }
824
+ if (!ts.isVariableStatement(statement)) continue;
825
+ const constant = (statement.declarationList.flags & ts.NodeFlags.Const) !== 0;
826
+ for (const decl of statement.declarationList.declarations) {
827
+ if (!ts.isIdentifier(decl.name)) continue;
828
+ const symbol = this.symbolOf(decl.name);
829
+ const binding = symbol === null ? void 0 : this.bindings.get(symbol);
830
+ if (symbol === null || binding === void 0 || DROPPED_KINDS.has(binding.kind)) continue;
831
+ if (constant && decl.initializer !== void 0 && PRIMITIVE_KINDS.has(binding.kind) && this.isModuleConstant(decl.initializer)) {
832
+ this.moduleConstants.add(symbol);
833
+ continue;
834
+ }
835
+ this.claimMember(symbol, decl.name.text, this.fields);
836
+ }
837
+ }
838
+ const visit = (node) => {
839
+ if ((ts.isVariableDeclaration(node) || ts.isParameter(node) || ts.isFunctionDeclaration(node)) && node.name !== void 0 && ts.isIdentifier(node.name) && SCENE_LOCALS.has(node.name.text)) {
840
+ this.residue(node, "unconverted", "scene", `name ${node.name.text}`, `The ported scene keeps the game API in locals named game and port; rename this ${node.name.text}.`);
841
+ }
842
+ ts.forEachChild(node, visit);
843
+ };
844
+ visit(this.sf);
845
+ }
846
+ claimMember(symbol, name, into) {
847
+ into.add(symbol);
848
+ this.memberNames.set(symbol, RESERVED_MEMBERS.has(name) ? `${name}${MEMBER_SUFFIX}` : name);
849
+ }
850
+ memberRef(symbol, fallback) {
851
+ const name = this.memberNames.get(symbol);
852
+ return name === void 0 ? fallback : `this.${name}`;
853
+ }
854
+ isModuleConstant(init) {
855
+ let constant = true;
856
+ const visit = (node) => {
857
+ if (!constant) return;
858
+ if (ts.isCallExpression(node) || ts.isNewExpression(node) || ts.isArrayLiteralExpression(node) || ts.isObjectLiteralExpression(node) || ts.isFunctionLike(node)) {
859
+ const mathCall = ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression) && node.expression.expression.text === "Math" && node.expression.name.text !== "random";
860
+ if (!mathCall) constant = false;
861
+ }
862
+ if (ts.isIdentifier(node) && !(ts.isPropertyAccessExpression(node.parent) && node.parent.name === node)) {
863
+ const symbol = this.symbolOf(node);
864
+ if (node.text !== "Math" && (symbol === null || !this.moduleConstants.has(symbol))) constant = false;
865
+ }
866
+ ts.forEachChild(node, visit);
867
+ };
868
+ visit(init);
869
+ return constant;
870
+ }
871
+ emitScene() {
872
+ this.classify();
873
+ const constants = [];
874
+ const fields = [];
875
+ const methods = [];
876
+ const setup = [];
877
+ for (const statement of this.sf.statements) {
878
+ if (ts.isImportDeclaration(statement)) {
879
+ if (ts.isStringLiteral(statement.moduleSpecifier) && importsThree(`from "${statement.moduleSpecifier.text}"`)) this.count("scene");
880
+ else this.residue(statement, "unconverted", "dom", "import", "Only @cimplify/sdk entry points and relative imports are allowed; bring this module into src/ or replace it.");
881
+ continue;
882
+ }
883
+ if (ts.isFunctionDeclaration(statement)) {
884
+ methods.push(this.emitMethod(statement));
885
+ continue;
886
+ }
887
+ if (ts.isVariableStatement(statement)) {
888
+ this.emitTopLevelVariables(statement, constants, fields, setup);
889
+ continue;
890
+ }
891
+ if (this.isLoopStart(statement)) {
892
+ this.count("loop");
893
+ continue;
894
+ }
895
+ this.setupDepth++;
896
+ const emitted = this.emit(statement);
897
+ this.setupDepth--;
898
+ if (emitted !== DROP) setup.push(emitted);
899
+ }
900
+ for (const [id, name] of this.hudSlots) {
901
+ this.gameTypeImports.add("HudSlot");
902
+ fields.push(`private ${name}!: HudSlot;`);
903
+ setup.unshift(`this.${name} = game.ui.hudText(${JSON.stringify(id)});`);
904
+ this.count("dom");
905
+ }
906
+ return this.assemble(constants, fields, methods, [...setup, ...this.setupTail()]);
907
+ }
908
+ isLoopStart(statement) {
909
+ if (!ts.isExpressionStatement(statement) || !ts.isCallExpression(statement.expression)) return false;
910
+ const callee = statement.expression.expression;
911
+ if (this.loopFunction === null) return false;
912
+ if (ts.isIdentifier(callee)) return this.symbolOf(callee) === this.loopFunction;
913
+ return ts.isPropertyAccessExpression(callee) && callee.name.text === "setAnimationLoop";
914
+ }
915
+ setupTail() {
916
+ const tail = [];
917
+ const env = [];
918
+ if (this.env.background !== null) env.push(`background: { color: ${this.env.background} }`);
919
+ if (this.env.fog !== null) env.push(`fog: ${this.env.fog}`);
920
+ if (this.env.shadows !== null) env.push(`shadows: ${this.env.shadows}`);
921
+ if (this.env.toneMapping !== null) env.push(`toneMapping: ${this.env.toneMapping}`);
922
+ if (this.env.exposure !== null) env.push(`exposure: ${this.env.exposure}`);
923
+ if (env.length > 0) tail.push(`game.graphics.setEnvironment({ ${env.join(", ")} });`);
924
+ for (const statement of this.sf.statements) {
925
+ if (!ts.isVariableStatement(statement)) continue;
926
+ for (const decl of statement.declarationList.declarations) {
927
+ if (!ts.isIdentifier(decl.name)) continue;
928
+ const symbol = this.symbolOf(decl.name);
929
+ const binding = symbol === null ? void 0 : this.bindings.get(symbol);
930
+ if (symbol === null || binding === void 0 || binding.light !== "directional" && binding.light !== "spot") continue;
931
+ const target = binding.target ?? ["0", "0", "0"];
932
+ tail.push(`port.lookAt(${this.memberRef(symbol, binding.name)}, ${target.join(", ")});`);
933
+ }
934
+ }
935
+ if (this.keyCodes.size > 0) tail.push(`port.keys([${[...this.keyCodes].sort().map((c) => JSON.stringify(c)).join(", ")}]);`);
936
+ return tail;
937
+ }
938
+ assemble(constants, fields, methods, setup) {
939
+ const gameValues = [...this.gameImports].sort();
940
+ const gameTypes = [...this.gameTypeImports, "Game", "GameScene"].filter((v, i, a) => a.indexOf(v) === i).sort();
941
+ const kitValues = [...this.kitImports].sort();
942
+ const kitTypes = [...this.kitTypeImports].sort();
943
+ const byName = (a, b) => a.replace("type ", "").localeCompare(b.replace("type ", ""));
944
+ const gameSpecifiers = [...gameTypes.map((t) => `type ${t}`), ...gameValues].sort(byName);
945
+ const kitSpecifiers = [...kitTypes.map((t) => `type ${t}`), ...kitValues].sort(byName);
946
+ const loop = this.loopFunction;
947
+ const loopCall = loop === null ? null : `${this.memberRef(loop, loop.name)}(${this.loopHasTime ? "this.port.elapsedSeconds() * 1000" : ""});`;
948
+ const body = [];
949
+ body.push(`import { ${gameSpecifiers.join(", ")} } from "@cimplify/sdk/game";`);
950
+ body.push(`import { ${kitSpecifiers.join(", ")} } from "@cimplify/sdk/game/kit";`);
951
+ body.push("");
952
+ for (const line of constants) body.push(this.indent(line, 0));
953
+ if (constants.length > 0) body.push("");
954
+ body.push(`export class ${sceneClassName(this.options.sceneName)} implements GameScene<Game> {`);
955
+ body.push(`${INDENT}readonly name = ${JSON.stringify(this.options.sceneName)};`);
956
+ body.push(`${INDENT}private game!: Game;`);
957
+ body.push(`${INDENT}private port!: ThreePort;`);
958
+ body.push(`${INDENT}private readonly assets: PortAssetReader | null;`);
959
+ for (const line of fields) body.push(this.indent(line, 1));
960
+ body.push("");
961
+ body.push(`${INDENT}constructor(assets: PortAssetReader | null = null) {`);
962
+ body.push(`${INDENT.repeat(2)}this.assets = assets;`);
963
+ body.push(`${INDENT}}`);
964
+ body.push("");
965
+ body.push(`${INDENT}enter(game: Game): void {`);
966
+ body.push(`${INDENT.repeat(2)}this.game = game;`);
967
+ body.push(`${INDENT.repeat(2)}const port = new ThreePort(game, this.assets);`);
968
+ body.push(`${INDENT.repeat(2)}this.port = port;`);
969
+ for (const statement of setup) body.push(this.indent(statement, 2));
970
+ body.push(`${INDENT}}`);
971
+ body.push("");
972
+ body.push(`${INDENT}fixedUpdate(): void {`);
973
+ body.push(`${INDENT.repeat(2)}this.port.dispatchInput();`);
974
+ if (loopCall !== null) body.push(`${INDENT.repeat(2)}${loopCall}`);
975
+ body.push(`${INDENT}}`);
976
+ for (const method of methods) {
977
+ body.push("");
978
+ body.push(this.indent(method, 1));
979
+ }
980
+ body.push("}");
981
+ return `${body.join("\n").replace(BLANK_RUNS, "\n\n")}
982
+ `;
983
+ }
984
+ indent(block, depth) {
985
+ const lines2 = block.split("\n");
986
+ const base = lines2.slice(1).reduce((min, line) => {
987
+ if (line.trim().length === 0) return min;
988
+ const lead = line.length - line.trimStart().length;
989
+ return Math.min(min, lead);
990
+ }, Number.POSITIVE_INFINITY);
991
+ const strip = Number.isFinite(base) ? base : 0;
992
+ const prefix = INDENT.repeat(depth);
993
+ const closing = lines2.length > 1 ? lines2[lines2.length - 1] : "";
994
+ const closingLead = closing.length - closing.trimStart().length;
995
+ const shift = Math.min(strip, closingLead);
996
+ return lines2.map((line, i) => {
997
+ if (line.trim().length === 0) return "";
998
+ if (i === 0) return prefix + line.trimStart();
999
+ return prefix + line.slice(Math.min(shift, line.length - line.trimStart().length));
1000
+ }).join("\n");
1001
+ }
1002
+ emitTopLevelVariables(statement, constants, fields, setup) {
1003
+ const constant = (statement.declarationList.flags & ts.NodeFlags.Const) !== 0;
1004
+ for (const decl of statement.declarationList.declarations) {
1005
+ if (!ts.isIdentifier(decl.name)) {
1006
+ this.residue(decl, "unconverted", "scene", "destructuring declaration", "Declare each value on its own line so the converter can place it.");
1007
+ fields.push(`private ${decl.getText(this.sf)};`);
1008
+ continue;
1009
+ }
1010
+ const symbol = this.symbolOf(decl.name);
1011
+ const binding = symbol === null ? null : this.bindings.get(symbol) ?? null;
1012
+ const kind = binding?.kind ?? "unknown";
1013
+ if (DROPPED_KINDS.has(kind)) {
1014
+ this.count(kind === "clock" ? "loop" : kind === "scene" ? "scene" : kind === "renderer" ? "renderer" : "loader");
1015
+ continue;
1016
+ }
1017
+ const init = decl.initializer;
1018
+ if (symbol !== null && this.moduleConstants.has(symbol) && init !== void 0) {
1019
+ constants.push(`const ${decl.name.text} = ${this.emit(init)};`);
1020
+ continue;
1021
+ }
1022
+ if (binding !== null) this.foldSiblings(decl, binding);
1023
+ const member = symbol === null ? decl.name.text : this.memberNames.get(symbol) ?? decl.name.text;
1024
+ const hudId = init !== void 0 && ts.isCallExpression(init) ? this.hudIdOf(init) : null;
1025
+ if (hudId !== null) {
1026
+ this.gameTypeImports.add("HudSlot");
1027
+ fields.push(`private ${member}!: HudSlot;`);
1028
+ setup.push(`this.${member} = game.ui.hudText(${JSON.stringify(hudId)});`);
1029
+ this.count("dom");
1030
+ continue;
1031
+ }
1032
+ const type = this.typeOf(kind, binding);
1033
+ if (init !== void 0 && this.isPure(init)) {
1034
+ const annotation = type !== null && this.needsAnnotation(init) ? `: ${type}` : "";
1035
+ fields.push(`private ${constant ? "readonly " : ""}${member}${annotation} = ${this.emit(init)};`);
1036
+ continue;
1037
+ }
1038
+ if (type === null) {
1039
+ const from = init === void 0 ? "" : ` from ${init.getText(this.sf).slice(0, 80)}`;
1040
+ this.residue(decl, "unconverted", "scene", "untyped declaration", `Give ${decl.name.text} a type; the converter could not infer one${from}.`);
1041
+ }
1042
+ fields.push(`private ${member}!: ${type ?? "unknown"};`);
1043
+ if (init === void 0) continue;
1044
+ this.setupDepth++;
1045
+ setup.push(`this.${member} = ${this.emit(init)};`);
1046
+ this.setupDepth--;
1047
+ }
1048
+ }
1049
+ needsAnnotation(init) {
1050
+ return ts.isArrayLiteralExpression(init) && init.elements.length === 0 || ts.isObjectLiteralExpression(init) && init.properties.length === 0 || init.kind === ts.SyntaxKind.NullKeyword;
1051
+ }
1052
+ isPure(expr) {
1053
+ let pure = true;
1054
+ const visit = (node) => {
1055
+ if (!pure) return;
1056
+ if (ts.isIdentifier(node)) {
1057
+ if (this.isThreeRoot(node)) pure = false;
1058
+ if (node.text === "document" || node.text === "window" || node.text === "performance") pure = false;
1059
+ const symbol = this.symbolOf(node);
1060
+ if (symbol !== null && this.functions.has(symbol)) pure = false;
1061
+ const binding = symbol === null ? void 0 : this.bindings.get(symbol);
1062
+ if (binding !== void 0 && !this.isPureBinding(binding)) pure = false;
1063
+ }
1064
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression) && node.expression.expression.text === "Math" && node.expression.name.text === "random") pure = false;
1065
+ ts.forEachChild(node, visit);
1066
+ };
1067
+ visit(expr);
1068
+ return pure;
1069
+ }
1070
+ isPureBinding(binding) {
1071
+ const decl = this.declarationOf(binding.symbol);
1072
+ if (decl === null || !ts.isVariableDeclaration(decl)) return true;
1073
+ if (decl.parent.parent.parent !== this.sf) return true;
1074
+ return decl.initializer === void 0 || this.isPure(decl.initializer);
1075
+ }
1076
+ foldSiblings(decl, binding) {
1077
+ const statement = decl.parent.parent;
1078
+ const container = statement.parent;
1079
+ if (!ts.isBlock(container) && !ts.isSourceFile(container)) return;
1080
+ for (const sibling of container.statements) {
1081
+ if (!ts.isExpressionStatement(sibling) || !ts.isBinaryExpression(sibling.expression)) continue;
1082
+ const assign = sibling.expression;
1083
+ if (assign.operatorToken.kind !== ts.SyntaxKind.EqualsToken) continue;
1084
+ const path2 = this.pathOf(assign.left);
1085
+ if (path2 === null || path2.root.getText(this.sf) !== binding.name) continue;
1086
+ const value = assign.right.getText(this.sf);
1087
+ const [first, second, third] = path2.names;
1088
+ if (path2.names.length === 1 && (first === "castShadow" || first === "receiveShadow")) {
1089
+ if (value === "true") binding.shadows |= first === "castShadow" ? CAST_SHADOW_FLAG : RECEIVE_SHADOW_FLAG;
1090
+ this.foldedStatements.add(sibling);
1091
+ }
1092
+ if (first === "shadow" && second === "mapSize" && (third === "width" || third === "height")) {
1093
+ binding.shadowMapSize = value;
1094
+ this.foldedStatements.add(sibling);
1095
+ }
1096
+ }
1097
+ }
1098
+ pathOf(expr) {
1099
+ const names = [];
1100
+ let current = expr;
1101
+ while (ts.isPropertyAccessExpression(current)) {
1102
+ names.unshift(current.name.text);
1103
+ current = current.expression;
1104
+ }
1105
+ return names.length === 0 ? null : { root: current, names };
1106
+ }
1107
+ emitMethod(fn) {
1108
+ for (const param of fn.parameters) {
1109
+ if (param.type !== void 0 || !ts.isIdentifier(param.name)) continue;
1110
+ const symbol2 = this.symbolOf(param.name);
1111
+ const binding = symbol2 === null ? null : this.bindings.get(symbol2) ?? null;
1112
+ if (binding === null || this.typeOf(binding.kind, binding) === null) {
1113
+ this.residue(param, "unconverted", "scene", "untyped parameter", `Give parameter ${param.name.text} of ${fn.name?.text ?? "this function"} a type; no call site told the converter what it receives.`);
1114
+ }
1115
+ }
1116
+ const symbol = fn.name === void 0 ? null : this.symbolOf(fn.name);
1117
+ if (symbol !== null && symbol === this.loopFunction) this.count("loop");
1118
+ if (fn.body !== void 0 && this.loopReachable.has(fn)) this.adviseAllocations(fn.body);
1119
+ const name = symbol === null ? fn.name?.text ?? "anonymous" : this.memberNames.get(symbol) ?? fn.name?.text ?? "anonymous";
1120
+ const isAsync = fn.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true;
1121
+ const params = fn.parameters.map((p) => this.emit(p)).join(", ");
1122
+ const returns = fn.type === void 0 ? "" : `: ${fn.type.getText(this.sf)}`;
1123
+ const body = fn.body === void 0 ? "{}" : this.withSceneLocals(this.emit(fn.body), this.statementIndent(fn) + INDENT);
1124
+ return `private ${isAsync ? "async " : ""}${name}(${params})${returns} ${body}`;
1125
+ }
1126
+ withSceneLocals(body, indent) {
1127
+ const locals = [];
1128
+ if (/\bgame\./.test(body)) locals.push("const game = this.game;");
1129
+ if (/\bport\./.test(body)) locals.push("const port = this.port;");
1130
+ if (locals.length === 0 || !body.startsWith("{")) return body;
1131
+ return `{
1132
+ ${locals.map((l) => indent + l).join("\n")}${body.slice(1)}`;
1133
+ }
1134
+ emit(node) {
1135
+ const rewritten = this.rewrite(node);
1136
+ if (rewritten !== null) return rewritten;
1137
+ return this.spliced(node);
1138
+ }
1139
+ spliced(node) {
1140
+ const start = node.getStart(this.sf);
1141
+ let out = "";
1142
+ let cursor = start;
1143
+ const parts = [];
1144
+ ts.forEachChild(node, (child) => {
1145
+ parts.push(child);
1146
+ });
1147
+ for (const child of parts) {
1148
+ const childStart = child.getStart(this.sf);
1149
+ if (childStart < cursor) continue;
1150
+ out += this.text.slice(cursor, childStart);
1151
+ const emitted = this.emit(child);
1152
+ if (emitted === DROP && ts.isStatement(child) && !ts.isBlock(node) && !ts.isSourceFile(node) && !ts.isCaseOrDefaultClause(node)) {
1153
+ out += "{}";
1154
+ cursor = child.end;
1155
+ continue;
1156
+ }
1157
+ if (emitted === DROP && ts.isStatement(child)) {
1158
+ out = out.replace(/[ \t]*$/, "");
1159
+ if (out.endsWith("\n")) out = out.slice(0, -1);
1160
+ cursor = child.end;
1161
+ continue;
1162
+ }
1163
+ out += emitted === DROP ? "" : emitted;
1164
+ cursor = child.end;
1165
+ }
1166
+ out += this.text.slice(cursor, node.end);
1167
+ return out;
1168
+ }
1169
+ rewrite(node) {
1170
+ if (this.foldedStatements.has(node)) {
1171
+ this.count("object");
1172
+ return DROP;
1173
+ }
1174
+ if (ts.isFunctionLike(node) && !ts.isFunctionDeclaration(node) && (ts.isArrowFunction(node) || ts.isFunctionExpression(node))) return this.rewriteCallback(node);
1175
+ if (ts.isFunctionDeclaration(node)) return this.rewriteFunction(node);
1176
+ if (ts.isForOfStatement(node)) return this.rewriteForOf(node);
1177
+ if (ts.isShorthandPropertyAssignment(node)) return this.rewriteShorthand(node);
1178
+ if (ts.isParameter(node)) return this.rewriteParameter(node);
1179
+ if (ts.isVariableStatement(node)) return this.rewriteLocalVariables(node);
1180
+ if (ts.isExpressionStatement(node)) return this.rewriteExpressionStatement(node);
1181
+ if (ts.isNewExpression(node)) return this.rewriteNew(node);
1182
+ if (ts.isCallExpression(node)) return this.rewriteCall(node);
1183
+ if (ts.isBinaryExpression(node)) return this.rewriteBinary(node);
1184
+ if (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) return this.rewriteIncrement(node);
1185
+ if (ts.isPropertyAccessExpression(node)) return this.rewriteProperty(node);
1186
+ if (ts.isElementAccessExpression(node)) return null;
1187
+ if (ts.isIdentifier(node)) return this.rewriteIdentifier(node);
1188
+ return null;
1189
+ }
1190
+ rewriteIdentifier(node) {
1191
+ const member = this.memberReference(node);
1192
+ if (member !== null) return member;
1193
+ if (!this.isThreeRoot(node)) return null;
1194
+ const line = this.sf.getLineAndCharacterOfPosition(node.getStart(this.sf)).line + 1;
1195
+ if (this.residueItems.some((r) => r.line === line && r.kind === "unconverted")) return null;
1196
+ this.residue(node, "unconverted", "scene", node.text, "This Three.js API has no automatic mapping; see the porting-threejs guide topic for the game API equivalent.");
1197
+ return null;
1198
+ }
1199
+ memberReference(node) {
1200
+ const parent = node.parent;
1201
+ if ((ts.isVariableDeclaration(parent) || ts.isParameter(parent) || ts.isFunctionDeclaration(parent) || ts.isPropertyAssignment(parent) || ts.isPropertyAccessExpression(parent) || ts.isShorthandPropertyAssignment(parent)) && parent.name === node) return null;
1202
+ const symbol = this.symbolOf(node);
1203
+ if (symbol === null) return null;
1204
+ const name = this.memberNames.get(symbol);
1205
+ if (name === void 0) return null;
1206
+ if (this.methods.has(symbol) && !(ts.isCallExpression(parent) && parent.expression === node)) return `this.${name}.bind(this)`;
1207
+ return `this.${name}`;
1208
+ }
1209
+ rewriteShorthand(node) {
1210
+ const symbol = this.checker.getShorthandAssignmentValueSymbol(node) ?? null;
1211
+ if (symbol === null || !this.memberNames.has(symbol)) return null;
1212
+ return `${node.name.text}: ${this.memberRef(symbol, node.name.text)}`;
1213
+ }
1214
+ rewriteFunction(fn) {
1215
+ if (fn.body === void 0 || fn.name === void 0 || fn.parent === this.sf) return null;
1216
+ if (this.loopReachable.has(fn)) this.adviseAllocations(fn.body);
1217
+ if (this.usesOwnThis(fn)) return null;
1218
+ const isAsync = fn.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true;
1219
+ return `const ${fn.name.text} = ${isAsync ? "async " : ""}(${fn.parameters.map((p) => this.emit(p)).join(", ")}) => ${this.emit(fn.body)};`;
1220
+ }
1221
+ usesOwnThis(fn) {
1222
+ let uses = false;
1223
+ const visit = (node) => {
1224
+ if (uses) return;
1225
+ if (node.kind === ts.SyntaxKind.ThisKeyword || ts.isIdentifier(node) && node.text === "arguments") uses = true;
1226
+ if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isClassLike(node)) return;
1227
+ ts.forEachChild(node, visit);
1228
+ };
1229
+ if (fn.body !== void 0) ts.forEachChild(fn.body, visit);
1230
+ return uses || fn.asteriskToken !== void 0;
1231
+ }
1232
+ rewriteForOf(node) {
1233
+ if (node.awaitModifier !== void 0 || !ts.isVariableDeclarationList(node.initializer)) return null;
1234
+ const decl = node.initializer.declarations[0];
1235
+ if (decl === void 0 || !ts.isIdentifier(decl.name) || !isArrayKind(this.kindOf(node.expression))) return null;
1236
+ const source = this.emit(node.expression);
1237
+ if (!SIMPLE_REFERENCE.test(source)) return null;
1238
+ const item = decl.name.text;
1239
+ let index = `${item}Index`;
1240
+ for (let n = 2; this.text.includes(index); n++) index = `${item}Index${n}`;
1241
+ const read = `const ${item} = ${source}[${index}];`;
1242
+ const body = this.emit(node.statement);
1243
+ const head = `for (let ${index} = 0; ${index} < ${source}.length; ${index}++)`;
1244
+ this.count("loop");
1245
+ if (ts.isBlock(node.statement) && body.startsWith("{")) {
1246
+ const first = node.statement.statements[0];
1247
+ const indent = first === void 0 ? this.statementIndent(node) + INDENT : this.statementIndent(first);
1248
+ return `${head} {
1249
+ ${indent}${read}${body.slice(1)}`;
1250
+ }
1251
+ return `${head} {
1252
+ ${this.statementIndent(node)}${INDENT}${read}
1253
+ ${this.statementIndent(node)}${INDENT}${body}
1254
+ ${this.statementIndent(node)}}`;
1255
+ }
1256
+ rewriteCallback(fn) {
1257
+ const param = fn.parameters[0];
1258
+ const symbol = param !== void 0 && ts.isIdentifier(param.name) ? this.symbolOf(param.name) : null;
1259
+ const binding = symbol === null ? void 0 : this.bindings.get(symbol);
1260
+ const gltf = param !== void 0 && ts.isIdentifier(param.name) && binding?.kind === "gltf" && ts.isBlock(fn.body) && this.usesScene(fn.body, symbol);
1261
+ const asArrow = ts.isArrowFunction(fn) || !this.usesOwnThis(fn);
1262
+ if (!gltf && !asArrow) return null;
1263
+ let body = this.emit(fn.body);
1264
+ if (gltf && param !== void 0 && ts.isIdentifier(param.name) && body.startsWith("{")) {
1265
+ const first = ts.isBlock(fn.body) ? fn.body.statements[0] : void 0;
1266
+ const indent = first === void 0 ? this.statementIndent(fn) + INDENT : this.statementIndent(first);
1267
+ body = `{
1268
+ ${indent}const ${param.name.text}Scene = port.instantiate(${param.name.text});${body.slice(1)}`;
1269
+ }
1270
+ const isAsync = fn.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true;
1271
+ const params = fn.parameters.map((p) => this.emit(p)).join(", ");
1272
+ const returns = fn.type === void 0 ? "" : `: ${fn.type.getText(this.sf)}`;
1273
+ return `${isAsync ? "async " : ""}(${params})${returns} => ${body}`;
1274
+ }
1275
+ usesScene(body, symbol) {
1276
+ let used = false;
1277
+ const visit = (node) => {
1278
+ if (ts.isPropertyAccessExpression(node) && node.name.text === "scene" && ts.isIdentifier(node.expression) && this.symbolOf(node.expression) === symbol) used = true;
1279
+ ts.forEachChild(node, visit);
1280
+ };
1281
+ visit(body);
1282
+ return used;
1283
+ }
1284
+ adviseAllocations(body) {
1285
+ const visit = (node) => {
1286
+ if (ts.isFunctionDeclaration(node)) return;
1287
+ if (ts.isNewExpression(node)) {
1288
+ const name = this.threeName(node.expression);
1289
+ if (name !== "Color") this.residue(node, "advisory", "loop", "allocation in the frame loop", "Create this once outside the loop (a closure-level scratch) and reuse it every frame.");
1290
+ }
1291
+ if (ts.isArrayLiteralExpression(node) || ts.isObjectLiteralExpression(node) && !ts.isCallExpression(node.parent)) {
1292
+ this.residue(node, "advisory", "loop", "allocation in the frame loop", "Array and object literals allocate every frame; hoist them into closure-level scratch.");
1293
+ }
1294
+ if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
1295
+ this.residue(node, "advisory", "loop", "closure in the frame loop", "A closure created per frame allocates; hoist it to a named function.");
1296
+ return;
1297
+ }
1298
+ ts.forEachChild(node, visit);
1299
+ };
1300
+ visit(body);
1301
+ }
1302
+ rewriteParameter(param) {
1303
+ if (param.type !== void 0 || !ts.isIdentifier(param.name)) return null;
1304
+ const symbol = this.symbolOf(param.name);
1305
+ const binding = symbol === null ? null : this.bindings.get(symbol) ?? null;
1306
+ if (binding === null) return null;
1307
+ const type = this.typeOf(binding.kind, binding);
1308
+ if (type === null) return null;
1309
+ const initializer = param.initializer === void 0 ? "" : ` = ${this.emit(param.initializer)}`;
1310
+ return `${param.dotDotDotToken === void 0 ? "" : "..."}${param.name.text}: ${type}${initializer}`;
1311
+ }
1312
+ rewriteLocalVariables(statement) {
1313
+ const parts = [];
1314
+ for (const decl of statement.declarationList.declarations) {
1315
+ if (!ts.isIdentifier(decl.name)) {
1316
+ parts.push(this.emit(decl));
1317
+ continue;
1318
+ }
1319
+ const symbol = this.symbolOf(decl.name);
1320
+ const binding = symbol === null ? null : this.bindings.get(symbol) ?? null;
1321
+ const kind = binding?.kind ?? "unknown";
1322
+ if (kind === "clock" || kind === "gltfLoader" || kind === "textureLoader" || kind === "scene" || kind === "renderer") {
1323
+ this.count(kind === "clock" ? "loop" : "loader");
1324
+ continue;
1325
+ }
1326
+ if (binding !== null) this.foldSiblings(decl, binding);
1327
+ const type = this.typeOf(kind, binding);
1328
+ const annotate = decl.type === void 0 && type !== null && (decl.initializer === void 0 || this.needsAnnotation(decl.initializer));
1329
+ const init = decl.initializer === void 0 ? "" : ` = ${this.emit(decl.initializer)}`;
1330
+ parts.push(`${decl.name.text}${annotate ? `: ${type}` : decl.type === void 0 ? "" : `: ${decl.type.getText(this.sf)}`}${init}`);
1331
+ }
1332
+ if (parts.length === 0) return DROP;
1333
+ const flags = statement.declarationList.flags;
1334
+ const keyword = (flags & ts.NodeFlags.Const) !== 0 ? "const" : (flags & ts.NodeFlags.Let) !== 0 ? "let" : "var";
1335
+ return `${keyword} ${parts.join(", ")};`;
1336
+ }
1337
+ statementIndent(node) {
1338
+ const start = node.getStart(this.sf);
1339
+ const lineStart = this.text.lastIndexOf("\n", start - 1) + 1;
1340
+ return this.text.slice(lineStart, start).replace(/\S/g, " ");
1341
+ }
1342
+ rewriteExpressionStatement(statement) {
1343
+ const expr = statement.expression;
1344
+ if (ts.isCallExpression(expr)) {
1345
+ const many = this.rewriteMultiCall(expr);
1346
+ if (many !== null) return many.length === 0 ? DROP : many.map((line) => `${line};`).join(`
1347
+ ${this.statementIndent(statement)}`);
1348
+ if (this.isDroppedCall(expr)) return DROP;
1349
+ }
1350
+ if (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.EqualsToken && this.isDroppedAssignment(expr)) return DROP;
1351
+ return null;
1352
+ }
1353
+ rewriteMultiCall(expr) {
1354
+ const callee = expr.expression;
1355
+ if (!ts.isPropertyAccessExpression(callee)) return null;
1356
+ const method = callee.name.text;
1357
+ const receiverKind = this.kindOf(callee.expression);
1358
+ if (receiverKind === "scene" && (method === "add" || method === "remove")) {
1359
+ this.count("scene");
1360
+ if (method === "add" && this.setupDepth > 0) return [];
1361
+ return expr.arguments.filter((a) => !this.isLightTarget(a) && !(method === "add" && this.createdVisible(a))).map((a) => `port.setVisible(${this.emitEntity(a)}, ${method === "add" ? "true" : "false"})`);
1362
+ }
1363
+ if (receiverKind === "entity" && (method === "add" || method === "remove")) {
1364
+ this.count("object");
1365
+ return expr.arguments.map((a) => `port.${method}(${this.emit(callee.expression)}, ${this.emitEntity(a)})`);
1366
+ }
1367
+ return null;
1368
+ }
1369
+ createdVisible(expr) {
1370
+ if (!ts.isIdentifier(expr)) return false;
1371
+ const symbol = this.symbolOf(expr);
1372
+ const decl = symbol === null ? null : this.declarationOf(symbol);
1373
+ if (decl === null || !ts.isVariableDeclaration(decl) || decl.initializer === void 0 || !ts.isNewExpression(decl.initializer)) return false;
1374
+ const name = this.threeName(decl.initializer.expression);
1375
+ return name !== null && ENTITY_CLASSES.has(name) && decl.parent.parent.parent !== this.sf;
1376
+ }
1377
+ isLightTarget(expr) {
1378
+ return ts.isPropertyAccessExpression(expr) && expr.name.text === "target";
1379
+ }
1380
+ emitEntity(expr) {
1381
+ if (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression) && expr.expression.name.text === "getObject" && this.kindOf(expr.expression.expression) === "controls") {
1382
+ const binding = this.bindingOf(expr.expression.expression);
1383
+ if (binding?.camera) return this.emit(binding.camera);
1384
+ }
1385
+ return this.emit(expr);
1386
+ }
1387
+ isDroppedCall(expr) {
1388
+ const callee = expr.expression;
1389
+ if (ts.isIdentifier(callee) && callee.text === "requestAnimationFrame") {
1390
+ this.count("loop");
1391
+ return true;
1392
+ }
1393
+ if (!ts.isPropertyAccessExpression(callee)) return false;
1394
+ const method = callee.name.text;
1395
+ const receiver = callee.expression;
1396
+ const receiverKind = this.kindOf(receiver);
1397
+ if (method === "requestAnimationFrame") {
1398
+ this.count("loop");
1399
+ return true;
1400
+ }
1401
+ if (receiverKind === "renderer") return this.dropRendererCall(expr, method);
1402
+ if (method === "appendChild" && expr.arguments[0] !== void 0 && this.kindOf(expr.arguments[0]) === "domElement") {
1403
+ this.count("renderer");
1404
+ return true;
1405
+ }
1406
+ if ((receiverKind === "geometry" || receiverKind === "material" || receiverKind === "texture") && method === "dispose") {
1407
+ this.count(receiverKind === "geometry" ? "geometry" : "material");
1408
+ return true;
1409
+ }
1410
+ if (receiverKind === "clock" && (method === "start" || method === "stop")) {
1411
+ this.count("loop");
1412
+ return true;
1413
+ }
1414
+ if (receiverKind === "mixer" && method === "update") {
1415
+ this.count("animation");
1416
+ return true;
1417
+ }
1418
+ if (receiverKind === "entity" && (CAMERA_DROPPED_MEMBERS.has(method) || MATRIX_UPDATES.has(method))) {
1419
+ this.count("camera");
1420
+ return true;
1421
+ }
1422
+ if (method === "addEventListener") {
1423
+ const type = expr.arguments[0];
1424
+ if (type !== void 0 && ts.isStringLiteralLike(type) && DROPPED_EVENTS.has(type.text) && this.isListenerTarget(receiver)) {
1425
+ this.count("renderer");
1426
+ return true;
1427
+ }
1428
+ }
1429
+ const path2 = this.pathOf(callee);
1430
+ if (path2 !== null && path2.names.length === 3 && path2.names[0] === "target" && path2.names[1] === "position" && method === "set" && this.kindOf(path2.root) === "entity") {
1431
+ const light = this.bindingOf(path2.root);
1432
+ if (light !== null && expr.arguments.length === 3) {
1433
+ light.target = [this.emit(expr.arguments[0]), this.emit(expr.arguments[1]), this.emit(expr.arguments[2])];
1434
+ this.count("light");
1435
+ return true;
1436
+ }
1437
+ }
1438
+ return false;
1439
+ }
1440
+ dropRendererCall(expr, method) {
1441
+ if (method === "setClearColor" && expr.arguments[0] !== void 0) {
1442
+ const color = this.colorExpr(expr.arguments[0]);
1443
+ if (color !== null) return this.applyEnvironment("background", color, expr);
1444
+ }
1445
+ this.count("renderer");
1446
+ return true;
1447
+ }
1448
+ isListenerTarget(expr) {
1449
+ if (ts.isIdentifier(expr) && LISTENER_TARGETS.has(expr.text)) return true;
1450
+ if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.expression) && expr.expression.text === "document" && expr.name.text === "body") return true;
1451
+ return this.kindOf(expr) === "domElement";
1452
+ }
1453
+ applyEnvironment(key, value, at) {
1454
+ if (this.setupDepth > 0) {
1455
+ this.count("scene");
1456
+ this.env[key] = value;
1457
+ return true;
1458
+ }
1459
+ this.residue(at, "unconverted", "scene", "environment change at run time", `Call game.graphics.setEnvironment with the full environment here; the converter only folds setup-time ${key}.`);
1460
+ return false;
1461
+ }
1462
+ isDroppedAssignment(expr) {
1463
+ const path2 = this.pathOf(expr.left);
1464
+ if (path2 === null) return false;
1465
+ const rootKind = this.kindOf(path2.root);
1466
+ const [first, second] = path2.names;
1467
+ if (rootKind === "scene" && path2.names.length === 1) {
1468
+ if (first === "background") {
1469
+ const color = this.colorExpr(expr.right);
1470
+ if (color === null) {
1471
+ this.residue(expr, "unconverted", "scene", "scene.background", "Only colors convert; use setEnvironment({ background: { texture } }) or { sky: true } for textures and skies.");
1472
+ return false;
1473
+ }
1474
+ return this.applyEnvironment("background", color, expr);
1475
+ }
1476
+ if (first === "fog") {
1477
+ const fog = this.fogExpr(expr.right);
1478
+ if (fog === null) {
1479
+ this.residue(expr, "unconverted", "scene", "scene.fog", "Use setEnvironment({ fog: { mode, color, near, far | density } }).");
1480
+ return false;
1481
+ }
1482
+ return this.applyEnvironment("fog", fog, expr);
1483
+ }
1484
+ }
1485
+ if (rootKind === "renderer") {
1486
+ if (first === "shadowMap" && second === "enabled") return this.applyEnvironment("shadows", this.emit(expr.right), expr);
1487
+ if (first === "toneMapping") {
1488
+ const constant = this.threeName(expr.right);
1489
+ const mapped = constant === null ? void 0 : TONE_MAPPINGS[constant];
1490
+ if (mapped !== void 0) return this.applyEnvironment("toneMapping", JSON.stringify(mapped), expr);
1491
+ }
1492
+ if (first === "toneMappingExposure") return this.applyEnvironment("exposure", this.emit(expr.right), expr);
1493
+ this.count("renderer");
1494
+ return true;
1495
+ }
1496
+ if (rootKind === "entity" && path2.names.length === 1 && CAMERA_DROPPED_MEMBERS.has(first)) {
1497
+ this.count("camera");
1498
+ return true;
1499
+ }
1500
+ if (rootKind === "entity" && first === "shadow") {
1501
+ this.count("light");
1502
+ return true;
1503
+ }
1504
+ if (rootKind === "action" && first === "clampWhenFinished") {
1505
+ this.residue(expr, "advisory", "animation", "clampWhenFinished", "Engine clips stop on their last pose with setLoop(false); check the end pose in the preview.");
1506
+ return true;
1507
+ }
1508
+ return false;
1509
+ }
1510
+ fogExpr(expr) {
1511
+ if (!ts.isNewExpression(expr)) return null;
1512
+ const name = this.threeName(expr.expression);
1513
+ const args = expr.arguments ?? [];
1514
+ const color = args[0] === void 0 ? null : this.colorExpr(args[0]);
1515
+ if (color === null) return null;
1516
+ if (name === "Fog") return `{ mode: "linear", color: ${color}, near: ${args[1] === void 0 ? "1" : this.emit(args[1])}, far: ${args[2] === void 0 ? "1000" : this.emit(args[2])} }`;
1517
+ if (name === "FogExp2") return `{ mode: "exp2", color: ${color}, density: ${args[1] === void 0 ? "0.00025" : this.emit(args[1])} }`;
1518
+ return null;
1519
+ }
1520
+ colorExpr(expr) {
1521
+ if (ts.isNumericLiteral(expr)) return expr.getText(this.sf);
1522
+ if (ts.isStringLiteralLike(expr)) {
1523
+ const value = colorLiteral(expr.text);
1524
+ return value === null ? null : hexText(value);
1525
+ }
1526
+ if (ts.isNewExpression(expr) && this.threeName(expr.expression) === "Color") {
1527
+ const args = expr.arguments ?? [];
1528
+ if (args.length === 1) return this.colorExpr(args[0]);
1529
+ if (args.length === 3 && args.every((a) => ts.isNumericLiteral(a))) {
1530
+ const [r, g, b] = args.map((a) => Math.round(Number(a.text) * COLOR_CHANNEL_MAX));
1531
+ return hexText(r << 16 | g << 8 | b);
1532
+ }
1533
+ return null;
1534
+ }
1535
+ const kind = this.kindOf(expr);
1536
+ if (kind === "number" || ts.isIdentifier(expr) || ts.isPropertyAccessExpression(expr)) return this.emit(expr);
1537
+ return null;
1538
+ }
1539
+ rewriteNew(expr) {
1540
+ const name = this.threeName(expr.expression);
1541
+ if (name === null) return null;
1542
+ const args = expr.arguments ?? [];
1543
+ const geometry = THREE_GEOMETRIES[name];
1544
+ if (geometry !== void 0) return this.newGeometry(expr, name, args);
1545
+ const material = THREE_MATERIALS[name];
1546
+ if (material !== void 0) return this.newMaterial(expr, name, args);
1547
+ const light = THREE_LIGHTS[name];
1548
+ if (light !== void 0) return this.newLight(expr, args);
1549
+ const controls = THREE_CONTROLS[name];
1550
+ if (controls !== void 0) {
1551
+ this.kitImports.add(controls);
1552
+ this.count("controls");
1553
+ this.touchHandled = true;
1554
+ if (args[0] === void 0) this.gameImports.add("NO_ENTITY");
1555
+ const camera = args[0] === void 0 ? "NO_ENTITY" : this.emit(args[0]);
1556
+ return `new ${controls}(game.transforms, ${camera}, game.input)`;
1557
+ }
1558
+ switch (name) {
1559
+ case "Mesh":
1560
+ return this.newMesh(expr, args);
1561
+ case "Group":
1562
+ this.count("object");
1563
+ return "port.group()";
1564
+ case "Object3D":
1565
+ this.count("object");
1566
+ return "port.object()";
1567
+ case "PerspectiveCamera":
1568
+ this.count("camera");
1569
+ return `port.perspectiveCamera(${args[0] === void 0 ? DEFAULT_FOV : this.emit(args[0])}, ${args[2] === void 0 ? DEFAULT_NEAR : this.emit(args[2])}, ${args[3] === void 0 ? DEFAULT_FAR : this.emit(args[3])})`;
1570
+ case "OrthographicCamera": {
1571
+ this.count("camera");
1572
+ const values = [0, 1, 2, 3].map((i) => args[i] === void 0 ? i % 2 === 0 ? "-1" : "1" : this.emit(args[i]));
1573
+ return `port.orthographicCamera(${values.join(", ")}, ${args[4] === void 0 ? DEFAULT_NEAR : this.emit(args[4])}, ${args[5] === void 0 ? DEFAULT_FAR : this.emit(args[5])})`;
1574
+ }
1575
+ case "Vector3":
1576
+ this.gameImports.add("Vec3");
1577
+ this.count("math");
1578
+ return `new Vec3(${args.map((a) => this.emit(a)).join(", ")})`;
1579
+ case "Quaternion":
1580
+ this.gameImports.add("Quat");
1581
+ this.count("math");
1582
+ return `new Quat(${args.map((a) => this.emit(a)).join(", ")})`;
1583
+ case "Color": {
1584
+ const color = this.colorExpr(expr);
1585
+ if (color !== null) {
1586
+ this.count("math");
1587
+ return color;
1588
+ }
1589
+ this.residue(expr, "unconverted", "math", "Color", "Use a 0xrrggbb number.");
1590
+ return null;
1591
+ }
1592
+ case "AnimationMixer":
1593
+ this.count("animation");
1594
+ if (args[0] === void 0) this.gameImports.add("NO_ENTITY");
1595
+ return args[0] === void 0 ? "NO_ENTITY" : this.emit(args[0]);
1596
+ case "Scene":
1597
+ case "Clock":
1598
+ case "GLTFLoader":
1599
+ case "TextureLoader":
1600
+ case "WebGLRenderer":
1601
+ this.count("renderer");
1602
+ return "undefined";
1603
+ default:
1604
+ this.residue(expr, "unconverted", this.areaOfClass(name), name, "No automatic mapping; see the porting-threejs guide topic for the game API equivalent.");
1605
+ return null;
1606
+ }
1607
+ }
1608
+ areaOfClass(name) {
1609
+ if (name.endsWith("Geometry")) return "geometry";
1610
+ if (name.endsWith("Material")) return "material";
1611
+ if (name.endsWith("Light")) return "light";
1612
+ if (name.endsWith("Loader")) return "loader";
1613
+ if (name.endsWith("Camera")) return "camera";
1614
+ if (name.startsWith("Audio") || name.endsWith("Audio")) return "audio";
1615
+ if (name === "Raycaster") return "picking";
1616
+ return "scene";
1617
+ }
1618
+ newGeometry(expr, name, args) {
1619
+ const rule = THREE_GEOMETRIES[name];
1620
+ const extended = args.length > rule.simpleArgs && rule.extendedFn !== null;
1621
+ const fn = extended ? rule.extendedFn ?? rule.fn : rule.fn;
1622
+ const values = args.map((a) => this.emit(a));
1623
+ for (let i = values.length; i < rule.defaults.length; i++) values.push(String(rule.defaults[i]));
1624
+ if (name === "PlaneGeometry" && !extended) values.length = Math.min(values.length, 2);
1625
+ if (name === "BoxGeometry" && !extended) values.length = Math.min(values.length, 3);
1626
+ this.gameImports.add(fn);
1627
+ this.count("geometry");
1628
+ if (expr.arguments === void 0) return `game.graphics.createGeometry(${fn}(${values.join(", ")}))`;
1629
+ return `game.graphics.createGeometry(${fn}(${values.join(", ")}))`;
1630
+ }
1631
+ newMaterial(expr, name, args) {
1632
+ const rule = THREE_MATERIALS[name];
1633
+ const allowed = MATERIAL_PROPERTIES_BY_KIND[rule.kind];
1634
+ const props = [`kind: "${rule.kind}"`];
1635
+ const textures = [];
1636
+ const desc = args[0];
1637
+ let roughnessSet = false;
1638
+ let shininess = null;
1639
+ if (desc !== void 0 && !ts.isObjectLiteralExpression(desc)) {
1640
+ this.residue(expr, "unconverted", "material", name, "Pass the material parameters as an object literal so they can be mapped.");
1641
+ return null;
1642
+ }
1643
+ if (desc !== void 0) {
1644
+ for (const prop of desc.properties) {
1645
+ if (!ts.isPropertyAssignment(prop) && !ts.isShorthandPropertyAssignment(prop)) {
1646
+ this.residue(prop, "unconverted", "material", `${name} spread`, "Write material parameters out one by one.");
1647
+ continue;
1648
+ }
1649
+ const key = prop.name.getText(this.sf).replace(/^["']|["']$/g, "");
1650
+ const value = ts.isPropertyAssignment(prop) ? prop.initializer : prop.name;
1651
+ if (key === "shininess" && rule.shininessToRoughness) {
1652
+ shininess = ts.isNumericLiteral(value) ? value.text : `(${this.emit(value)})`;
1653
+ continue;
1654
+ }
1655
+ if (key === "specular" && rule.shininessToRoughness) continue;
1656
+ if (!allowed.has(key)) {
1657
+ this.residue(prop, "advisory", "material", `${name}.${key}`, `The ${rule.kind} material has no ${key}; it was dropped. Check the look in the preview.`);
1658
+ continue;
1659
+ }
1660
+ if (key === "roughness") roughnessSet = true;
1661
+ if (THREE_TEXTURE_SLOTS.includes(key)) {
1662
+ if (this.kindOf(value) === "texture") {
1663
+ textures.push([key, this.emit(value)]);
1664
+ continue;
1665
+ }
1666
+ }
1667
+ if (COLOR_PROPERTIES.has(key)) {
1668
+ const color = this.colorExpr(value);
1669
+ if (color === null) {
1670
+ this.residue(prop, "unconverted", "material", `${name}.${key}`, "Use a 0xrrggbb number for colors.");
1671
+ props.push(`${key}: ${this.emit(value)}`);
1672
+ } else props.push(`${key}: ${color}`);
1673
+ continue;
1674
+ }
1675
+ if (key === "side") {
1676
+ const constant = this.threeName(value);
1677
+ const side = constant === null ? void 0 : THREE_SIDES[constant];
1678
+ if (side !== void 0) props.push(`side: "${side}"`);
1679
+ else this.residue(prop, "unconverted", "material", `${name}.side`, 'Use side: "front" | "back" | "double".');
1680
+ continue;
1681
+ }
1682
+ props.push(`${key}: ${this.emit(value)}`);
1683
+ }
1684
+ }
1685
+ if (rule.shininessToRoughness && !roughnessSet) {
1686
+ const s = shininess ?? String(PHONG_SHININESS_DEFAULT);
1687
+ const literal = Number(s);
1688
+ props.push(Number.isFinite(literal) ? `roughness: ${Number(Math.sqrt(SHININESS_OFFSET / (literal + SHININESS_OFFSET)).toFixed(ROUGHNESS_DIGITS))}` : `roughness: Math.sqrt(${SHININESS_OFFSET} / (${s} + ${SHININESS_OFFSET}))`);
1689
+ }
1690
+ if (rule.roughness !== null && !roughnessSet) props.push(`roughness: ${rule.roughness}`);
1691
+ this.count("material");
1692
+ let out = `game.graphics.createMaterial({ ${props.join(", ")} })`;
1693
+ for (const [slot, texture] of textures) {
1694
+ out = `port.withTexture(${out}, "${slot}", ${texture})`;
1695
+ this.count("loader");
1696
+ }
1697
+ return out;
1698
+ }
1699
+ newLight(expr, args) {
1700
+ const name = this.threeName(expr.expression) ?? "";
1701
+ const rule = THREE_LIGHTS[name];
1702
+ const options = [];
1703
+ rule.params.forEach((param, i) => {
1704
+ const arg = args[i];
1705
+ if (arg === void 0) return;
1706
+ if (param === "color" || param === "groundColor") {
1707
+ const color = this.colorExpr(arg);
1708
+ if (color === null) this.residue(arg, "unconverted", "light", `${name} ${param}`, "Use a 0xrrggbb number.");
1709
+ options.push(`${param}: ${color ?? this.emit(arg)}`);
1710
+ return;
1711
+ }
1712
+ options.push(`${param}: ${this.emit(arg)}`);
1713
+ });
1714
+ const owner = this.ownerBinding(expr);
1715
+ if (owner !== null && (owner.shadows & CAST_SHADOW_FLAG) !== 0) options.push("castShadow: true");
1716
+ if (owner?.shadowMapSize) options.push(`shadowMapSize: ${owner.shadowMapSize}`);
1717
+ this.count("light");
1718
+ return `port.light("${rule.light}"${options.length === 0 ? "" : `, { ${options.join(", ")} }`})`;
1719
+ }
1720
+ ownerBinding(expr) {
1721
+ const parent = expr.parent;
1722
+ if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
1723
+ const symbol = this.symbolOf(parent.name);
1724
+ return symbol === null ? null : this.bindings.get(symbol) ?? null;
1725
+ }
1726
+ if (ts.isBinaryExpression(parent) && ts.isIdentifier(parent.left)) return this.bindingOf(parent.left);
1727
+ return null;
1728
+ }
1729
+ newMesh(expr, args) {
1730
+ const geometry = args[0];
1731
+ const material = args[1];
1732
+ if (geometry === void 0) {
1733
+ this.residue(expr, "unconverted", "object", "Mesh without geometry", "Create the geometry first and pass it to the mesh.");
1734
+ return null;
1735
+ }
1736
+ if (material !== void 0 && ts.isArrayLiteralExpression(material)) {
1737
+ this.residue(expr, "unconverted", "material", "Mesh with a material array", "Split the geometry into groups or use one material per mesh.");
1738
+ return null;
1739
+ }
1740
+ const owner = this.ownerBinding(expr);
1741
+ const flags = [];
1742
+ if (owner !== null && (owner.shadows & CAST_SHADOW_FLAG) !== 0) {
1743
+ flags.push("CAST_SHADOW");
1744
+ this.kitImports.add("CAST_SHADOW");
1745
+ }
1746
+ if (owner !== null && (owner.shadows & RECEIVE_SHADOW_FLAG) !== 0) {
1747
+ flags.push("RECEIVE_SHADOW");
1748
+ this.kitImports.add("RECEIVE_SHADOW");
1749
+ }
1750
+ const materialText = material === void 0 ? `game.graphics.createMaterial({ kind: "unlit", color: 0xffffff })` : this.emit(material);
1751
+ this.count("object");
1752
+ return `port.mesh(${this.emit(geometry)}, ${materialText}${flags.length === 0 ? "" : `, ${flags.join(" | ")}`})`;
1753
+ }
1754
+ assetPath(arg) {
1755
+ if (!ts.isStringLiteralLike(arg)) {
1756
+ this.residue(arg, "advisory", "loader", "computed asset path", `Asset paths must name files under ${this.options.assetPrefix}; check this one resolves.`);
1757
+ return this.emit(arg);
1758
+ }
1759
+ const relative = arg.text.replace(/^\.?\//, "");
1760
+ if (/^[a-z]+:/i.test(relative)) {
1761
+ this.residue(arg, "unconverted", "loader", "remote asset URL", "Download the file into the project's assets/ folder; Interactives cannot fetch URLs.");
1762
+ return JSON.stringify(arg.text);
1763
+ }
1764
+ this.assetPaths.add(relative);
1765
+ return JSON.stringify(`${this.options.assetPrefix}${relative}`);
1766
+ }
1767
+ rewriteCall(expr) {
1768
+ const callee = expr.expression;
1769
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression)) {
1770
+ const owner2 = callee.expression.text;
1771
+ if (owner2 === "Math" && callee.name.text === "random" && this.bindingOf(callee.expression) === null) {
1772
+ this.count("math");
1773
+ return "port.random()";
1774
+ }
1775
+ if (owner2 === "performance" && callee.name.text === "now") {
1776
+ this.count("loop");
1777
+ return "(port.elapsedSeconds() * 1000)";
1778
+ }
1779
+ }
1780
+ if (!ts.isPropertyAccessExpression(callee)) return null;
1781
+ const owner = this.threeName(callee.expression);
1782
+ if (owner !== null) return this.rewriteThreeStatic(expr, owner, callee.name.text);
1783
+ const method = callee.name.text;
1784
+ const receiver = callee.expression;
1785
+ const path2 = this.pathOf(callee);
1786
+ if (path2 !== null && path2.names.length >= 2 && this.kindOf(path2.root) === "entity") return this.rewriteEntityPathCall(expr, path2.root, path2.names);
1787
+ if (path2 !== null && path2.names.length >= 2 && this.kindOf(path2.root) === "material") return this.rewriteMaterialPathCall(expr, path2.root, path2.names);
1788
+ const receiverKind = this.kindOf(receiver);
1789
+ if (method === "addEventListener") return this.rewriteListener(expr, receiver);
1790
+ if (receiverKind === "clock") return this.rewriteClock(expr, method);
1791
+ if (receiverKind === "gltfLoader") return this.rewriteGltfLoad(expr, method);
1792
+ if (receiverKind === "textureLoader" || ts.isNewExpression(receiver) && this.threeName(receiver.expression) === "TextureLoader") return this.rewriteTextureLoad(expr, method);
1793
+ if (receiverKind === "mixer") return this.rewriteMixer(expr, receiver, method);
1794
+ if (receiverKind === "action") return this.rewriteAction(expr, method);
1795
+ if (receiverKind === "controls") return this.rewriteControls(expr, receiver, method);
1796
+ if (receiverKind === "vector") return this.rewriteVector(expr, method);
1797
+ if (receiverKind === "entity") return this.rewriteEntityCall(expr, receiver, method);
1798
+ if (receiverKind === "material") return this.rewriteMaterialCall(expr, method);
1799
+ return null;
1800
+ }
1801
+ rewriteThreeStatic(expr, owner, method) {
1802
+ const args = expr.arguments.map((a) => this.emit(a));
1803
+ if (owner === "MathUtils") {
1804
+ if (THREE_MATH_HELPERS[method] !== void 0) {
1805
+ this.kitImports.add("MathUtils");
1806
+ this.count("math");
1807
+ return `MathUtils.${THREE_MATH_HELPERS[method]}(${args.join(", ")})`;
1808
+ }
1809
+ if (method === "randFloat" && args.length === 2) {
1810
+ this.count("math");
1811
+ return `game.random.range(${args[0]}, ${args[1]})`;
1812
+ }
1813
+ if (method === "randInt" && args.length === 2) {
1814
+ this.count("math");
1815
+ return `game.random.int(${args[0]}, ${args[1]})`;
1816
+ }
1817
+ if (method === "randFloatSpread" && args.length === 1) {
1818
+ this.count("math");
1819
+ return `game.random.range(-(${args[0]}) / 2, (${args[0]}) / 2)`;
1820
+ }
1821
+ }
1822
+ if (owner === "AnimationClip" && method === "findByName" && expr.arguments[1] !== void 0) {
1823
+ this.count("animation");
1824
+ return this.emit(expr.arguments[1]);
1825
+ }
1826
+ this.residue(expr, "unconverted", owner === "MathUtils" ? "math" : "scene", `${owner}.${method}`, "No automatic mapping; see the porting-threejs guide topic.");
1827
+ return null;
1828
+ }
1829
+ rewriteListener(expr, receiver) {
1830
+ const type = expr.arguments[0];
1831
+ const handler = expr.arguments[1];
1832
+ if (type === void 0 || handler === void 0 || !ts.isStringLiteralLike(type) || !this.isListenerTarget(receiver)) return null;
1833
+ const keyType = KEY_EVENTS[type.text];
1834
+ if (keyType !== void 0) {
1835
+ this.keyListeners++;
1836
+ this.count("input");
1837
+ this.kitTypeImports.add("PortKeyEvent");
1838
+ return `port.addKeyListener("${keyType}", ${this.emit(handler)})`;
1839
+ }
1840
+ const pointerType = POINTER_EVENTS[type.text];
1841
+ if (pointerType !== void 0) {
1842
+ this.count("input");
1843
+ this.touchHandled = true;
1844
+ return `port.addPointerListener("${pointerType}", ${this.emit(handler)})`;
1845
+ }
1846
+ this.residue(expr, "unconverted", "input", `${type.text} listener`, "Read game.input in fixedUpdate instead (pointerAt, look, wheel, gestures); see the input topic.");
1847
+ return null;
1848
+ }
1849
+ rewriteClock(expr, method) {
1850
+ this.count("loop");
1851
+ if (method === "getDelta") return "game.stepSeconds";
1852
+ if (method === "getElapsedTime") return "port.elapsedSeconds()";
1853
+ this.residue(expr, "unconverted", "loop", `Clock.${method}`, "Use game.stepSeconds and port.elapsedSeconds().");
1854
+ return null;
1855
+ }
1856
+ rewriteGltfLoad(expr, method) {
1857
+ const url = expr.arguments[0];
1858
+ if (url === void 0) return null;
1859
+ this.features.add("models");
1860
+ this.count("loader");
1861
+ if (method === "loadAsync") return `port.model(${this.assetPath(url)})`;
1862
+ if (method !== "load") {
1863
+ this.residue(expr, "unconverted", "loader", `GLTFLoader.${method}`, "Draco and meshopt are handled by the engine baker; drop loader configuration.");
1864
+ return null;
1865
+ }
1866
+ const onLoad = expr.arguments[1];
1867
+ const onError = expr.arguments[3];
1868
+ let out = `void port.model(${this.assetPath(url)})`;
1869
+ if (onLoad !== void 0) out = `void port.model(${this.assetPath(url)}).then(${this.emit(onLoad)})`;
1870
+ if (onError !== void 0) out += `.catch(${this.emit(onError)})`;
1871
+ return out;
1872
+ }
1873
+ rewriteTextureLoad(expr, method) {
1874
+ const url = expr.arguments[0];
1875
+ if (method !== "load" && method !== "loadAsync") return null;
1876
+ if (url === void 0 || expr.arguments.length > 1) {
1877
+ this.residue(expr, "unconverted", "loader", "TextureLoader callbacks", "Use port.texture(path), which returns a promise of the texture id.");
1878
+ return null;
1879
+ }
1880
+ this.features.add("images");
1881
+ this.count("loader");
1882
+ return `port.texture(${this.assetPath(url)})`;
1883
+ }
1884
+ rewriteMixer(expr, receiver, method) {
1885
+ if (method === "clipAction" && expr.arguments[0] !== void 0) {
1886
+ this.count("animation");
1887
+ return `port.clipAction(${this.emit(receiver)}, ${this.emit(expr.arguments[0])})`;
1888
+ }
1889
+ this.residue(expr, "unconverted", "animation", `AnimationMixer.${method}`, "Keep the PortClipAction handles and stop or fade them one by one.");
1890
+ return null;
1891
+ }
1892
+ rewriteAction(expr, method) {
1893
+ if (method === "setLoop" && expr.arguments[0] !== void 0) {
1894
+ const constant = this.threeName(expr.arguments[0]);
1895
+ const loop = constant === null ? void 0 : LOOP_MODES[constant];
1896
+ if (loop !== void 0 && ts.isPropertyAccessExpression(expr.expression)) {
1897
+ this.count("animation");
1898
+ return `${this.emit(expr.expression.expression)}.setLoop(${loop})`;
1899
+ }
1900
+ }
1901
+ if (ACTION_PASSTHROUGH.has(method)) {
1902
+ this.count("animation");
1903
+ return null;
1904
+ }
1905
+ this.residue(expr, "unconverted", "animation", `AnimationAction.${method}`, "PortClipAction supports play, stop, fadeIn, fadeOut, reset, setLoop, setEffectiveTimeScale and setEffectiveWeight.");
1906
+ return null;
1907
+ }
1908
+ rewriteControls(expr, receiver, method) {
1909
+ if (method === "update") {
1910
+ this.count("controls");
1911
+ return `${this.emit(receiver)}.update(game.stepSeconds)`;
1912
+ }
1913
+ if (method === "getObject") {
1914
+ const binding = this.bindingOf(receiver);
1915
+ if (binding?.camera) {
1916
+ this.count("controls");
1917
+ return this.emit(binding.camera);
1918
+ }
1919
+ }
1920
+ if (method === "moveForward" || method === "moveRight" || method === "dispose") {
1921
+ this.count("controls");
1922
+ return null;
1923
+ }
1924
+ this.residue(expr, "unconverted", "controls", `controls.${method}`, "Pointer lock and control events differ on phones; firstPerson(game, options) gives look and move on touch and mouse.");
1925
+ return null;
1926
+ }
1927
+ rewriteVector(expr, method) {
1928
+ const mapped = THREE_VECTOR_METHODS[method];
1929
+ if (mapped !== void 0 && ts.isPropertyAccessExpression(expr.expression)) {
1930
+ this.count("math");
1931
+ return `${this.emit(expr.expression.expression)}.${mapped}(${expr.arguments.map((a) => this.emitVectorArg(a)).join(", ")})`;
1932
+ }
1933
+ if (method === "clone" && ts.isPropertyAccessExpression(expr.expression)) {
1934
+ this.gameImports.add("Vec3");
1935
+ this.count("math");
1936
+ return `new Vec3().copy(${this.emit(expr.expression.expression)})`;
1937
+ }
1938
+ this.residue(expr, "unconverted", "math", `Vector3.${method}`, "Vec3 has set, copy, add, sub, scale, addScaled, dot, cross, length, normalize, lerp, distanceTo and friends.");
1939
+ return null;
1940
+ }
1941
+ emitVectorArg(arg) {
1942
+ if (ts.isPropertyAccessExpression(arg) && arg.name.text === "position" && this.kindOf(arg.expression) === "entity") {
1943
+ this.gameImports.add("Vec3");
1944
+ return `game.transforms.position(${this.emit(arg.expression)}, new Vec3())`;
1945
+ }
1946
+ return this.emit(arg);
1947
+ }
1948
+ rewriteEntityCall(expr, receiver, method) {
1949
+ const entity = this.emit(receiver);
1950
+ const args = expr.arguments;
1951
+ if (method === "lookAt") {
1952
+ this.count("object");
1953
+ if (args.length === 3) return `port.lookAt(${entity}, ${args.map((a) => this.emit(a)).join(", ")})`;
1954
+ const target = args[0];
1955
+ if (target !== void 0 && ts.isPropertyAccessExpression(target) && target.name.text === "position" && this.kindOf(target.expression) === "entity") return `port.lookAtEntity(${entity}, ${this.emit(target.expression)})`;
1956
+ if (target !== void 0 && this.kindOf(target) === "vector" && SIMPLE_OPERAND.test(target.getText(this.sf))) {
1957
+ const v = this.emit(target);
1958
+ return `port.lookAt(${entity}, ${v}.x, ${v}.y, ${v}.z)`;
1959
+ }
1960
+ }
1961
+ if (method === "add" || method === "remove") {
1962
+ this.count("object");
1963
+ if (args.length === 1) return `port.${method}(${entity}, ${this.emitEntity(args[0])})`;
1964
+ }
1965
+ this.residue(expr, "unconverted", "object", `Object3D.${method}`, "No automatic mapping; use game.transforms (setPosition, setRotationQuat, translate, setParent) or ThreePort helpers.");
1966
+ return null;
1967
+ }
1968
+ rewriteEntityPathCall(expr, root, names) {
1969
+ const entity = this.emit(root);
1970
+ const args = expr.arguments;
1971
+ const values = args.map((a) => this.emit(a));
1972
+ const [attribute, method] = names;
1973
+ if (names.length === 2 && (attribute === "position" || attribute === "scale")) {
1974
+ const setter = attribute === "position" ? "setPosition" : "setScale";
1975
+ if (method === "set" && args.length === 3) {
1976
+ this.count("object");
1977
+ return `game.transforms.${setter}(${entity}, ${values.join(", ")})`;
1978
+ }
1979
+ if (attribute === "scale" && method === "setScalar" && args.length === 1) {
1980
+ this.count("object");
1981
+ return `port.setScalar(${entity}, ${values[0]})`;
1982
+ }
1983
+ if (attribute === "position" && args.length === 1) {
1984
+ const other = args[0];
1985
+ const otherEntity = ts.isPropertyAccessExpression(other) && other.name.text === "position" && this.kindOf(other.expression) === "entity" ? this.emit(other.expression) : null;
1986
+ if (method === "copy" && otherEntity !== null) {
1987
+ this.count("object");
1988
+ return `port.copyPosition(${entity}, ${otherEntity})`;
1989
+ }
1990
+ if ((method === "distanceTo" || method === "distanceToSquared") && otherEntity !== null) {
1991
+ this.count("object");
1992
+ return `port.${method === "distanceTo" ? "distance" : "distanceSquared"}(${entity}, ${otherEntity})`;
1993
+ }
1994
+ if (method === "add" && this.kindOf(other) === "vector") {
1995
+ this.count("object");
1996
+ return `game.transforms.translate(${entity}, ${values[0]})`;
1997
+ }
1998
+ if (method === "copy" && this.kindOf(other) === "vector" && SIMPLE_OPERAND.test(values[0])) {
1999
+ this.count("object");
2000
+ return `game.transforms.setPosition(${entity}, ${values[0]}.x, ${values[0]}.y, ${values[0]}.z)`;
2001
+ }
2002
+ }
2003
+ }
2004
+ if (names.length === 2 && attribute === "rotation" && method === "set" && args.length >= 3) {
2005
+ this.count("object");
2006
+ return `port.setRotation(${entity}, ${values.slice(0, 3).join(", ")})`;
2007
+ }
2008
+ if (names.length === 3 && attribute === "material" && names[1] === "color" && (names[2] === "set" || names[2] === "setHex")) {
2009
+ const material = this.materialOf(root);
2010
+ const color = args[0] === void 0 ? null : this.colorExpr(args[0]);
2011
+ if (material !== null && color !== null) {
2012
+ this.count("material");
2013
+ return `game.graphics.setMaterialColor(${material}, ${color})`;
2014
+ }
2015
+ }
2016
+ this.residue(expr, "unconverted", "object", `Object3D.${names.join(".")}`, "No automatic mapping; use game.transforms or ThreePort helpers.");
2017
+ return null;
2018
+ }
2019
+ materialOf(root) {
2020
+ const binding = this.bindingOf(root);
2021
+ if (binding === null || binding.material === null) return null;
2022
+ const material = this.bindings.get(binding.material);
2023
+ return material === void 0 ? null : this.memberRef(binding.material, material.name);
2024
+ }
2025
+ rewriteMaterialCall(expr, method) {
2026
+ this.residue(expr, "unconverted", "material", `Material.${method}`, "Use game.graphics.setMaterialColor, setMaterialOpacity, setMaterialTexture or setMaterialState.");
2027
+ return null;
2028
+ }
2029
+ rewriteMaterialPathCall(expr, root, names) {
2030
+ const [property, method] = names;
2031
+ const arg = expr.arguments[0];
2032
+ if (property === "color" && (method === "set" || method === "setHex") && arg !== void 0) {
2033
+ const color = this.colorExpr(arg);
2034
+ if (color !== null) {
2035
+ this.count("material");
2036
+ return `game.graphics.setMaterialColor(${this.emit(root)}, ${color})`;
2037
+ }
2038
+ }
2039
+ this.residue(expr, "unconverted", "material", `Material.${names.join(".")}`, "Use game.graphics.setMaterialColor, setMaterialOpacity or setMaterialTexture.");
2040
+ return null;
2041
+ }
2042
+ rewriteBinary(expr) {
2043
+ const op = expr.operatorToken.kind;
2044
+ const compound = {
2045
+ [ts.SyntaxKind.EqualsToken]: "=",
2046
+ [ts.SyntaxKind.PlusEqualsToken]: "+",
2047
+ [ts.SyntaxKind.MinusEqualsToken]: "-",
2048
+ [ts.SyntaxKind.AsteriskEqualsToken]: "*",
2049
+ [ts.SyntaxKind.SlashEqualsToken]: "/"
2050
+ };
2051
+ const operator = compound[op];
2052
+ if (operator === void 0) return null;
2053
+ const path2 = this.pathOf(expr.left);
2054
+ if (path2 === null) return null;
2055
+ const rootKind = this.kindOf(path2.root);
2056
+ if (rootKind === "entity") return this.assignEntity(expr, path2.root, path2.names, operator);
2057
+ if (ts.isCallExpression(path2.root) && path2.names.length === 1 && HUD_PROPERTIES.has(path2.names[0]) && operator === "=") {
2058
+ const id = this.hudIdOf(path2.root);
2059
+ const slot = id === null ? void 0 : this.hudSlots.get(id);
2060
+ if (slot !== void 0) return this.assignHud(expr.right, `this.${slot}`);
2061
+ }
2062
+ if (rootKind === "hud" && path2.names.length === 1 && HUD_PROPERTIES.has(path2.names[0]) && operator === "=" && ts.isIdentifier(path2.root)) return this.assignHud(expr.right, this.emit(path2.root));
2063
+ if (rootKind === "material" && path2.names.length === 1 && path2.names[0] === "opacity" && operator === "=") {
2064
+ this.count("material");
2065
+ return `game.graphics.setMaterialOpacity(${this.emit(path2.root)}, ${this.emit(expr.right)})`;
2066
+ }
2067
+ if (rootKind === "controls" && path2.names.length === 1 && path2.names[0] === "isLocked") return null;
2068
+ return null;
2069
+ }
2070
+ assignHud(value, slot) {
2071
+ this.count("dom");
2072
+ const text = this.emit(value);
2073
+ return `game.ui.setText(${slot}, ${this.kindOf(value) === "string" ? text : `String(${text})`})`;
2074
+ }
2075
+ assignEntity(expr, root, names, operator) {
2076
+ const entity = this.emit(root);
2077
+ const value = this.emit(expr.right);
2078
+ const [attribute, axis] = names;
2079
+ const suffix = TRANSFORM_ATTRIBUTES[attribute];
2080
+ if (names.length === 2 && suffix !== void 0 && AXIS_NAMES[axis] !== void 0) {
2081
+ const axisName = AXIS_NAMES[axis];
2082
+ this.kitImports.add(axisName);
2083
+ this.count("object");
2084
+ if (operator === "=") return `port.set${suffix}Axis(${entity}, ${axisName}, ${value})`;
2085
+ if (operator === "+") return `port.add${suffix}Axis(${entity}, ${axisName}, ${value})`;
2086
+ if (operator === "-") return `port.add${suffix}Axis(${entity}, ${axisName}, -(${value}))`;
2087
+ return `port.set${suffix}Axis(${entity}, ${axisName}, port.${attribute}Axis(${entity}, ${axisName}) ${operator} (${value}))`;
2088
+ }
2089
+ if (names.length === 1 && attribute === "visible" && operator === "=") {
2090
+ this.count("object");
2091
+ return `port.setVisible(${entity}, ${value})`;
2092
+ }
2093
+ if (names.length === 2 && attribute === "material" && axis === "opacity" && operator === "=") {
2094
+ const material = this.materialOf(root);
2095
+ if (material !== null) {
2096
+ this.count("material");
2097
+ return `game.graphics.setMaterialOpacity(${material}, ${value})`;
2098
+ }
2099
+ }
2100
+ if (attribute === "castShadow" || attribute === "receiveShadow") {
2101
+ this.residue(expr, "unconverted", "object", `Object3D.${attribute} at run time`, "Shadow flags are set when the mesh is created: pass CAST_SHADOW | RECEIVE_SHADOW to port.mesh.");
2102
+ return null;
2103
+ }
2104
+ this.residue(expr, "unconverted", "object", `Object3D.${names.join(".")}`, attribute === "userData" ? "Keep per-object data in typed arrays indexed by entityIndex(entity) or in your own fields." : "No automatic mapping; use game.transforms or ThreePort helpers.");
2105
+ return null;
2106
+ }
2107
+ rewriteIncrement(expr) {
2108
+ if (expr.operator !== ts.SyntaxKind.PlusPlusToken && expr.operator !== ts.SyntaxKind.MinusMinusToken) return null;
2109
+ const path2 = this.pathOf(expr.operand);
2110
+ if (path2 === null || this.kindOf(path2.root) !== "entity" || path2.names.length !== 2) return null;
2111
+ const suffix = TRANSFORM_ATTRIBUTES[path2.names[0]];
2112
+ const axisName = AXIS_NAMES[path2.names[1]];
2113
+ if (suffix === void 0 || axisName === void 0) return null;
2114
+ this.kitImports.add(axisName);
2115
+ this.count("object");
2116
+ return `port.add${suffix}Axis(${this.emit(path2.root)}, ${axisName}, ${expr.operator === ts.SyntaxKind.PlusPlusToken ? "1" : "-1"})`;
2117
+ }
2118
+ rewriteProperty(expr) {
2119
+ const parent = expr.parent;
2120
+ this.flagEntityMember(expr);
2121
+ this.flagHudMember(expr);
2122
+ if (ts.isCallExpression(parent) && parent.expression === expr) return null;
2123
+ if (ts.isPropertyAccessExpression(parent) && parent.expression === expr) return null;
2124
+ if (ts.isBinaryExpression(parent) && parent.left === expr && parent.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && parent.operatorToken.kind <= ts.SyntaxKind.LastAssignment) return null;
2125
+ if (ts.isNewExpression(parent) && parent.expression === expr) return null;
2126
+ const member = this.threeMember(expr);
2127
+ if (member !== null) {
2128
+ this.count("math");
2129
+ if (member[0] === "MathUtils" && member[1] === "DEG2RAD") return "(Math.PI / 180)";
2130
+ if (member[0] === "MathUtils" && member[1] === "RAD2DEG") return "(180 / Math.PI)";
2131
+ this.residue(expr, "unconverted", "scene", `${member[0]}.${member[1]}`, "No automatic mapping; see the porting-threejs guide topic.");
2132
+ return null;
2133
+ }
2134
+ const constant = this.threeName(expr);
2135
+ if (constant !== null) {
2136
+ const side = THREE_SIDES[constant];
2137
+ if (side !== void 0) {
2138
+ this.count("material");
2139
+ return JSON.stringify(side);
2140
+ }
2141
+ this.residue(expr, "unconverted", "scene", constant, "No automatic mapping; see the porting-threejs guide topic.");
2142
+ return null;
2143
+ }
2144
+ const name = expr.name.text;
2145
+ const ownerKind = this.kindOf(expr.expression);
2146
+ if (ownerKind === "gltf" && name === "scene" && ts.isIdentifier(expr.expression)) {
2147
+ this.count("loader");
2148
+ return `${expr.expression.text}Scene`;
2149
+ }
2150
+ if (ownerKind === "gltf" && name === "animations") {
2151
+ this.count("animation");
2152
+ return `${this.emit(expr.expression)}.clipNames`;
2153
+ }
2154
+ if (ownerKind === "clock" && name === "elapsedTime") {
2155
+ this.count("loop");
2156
+ return "port.elapsedSeconds()";
2157
+ }
2158
+ if (ownerKind === "controls" && name === "isLocked") {
2159
+ this.count("controls");
2160
+ return `${this.emit(expr.expression)}.locked`;
2161
+ }
2162
+ if (ts.isPropertyAccessExpression(expr.expression) && this.kindOf(expr.expression.expression) === "entity") {
2163
+ const suffix = TRANSFORM_ATTRIBUTES[expr.expression.name.text];
2164
+ const axisName = AXIS_NAMES[name];
2165
+ if (suffix !== void 0 && axisName !== void 0) {
2166
+ this.kitImports.add(axisName);
2167
+ this.count("object");
2168
+ return `port.${expr.expression.name.text}Axis(${this.emit(expr.expression.expression)}, ${axisName})`;
2169
+ }
2170
+ }
2171
+ if (ts.isIdentifier(expr.expression) && (expr.expression.text === "window" || expr.expression.text === "document") && this.bindingOf(expr.expression) === null) {
2172
+ this.residue(expr, "unconverted", "dom", `${expr.expression.text}.${name}`, "The canvas profile has no page; use game.ui for text and the surface size the platform gives you.");
2173
+ }
2174
+ return null;
2175
+ }
2176
+ flagHudMember(expr) {
2177
+ if (HUD_PROPERTIES.has(expr.name.text) || !ts.isIdentifier(expr.expression) || this.bindingOf(expr.expression)?.kind !== "hud") return;
2178
+ this.residue(expr, "unconverted", "dom", `element.${expr.name.text}`, "HUD slots only carry text; draw other UI with game.ui panels (game.ui.show) or the engine game UI.");
2179
+ }
2180
+ flagEntityMember(expr) {
2181
+ if (this.kindOf(expr.expression) !== "entity") return;
2182
+ const name = expr.name.text;
2183
+ const parent = expr.parent;
2184
+ if (ts.isCallExpression(parent) && parent.expression === expr) return;
2185
+ const chained = ts.isPropertyAccessExpression(parent) && parent.expression === expr;
2186
+ if (TRANSFORM_ATTRIBUTES[name] !== void 0) {
2187
+ if (chained || ts.isCallExpression(parent) && parent.arguments.includes(expr)) return;
2188
+ this.residue(expr, "unconverted", "object", `Object3D.${name} as a value`, "Read axes with port.positionAxis or copy into a Vec3 with game.transforms.position(entity, out).");
2189
+ return;
2190
+ }
2191
+ if (HANDLED_ENTITY_MEMBERS.has(name)) return;
2192
+ this.residue(expr, "unconverted", "object", `Object3D.${name}`, name === "userData" ? "Keep per-object data in typed arrays indexed by entityIndex(entity) or in your own fields." : "No automatic mapping; use game.transforms or ThreePort helpers.");
2193
+ }
2194
+ };
2195
+
2196
+ // src/interactives/port/project.ts
2197
+ var PORT_REPORT_FILE = "port-report.json";
2198
+ var PORTED_SCENE_FILE = "src/scene.ts";
2199
+ var PORT_ASSET_PREFIX = "assets/core/";
2200
+ var MODULE_EXTENSIONS = [".js", ".mjs", ".ts", ".jsx", ".tsx"];
2201
+ var SKIPPED_DIRECTORIES = ["node_modules/", "vendor/", "dist/", "build/", ".git/"];
2202
+ var LOOP_MARKER = /requestAnimationFrame|setAnimationLoop/;
2203
+ var EXTENSION = /\.(m?js|jsx?|tsx?)$/;
2204
+ var GAME_SOURCE = /^src\/.*\.(ts|tsx|js|mjs)$/;
2205
+ function client(scene) {
2206
+ return `import type { Ctx } from "@cimplify/sdk/interactives";
2207
+ import { runCanvasGame } from "@cimplify/sdk/game/kit/canvas";
2208
+ import { ${scene} } from "./scene";
2209
+
2210
+ export default async function main(ctx: Ctx): Promise<void> {
2211
+ await runCanvasGame(ctx, { scene: new ${scene}(ctx.assets) });
2212
+ }
2213
+ `;
2214
+ }
2215
+ function conformance(scene) {
2216
+ return `import { createGame } from "@cimplify/sdk/game";
2217
+ import { platformBackend, PREPARED_PLATFORM } from "@cimplify/sdk/game/kit/canvas";
2218
+ import { ${scene} } from "./scene";
2219
+
2220
+ const SEED = 1;
2221
+ const STEPS_PER_FRAME = 1;
2222
+ const WARM_FRAMES = 30000;
2223
+
2224
+ export default async function createFrameHook(platform = PREPARED_PLATFORM): Promise<(frame: number) => void> {
2225
+ const game = await createGame({ backend: await platformBackend(platform), seed: SEED });
2226
+ const scene = new ${scene}();
2227
+ game.addScene(scene);
2228
+ game.start(scene.name);
2229
+ for (let i = 0; i < WARM_FRAMES; i++) game.advance(STEPS_PER_FRAME);
2230
+ return () => game.advance(STEPS_PER_FRAME);
2231
+ }
2232
+ `;
2233
+ }
2234
+ function isModule(path2) {
2235
+ return MODULE_EXTENSIONS.some((e) => path2.endsWith(e)) && !path2.endsWith(".d.ts") && !SKIPPED_DIRECTORIES.some((d) => path2.startsWith(d) || path2.includes(`/${d}`));
2236
+ }
2237
+ function lines(path2, content) {
2238
+ return { path: path2, lines: countSourceLines(content) };
2239
+ }
2240
+ function pickEntry(modules) {
2241
+ const three = modules.filter((m) => importsThree(m.content));
2242
+ return three.find((m) => LOOP_MARKER.test(m.content)) ?? three[0] ?? null;
2243
+ }
2244
+ function moduleResidue(file, index) {
2245
+ return {
2246
+ id: `M${index + 1}`,
2247
+ kind: "unconverted",
2248
+ area: "scene",
2249
+ construct: "second Three.js module",
2250
+ file: file.path,
2251
+ line: 1,
2252
+ column: 1,
2253
+ source: file.path,
2254
+ hint: "Only the entry module is converted. Fold this module into src/scene.ts or rewrite it against the game API, then import it relatively."
2255
+ };
2256
+ }
2257
+ function portThreeProject(name, inputs) {
2258
+ const modules = inputs.filter((f) => isModule(f.path));
2259
+ const entry = pickEntry(modules);
2260
+ if (entry === null) throw new Error("no module imports three; point cimplify port threejs at the folder that holds the game's JavaScript");
2261
+ const converted = new ThreeModuleConverter(entry.path, entry.content, { sceneName: name, assetPrefix: PORT_ASSET_PREFIX }).convert();
2262
+ const residue = [...converted.residue];
2263
+ if (!converted.loopFound) {
2264
+ residue.push({
2265
+ id: `L${residue.length + 1}`,
2266
+ kind: "unconverted",
2267
+ area: "loop",
2268
+ construct: "game loop",
2269
+ file: entry.path,
2270
+ line: 1,
2271
+ column: 1,
2272
+ source: entry.path,
2273
+ hint: "No requestAnimationFrame or setAnimationLoop loop was found; call the per-frame code from fixedUpdate in src/scene.ts."
2274
+ });
2275
+ }
2276
+ const extra = [];
2277
+ modules.filter((m) => m !== entry).forEach((m) => {
2278
+ const target = `src/${m.path.replace(EXTENSION, (ext) => ext.startsWith(".ts") ? ext : ".js")}`;
2279
+ extra.push({ path: target, content: m.content });
2280
+ if (importsThree(m.content)) residue.push(moduleResidue(m, residue.length));
2281
+ });
2282
+ const spec = {
2283
+ description: `${titleFromName(name)} ported from Three.js`,
2284
+ category: "games",
2285
+ profile: "canvas",
2286
+ game: true,
2287
+ capabilities: {},
2288
+ engineFeatures: converted.engineFeatures,
2289
+ files: [{ path: "src/client.ts", content: client(sceneClassName(name)) }, { path: PORTED_SCENE_FILE, content: converted.scene }, { path: "src/conformance.ts", content: conformance(sceneClassName(name)) }, ...extra]
2290
+ };
2291
+ const files = materialize(name, spec);
2292
+ const source = modules.map((m) => lines(m.path, m.content));
2293
+ const output = files.filter((f) => GAME_SOURCE.test(f.path)).map((f) => lines(f.path, f.content));
2294
+ const scene = output.find((f) => f.path === PORTED_SCENE_FILE);
2295
+ const report = {
2296
+ source,
2297
+ output,
2298
+ linesBefore: source.reduce((sum, f) => sum + f.lines, 0),
2299
+ linesAfter: output.reduce((sum, f) => sum + f.lines, 0),
2300
+ gameLinesAfter: scene?.lines ?? 0,
2301
+ converted: converted.converted,
2302
+ convertedByArea: converted.convertedByArea,
2303
+ residue: residue.map((r, i) => ({ ...r, id: `R${i + 1}` }))
2304
+ };
2305
+ const assets = converted.assets.map((from) => ({ from, to: `${PORT_ASSET_PREFIX}${from}` }));
2306
+ return { files: [...files, { path: PORT_REPORT_FILE, content: `${JSON.stringify(report, null, 2)}
2307
+ ` }], report, assets };
2308
+ }
2309
+
2310
+ // src/interactives/commands/port.ts
2311
+ var USAGE = "usage: cimplify port threejs <dir> [--out <dir>] [--name <name>] [--force] [--ai]";
2312
+ var SOURCES = ["threejs", "three"];
2313
+ var SKIPPED_DIRECTORIES2 = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "vendor", ".cimplify"]);
2314
+ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".jsx", ".ts", ".tsx", ".html", ".css", ".json"]);
2315
+ var MAX_SOURCE_BYTES = 2e6;
2316
+ var NAME_PATTERN = /[^a-z0-9-]+/g;
2317
+ var OUT_SUFFIX = "-cimplify";
2318
+ async function readSources(root) {
2319
+ const files = [];
2320
+ const walk = async (dir, rel) => {
2321
+ for (const entry of await promises.readdir(dir, { withFileTypes: true })) {
2322
+ const relPath = rel === "" ? entry.name : `${rel}/${entry.name}`;
2323
+ if (entry.isDirectory()) {
2324
+ if (!SKIPPED_DIRECTORIES2.has(entry.name)) await walk(path.join(dir, entry.name), relPath);
2325
+ continue;
2326
+ }
2327
+ if (!entry.isFile() || !TEXT_EXTENSIONS.has(path.extname(entry.name))) continue;
2328
+ const stat = await promises.stat(path.join(dir, entry.name));
2329
+ if (stat.size > MAX_SOURCE_BYTES) continue;
2330
+ files.push({ path: relPath, content: await promises.readFile(path.join(dir, entry.name), "utf8") });
2331
+ }
2332
+ };
2333
+ await walk(root, "");
2334
+ return files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
2335
+ }
2336
+ async function ensureEmpty(out, force) {
2337
+ if (!existsSync(out)) return;
2338
+ const entries = await promises.readdir(out);
2339
+ if (entries.length > 0 && !force) {
2340
+ throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, `${out} is not empty`, { remediation: "Choose another --out folder, or pass --force to overwrite the files the port writes." });
2341
+ }
2342
+ }
2343
+ async function writePortedProject(project, from, out) {
2344
+ const missing = [];
2345
+ for (const file of project.files) {
2346
+ const target = path.join(out, ...file.path.split("/"));
2347
+ await promises.mkdir(path.dirname(target), { recursive: true });
2348
+ await promises.writeFile(target, file.content);
2349
+ }
2350
+ for (const asset of project.assets) {
2351
+ const source = path.join(from, ...asset.from.split("/"));
2352
+ if (!existsSync(source)) {
2353
+ missing.push(asset.from);
2354
+ continue;
2355
+ }
2356
+ const target = path.join(out, ...asset.to.split("/"));
2357
+ await promises.mkdir(path.dirname(target), { recursive: true });
2358
+ await promises.copyFile(source, target);
2359
+ }
2360
+ return missing;
2361
+ }
2362
+ function summarize(report) {
2363
+ const unconverted = report.residue.filter((r) => r.kind === "unconverted").length;
2364
+ const advisory = report.residue.length - unconverted;
2365
+ info(` converted ${report.converted} Three.js constructs`);
2366
+ info(` lines: ${report.linesBefore} before, ${report.linesAfter} after (${report.gameLinesAfter} in src/scene.ts)`);
2367
+ info(` residue: ${unconverted} unconverted, ${advisory} advisory`);
2368
+ for (const item of report.residue) info(dim(` ${item.kind === "unconverted" ? "todo" : "note"} ${item.file}:${item.line} ${item.construct}: ${item.hint}`));
2369
+ }
2370
+ async function run(argv) {
2371
+ const args = parseArgs(argv);
2372
+ const [kind, dir] = args.positional;
2373
+ if (kind === void 0 || dir === void 0 || !SOURCES.includes(kind)) throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, USAGE);
2374
+ const from = path.resolve(process.cwd(), dir);
2375
+ if (!existsSync(from)) throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, `${from} does not exist`);
2376
+ const name = (flagString(args, "name") ?? path.basename(from)).toLowerCase().replace(NAME_PATTERN, "-").replace(/^-+|-+$/g, "") || "ported-game";
2377
+ const out = path.resolve(process.cwd(), flagString(args, "out") ?? `${name}${OUT_SUFFIX}`);
2378
+ await ensureEmpty(out, flagBool(args, "force"));
2379
+ step(`Porting the Three.js game in ${from}`);
2380
+ let project;
2381
+ try {
2382
+ project = portThreeProject(name, await readSources(from));
2383
+ } catch (error) {
2384
+ throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, error instanceof Error ? error.message : String(error));
2385
+ }
2386
+ const missing = await writePortedProject(project, from, out);
2387
+ for (const asset of missing) warn(`asset ${asset} was not found next to the game; copy it into ${out}/assets/core/`);
2388
+ success(`Wrote ${out}`);
2389
+ summarize(project.report);
2390
+ let ai = null;
2391
+ if (flagBool(args, "ai") && project.report.residue.length > 0) {
2392
+ step("Asking Reesa to finish the port");
2393
+ const outcome = await askAuthoring(out, threejsPortRequest(project.report), {
2394
+ model: modelFromEnv(),
2395
+ onEvent: (event) => {
2396
+ const line = describeEvent(event);
2397
+ if (line !== null) info(dim(` ${line}`));
2398
+ }
2399
+ });
2400
+ reportOutcome(outcome);
2401
+ ai = { changed: outcome.changed.length, diagnostics: outcome.diagnostics.length, end: outcome.end };
2402
+ } else if (project.report.residue.length > 0) {
2403
+ info(`Run cimplify ai in ${out} with the request from port-report.json, or rerun with --ai, to let Reesa finish the residue.`);
2404
+ }
2405
+ result({ out, report: project.report, missingAssets: missing, ai });
2406
+ }
2407
+
2408
+ export { run as default, writePortedProject };