unity-agentic-tools 0.2.0 → 0.3.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.
package/dist/index.js CHANGED
@@ -5,48 +5,202 @@ var __defProp = Object.defineProperty;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
8
13
  var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
- var __moduleCache = /* @__PURE__ */ new WeakMap;
20
33
  var __toCommonJS = (from) => {
21
- var entry = __moduleCache.get(from), desc;
34
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
22
35
  if (entry)
23
36
  return entry;
24
37
  entry = __defProp({}, "__esModule", { value: true });
25
- if (from && typeof from === "object" || typeof from === "function")
26
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
27
- get: () => from[key],
28
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
29
- }));
38
+ if (from && typeof from === "object" || typeof from === "function") {
39
+ for (var key of __getOwnPropNames(from))
40
+ if (!__hasOwnProp.call(entry, key))
41
+ __defProp(entry, key, {
42
+ get: __accessProp.bind(from, key),
43
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
44
+ });
45
+ }
30
46
  __moduleCache.set(from, entry);
31
47
  return entry;
32
48
  };
49
+ var __moduleCache;
33
50
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
51
+ var __returnValue = (v) => v;
52
+ function __exportSetter(name, newValue) {
53
+ this[name] = __returnValue.bind(null, newValue);
54
+ }
34
55
  var __export = (target, all) => {
35
56
  for (var name in all)
36
57
  __defProp(target, name, {
37
58
  get: all[name],
38
59
  enumerable: true,
39
60
  configurable: true,
40
- set: (newValue) => all[name] = () => newValue
61
+ set: __exportSetter.bind(all, name)
41
62
  });
42
63
  };
64
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
65
+
66
+ // src/scanner.ts
67
+ var exports_scanner = {};
68
+ __export(exports_scanner, {
69
+ isNativeModuleAvailable: () => isNativeModuleAvailable,
70
+ getNativeWalkProjectFiles: () => getNativeWalkProjectFiles,
71
+ getNativeModuleError: () => getNativeModuleError,
72
+ getNativeGrepProject: () => getNativeGrepProject,
73
+ getNativeExtractSerializedFields: () => getNativeExtractSerializedFields,
74
+ getNativeExtractDllTypes: () => getNativeExtractDllTypes,
75
+ getNativeExtractDllFields: () => getNativeExtractDllFields,
76
+ getNativeExtractCsharpTypes: () => getNativeExtractCsharpTypes,
77
+ getNativeBuildTypeRegistry: () => getNativeBuildTypeRegistry,
78
+ getNativeBuildPackageGuidCache: () => getNativeBuildPackageGuidCache,
79
+ getNativeBuildGuidCache: () => getNativeBuildGuidCache,
80
+ UnityScanner: () => UnityScanner
81
+ });
82
+ function isNativeModuleAvailable() {
83
+ return RustScanner !== null;
84
+ }
85
+ function getNativeModuleError() {
86
+ return nativeModuleError;
87
+ }
88
+
89
+ class UnityScanner {
90
+ scanner;
91
+ constructor() {
92
+ if (!RustScanner) {
93
+ throw new Error(nativeModuleError || "Native module not available");
94
+ }
95
+ this.scanner = new RustScanner;
96
+ }
97
+ setProjectRoot(path) {
98
+ this.scanner.setProjectRoot(path);
99
+ }
100
+ scan_scene_minimal(file) {
101
+ return this.scanner.scanSceneMinimal(file);
102
+ }
103
+ scan_scene_with_components(file, options) {
104
+ return this.scanner.scanSceneWithComponents(file, options);
105
+ }
106
+ scan_scene_metadata(file) {
107
+ return this.scanner.scanSceneMetadata(file);
108
+ }
109
+ find_by_name(file, pattern, fuzzy = true) {
110
+ return this.scanner.findByName(file, pattern, fuzzy);
111
+ }
112
+ inspect(options) {
113
+ return this.scanner.inspect({
114
+ file: options.file,
115
+ identifier: options.identifier,
116
+ includeProperties: options.include_properties,
117
+ verbose: options.verbose
118
+ });
119
+ }
120
+ inspect_all(file, include_properties = false, verbose = false) {
121
+ return this.scanner.inspectAll(file, include_properties, verbose);
122
+ }
123
+ inspect_all_paginated(options) {
124
+ return this.scanner.inspectAllPaginated({
125
+ file: options.file,
126
+ includeProperties: options.include_properties,
127
+ verbose: options.verbose,
128
+ pageSize: options.page_size,
129
+ cursor: options.cursor,
130
+ maxDepth: options.max_depth,
131
+ filterComponent: options.filter_component
132
+ });
133
+ }
134
+ read_asset(file, decode_mesh) {
135
+ return this.scanner.readAsset(file, decode_mesh);
136
+ }
137
+ }
138
+ function getNativeWalkProjectFiles() {
139
+ return nativeWalkProjectFiles;
140
+ }
141
+ function getNativeGrepProject() {
142
+ return nativeGrepProject;
143
+ }
144
+ function getNativeBuildGuidCache() {
145
+ return nativeBuildGuidCache;
146
+ }
147
+ function getNativeExtractCsharpTypes() {
148
+ return nativeExtractCsharpTypes;
149
+ }
150
+ function getNativeBuildTypeRegistry() {
151
+ return nativeBuildTypeRegistry;
152
+ }
153
+ function getNativeExtractDllTypes() {
154
+ return nativeExtractDllTypes;
155
+ }
156
+ function getNativeBuildPackageGuidCache() {
157
+ return nativeBuildPackageGuidCache;
158
+ }
159
+ function getNativeExtractSerializedFields() {
160
+ return nativeExtractSerializedFields;
161
+ }
162
+ function getNativeExtractDllFields() {
163
+ return nativeExtractDllFields;
164
+ }
165
+ var import_module, import_fs, import_path, __dirname = "/home/runner/work/unity-agentic-tools/unity-agentic-tools/unity-agentic-tools/src", RustScanner = null, nativeModuleError = null, nativeWalkProjectFiles = null, nativeGrepProject = null, nativeBuildGuidCache = null, nativeExtractCsharpTypes = null, nativeBuildTypeRegistry = null, nativeExtractDllTypes = null, nativeBuildPackageGuidCache = null, nativeExtractSerializedFields = null, nativeExtractDllFields = null;
166
+ var init_scanner = __esm(() => {
167
+ import_module = require("module");
168
+ import_fs = require("fs");
169
+ import_path = require("path");
170
+ try {
171
+ const scriptDir = process.argv[1] ? import_path.dirname(import_path.resolve(process.argv[1])) : __dirname;
172
+ let rustModule;
173
+ const nativeLoaderPath = import_path.join(scriptDir, "..", "native", "index.js");
174
+ if (import_fs.existsSync(nativeLoaderPath)) {
175
+ const nativeRequire = import_module.createRequire(nativeLoaderPath);
176
+ rustModule = nativeRequire(nativeLoaderPath);
177
+ } else {
178
+ const wsRequire = import_module.createRequire(import_path.join(scriptDir, "_"));
179
+ rustModule = wsRequire("unity-file-tools");
180
+ }
181
+ RustScanner = rustModule.Scanner;
182
+ nativeWalkProjectFiles = rustModule.walkProjectFiles || null;
183
+ nativeGrepProject = rustModule.grepProject || null;
184
+ nativeBuildGuidCache = rustModule.buildGuidCache || null;
185
+ nativeExtractCsharpTypes = rustModule.extractCsharpTypes || null;
186
+ nativeBuildTypeRegistry = rustModule.buildTypeRegistry || null;
187
+ nativeExtractDllTypes = rustModule.extractDllTypes || null;
188
+ nativeBuildPackageGuidCache = rustModule.buildPackageGuidCache || null;
189
+ nativeExtractSerializedFields = rustModule.extractSerializedFields || null;
190
+ nativeExtractDllFields = rustModule.extractDllFields || null;
191
+ } catch (err) {
192
+ nativeModuleError = `Failed to load native Rust module.
193
+ ` + `Run: bun install (in the project root)
194
+ ` + `Original error: ${err.message}`;
195
+ }
196
+ });
43
197
 
44
198
  // package.json
45
199
  var require_package = __commonJS((exports2, module2) => {
46
200
  module2.exports = {
47
201
  name: "unity-agentic-tools",
48
202
  packageManager: "bun@latest",
49
- version: "0.2.0",
203
+ version: "0.3.0",
50
204
  description: "Fast, token-efficient Unity YAML parser for AI agents",
51
205
  exports: {
52
206
  ".": {
@@ -111,126 +265,72 @@ var require_package = __commonJS((exports2, module2) => {
111
265
  });
112
266
 
113
267
  // src/index.ts
268
+ init_scanner();
114
269
  var exports_src = {};
115
270
  __export(exports_src, {
271
+ yaml_default_for_type: () => yaml_default_for_type,
116
272
  walk_project_files: () => walk_project_files,
273
+ validateUnityYAML: () => validateUnityYAML,
274
+ unpackPrefab: () => unpackPrefab,
117
275
  setup: () => setup,
118
276
  search_project: () => search_project,
277
+ resolve_script_with_fields: () => resolve_script_with_fields,
278
+ reparentGameObject: () => reparentGameObject,
279
+ removeRemovedGameObject: () => removeRemovedGameObject,
280
+ removeRemovedComponent: () => removeRemovedComponent,
281
+ removePrefabOverride: () => removePrefabOverride,
282
+ removeComponent: () => removeComponent,
119
283
  read_settings: () => read_settings,
120
284
  isNativeModuleAvailable: () => isNativeModuleAvailable,
121
285
  grep_project: () => grep_project,
122
286
  getNativeModuleError: () => getNativeModuleError,
287
+ getNativeExtractDllTypes: () => getNativeExtractDllTypes,
288
+ getNativeExtractCsharpTypes: () => getNativeExtractCsharpTypes,
289
+ getNativeBuildTypeRegistry: () => getNativeBuildTypeRegistry,
290
+ generate_field_yaml: () => generate_field_yaml,
123
291
  generateGuid: () => generateGuid,
292
+ find_unity_project_root: () => find_unity_project_root,
124
293
  edit_tag: () => edit_tag,
125
294
  edit_sorting_layer: () => edit_sorting_layer,
126
295
  edit_settings: () => edit_settings,
127
296
  edit_layer: () => edit_layer,
297
+ editTransform: () => editTransform,
298
+ editProperty: () => editProperty,
299
+ editPrefabOverride: () => editPrefabOverride,
300
+ editComponentByFileId: () => editComponentByFileId,
301
+ editArray: () => editArray,
302
+ duplicateGameObject: () => duplicateGameObject,
303
+ deletePrefabInstance: () => deletePrefabInstance,
304
+ deleteGameObject: () => deleteGameObject,
305
+ createScriptableObject: () => createScriptableObject,
128
306
  createScene: () => createScene,
307
+ createPrefabVariant: () => createPrefabVariant,
308
+ createMetaFile: () => createMetaFile,
309
+ createGameObject: () => createGameObject,
310
+ copyComponent: () => copyComponent,
129
311
  cleanup: () => cleanup,
312
+ batchEditProperties: () => batchEditProperties,
313
+ batchEditComponentProperties: () => batchEditComponentProperties,
130
314
  atomicWrite: () => atomicWrite,
131
- UnityScanner: () => UnityScanner
315
+ addRemovedGameObject: () => addRemovedGameObject,
316
+ addRemovedComponent: () => addRemovedComponent,
317
+ addComponent: () => addComponent,
318
+ UnityScanner: () => UnityScanner,
319
+ UnityDocument: () => UnityDocument,
320
+ UnityBlock: () => UnityBlock
132
321
  });
133
322
  module.exports = __toCommonJS(exports_src);
134
323
 
135
- // src/scanner.ts
136
- var import_module = require("module");
137
- var import_fs = require("fs");
138
- var import_path = require("path");
139
- var __dirname = "/home/runner/work/unity-agentic-tools/unity-agentic-tools/unity-agentic-tools/src";
140
- var RustScanner = null;
141
- var nativeModuleError = null;
142
- var nativeWalkProjectFiles = null;
143
- var nativeGrepProject = null;
144
- var nativeBuildGuidCache = null;
145
- try {
146
- const scriptDir = process.argv[1] ? import_path.dirname(import_path.resolve(process.argv[1])) : __dirname;
147
- let rustModule;
148
- const nativeLoaderPath = import_path.join(scriptDir, "..", "native", "index.js");
149
- if (import_fs.existsSync(nativeLoaderPath)) {
150
- const nativeRequire = import_module.createRequire(nativeLoaderPath);
151
- rustModule = nativeRequire(nativeLoaderPath);
152
- } else {
153
- const wsRequire = import_module.createRequire(import_path.join(scriptDir, "_"));
154
- rustModule = wsRequire("unity-file-tools");
155
- }
156
- RustScanner = rustModule.Scanner;
157
- nativeWalkProjectFiles = rustModule.walkProjectFiles || null;
158
- nativeGrepProject = rustModule.grepProject || null;
159
- nativeBuildGuidCache = rustModule.buildGuidCache || null;
160
- } catch (err) {
161
- nativeModuleError = `Failed to load native Rust module.
162
- ` + `Run: bun install (in the project root)
163
- ` + `Original error: ${err.message}`;
164
- }
165
- function isNativeModuleAvailable() {
166
- return RustScanner !== null;
167
- }
168
- function getNativeModuleError() {
169
- return nativeModuleError;
170
- }
171
-
172
- class UnityScanner {
173
- scanner;
174
- constructor() {
175
- if (!RustScanner) {
176
- throw new Error(nativeModuleError || "Native module not available");
177
- }
178
- this.scanner = new RustScanner;
179
- }
180
- setProjectRoot(path) {
181
- this.scanner.setProjectRoot(path);
182
- }
183
- scan_scene_minimal(file) {
184
- return this.scanner.scanSceneMinimal(file);
185
- }
186
- scan_scene_with_components(file, options) {
187
- return this.scanner.scanSceneWithComponents(file, options);
188
- }
189
- find_by_name(file, pattern, fuzzy = true) {
190
- return this.scanner.findByName(file, pattern, fuzzy);
191
- }
192
- inspect(options) {
193
- return this.scanner.inspect({
194
- file: options.file,
195
- identifier: options.identifier,
196
- includeProperties: options.include_properties,
197
- verbose: options.verbose
198
- });
199
- }
200
- inspect_all(file, include_properties = false, verbose = false) {
201
- return this.scanner.inspectAll(file, include_properties, verbose);
202
- }
203
- inspect_all_paginated(options) {
204
- return this.scanner.inspectAllPaginated({
205
- file: options.file,
206
- includeProperties: options.include_properties,
207
- verbose: options.verbose,
208
- pageSize: options.page_size,
209
- cursor: options.cursor,
210
- maxDepth: options.max_depth,
211
- filterComponent: options.filter_component
212
- });
213
- }
214
- read_asset(file) {
215
- return this.scanner.readAsset(file);
216
- }
217
- }
218
- function getNativeWalkProjectFiles() {
219
- return nativeWalkProjectFiles;
220
- }
221
- function getNativeGrepProject() {
222
- return nativeGrepProject;
223
- }
224
- function getNativeBuildGuidCache() {
225
- return nativeBuildGuidCache;
226
- }
227
324
  // src/setup.ts
325
+ init_scanner();
228
326
  var import_fs2 = require("fs");
229
327
  var import_path2 = require("path");
230
328
  var VERSION = require_package().version;
231
329
  var CONFIG_DIR = ".unity-agentic";
232
330
  var CONFIG_FILE = "config.json";
233
331
  var GUID_CACHE_FILE = "guid-cache.json";
332
+ var PACKAGE_CACHE_FILE = "package-cache.json";
333
+ var TYPE_REGISTRY_FILE = "type-registry.json";
234
334
  var DOC_INDEX_FILE = "doc-index.json";
235
335
  function setup(options = {}) {
236
336
  const projectPath = import_path2.resolve(options.project || process.cwd());
@@ -241,6 +341,8 @@ function setup(options = {}) {
241
341
  project_path: projectPath,
242
342
  config_path: "",
243
343
  guid_cache_created: false,
344
+ package_cache_created: false,
345
+ type_registry_created: false,
244
346
  doc_index_created: false,
245
347
  error: `Not a Unity project: Assets folder not found at ${assetsPath}`
246
348
  };
@@ -259,6 +361,30 @@ function setup(options = {}) {
259
361
  const guidCache = buildGuidCache(projectPath);
260
362
  const guidCachePath = import_path2.join(configPath, GUID_CACHE_FILE);
261
363
  import_fs2.writeFileSync(guidCachePath, JSON.stringify(guidCache, null, 2));
364
+ let packageCacheCreated = false;
365
+ let packageGuidCount;
366
+ const nativePkgBuild = getNativeBuildPackageGuidCache();
367
+ if (nativePkgBuild) {
368
+ try {
369
+ const packageCache = nativePkgBuild(projectPath);
370
+ const packageCachePath = import_path2.join(configPath, PACKAGE_CACHE_FILE);
371
+ import_fs2.writeFileSync(packageCachePath, JSON.stringify(packageCache, null, 2));
372
+ packageCacheCreated = true;
373
+ packageGuidCount = Object.keys(packageCache).length;
374
+ } catch {}
375
+ }
376
+ let typeRegistryCreated = false;
377
+ let typeCount;
378
+ const nativeRegistryBuild = getNativeBuildTypeRegistry();
379
+ if (nativeRegistryBuild) {
380
+ try {
381
+ const types = nativeRegistryBuild(projectPath, true, true);
382
+ const registryPath = import_path2.join(configPath, TYPE_REGISTRY_FILE);
383
+ import_fs2.writeFileSync(registryPath, JSON.stringify(types, null, 2));
384
+ typeRegistryCreated = true;
385
+ typeCount = types.length;
386
+ } catch {}
387
+ }
262
388
  let docIndexCreated = false;
263
389
  if (options.indexDocs) {
264
390
  const docIndex = { chunks: {}, last_updated: Date.now() };
@@ -270,8 +396,12 @@ function setup(options = {}) {
270
396
  project_path: projectPath,
271
397
  config_path: configPath,
272
398
  guid_cache_created: true,
399
+ package_cache_created: packageCacheCreated,
400
+ type_registry_created: typeRegistryCreated,
273
401
  doc_index_created: docIndexCreated,
274
- guid_count: Object.keys(guidCache).length
402
+ guid_count: Object.keys(guidCache).length,
403
+ package_guid_count: packageGuidCount,
404
+ type_count: typeCount
275
405
  };
276
406
  }
277
407
  function buildGuidCache(projectRoot) {
@@ -395,7 +525,36 @@ var path = __toESM(require("path"));
395
525
 
396
526
  // src/utils.ts
397
527
  var import_fs4 = require("fs");
528
+ var import_path4 = require("path");
529
+ function find_unity_project_root(startDir) {
530
+ let dir = import_path4.resolve(startDir || process.cwd());
531
+ const root = import_path4.resolve("/");
532
+ while (dir !== root) {
533
+ if (import_fs4.existsSync(import_path4.join(dir, ".unity-agentic")))
534
+ return dir;
535
+ if (import_fs4.existsSync(import_path4.join(dir, "Assets")) && import_fs4.existsSync(import_path4.join(dir, "ProjectSettings")))
536
+ return dir;
537
+ if (import_fs4.existsSync(import_path4.join(dir, "Assets")))
538
+ return dir;
539
+ const parent = import_path4.dirname(dir);
540
+ if (parent === dir)
541
+ break;
542
+ dir = parent;
543
+ }
544
+ return null;
545
+ }
398
546
  function atomicWrite(filePath, content) {
547
+ if (import_fs4.existsSync(filePath)) {
548
+ try {
549
+ import_fs4.accessSync(filePath, import_fs4.constants.W_OK);
550
+ } catch {
551
+ return {
552
+ success: false,
553
+ file_path: filePath,
554
+ error: `Permission denied: ${filePath} is not writable`
555
+ };
556
+ }
557
+ }
399
558
  const tmpPath = `${filePath}.tmp`;
400
559
  try {
401
560
  import_fs4.writeFileSync(tmpPath, content, "utf-8");
@@ -428,6 +587,9 @@ function atomicWrite(filePath, content) {
428
587
  };
429
588
  }
430
589
  }
590
+ function is_match_all(pattern) {
591
+ return pattern === "*" || pattern === "." || pattern === "**" || pattern === ".*";
592
+ }
431
593
  function glob_match(pattern, text) {
432
594
  if (!pattern.includes("*") && !pattern.includes("?")) {
433
595
  return pattern.toLowerCase() === text.toLowerCase();
@@ -472,8 +634,18 @@ function validate_file_path(file_path, operation) {
472
634
  if (!isAbsolute && (normalized.includes("/../") || normalized.startsWith("../"))) {
473
635
  return "Path traversal (..) is not allowed in relative paths for security reasons.";
474
636
  }
475
- if (operation === "write" && !isAbsolute && normalized.startsWith("Packages/")) {
476
- return "Cannot write to Packages/ directory (read-only in Unity).";
637
+ if (operation === "write") {
638
+ if (normalized.includes("/Library/PackageCache/") || normalized.startsWith("Library/PackageCache/")) {
639
+ return "Cannot write to Library/PackageCache/ (immutable package cache).";
640
+ }
641
+ const segments = normalized.split("/");
642
+ const pkgIdx = segments.indexOf("Packages");
643
+ if (pkgIdx >= 0) {
644
+ const assetsIdx = segments.indexOf("Assets");
645
+ if (assetsIdx < 0 || assetsIdx > pkgIdx) {
646
+ return "Cannot write to Packages/ directory (read-only in Unity).";
647
+ }
648
+ }
477
649
  }
478
650
  return null;
479
651
  }
@@ -507,7 +679,10 @@ var SETTING_ALIASES = {
507
679
  project: "ProjectSettings",
508
680
  projectsettings: "ProjectSettings",
509
681
  navmesh: "NavMeshAreas",
510
- navmeshareas: "NavMeshAreas"
682
+ navmeshareas: "NavMeshAreas",
683
+ build: "EditorBuildSettings",
684
+ editorbuild: "EditorBuildSettings",
685
+ editorbuildsettings: "EditorBuildSettings"
511
686
  };
512
687
  function resolve_setting_name(setting) {
513
688
  const lower = setting.toLowerCase();
@@ -627,6 +802,36 @@ function parse_time_manager(content) {
627
802
  max_particle_timestep: get_float("Maximum Particle Timestep")
628
803
  };
629
804
  }
805
+ function parse_input_manager(content) {
806
+ const axes = [];
807
+ const entries = content.split(/\n - serializedVersion:/);
808
+ for (let i = 1;i < entries.length; i++) {
809
+ const block = entries[i];
810
+ const field = (key) => {
811
+ const re = new RegExp(`^\\s*${key}:[ \\t]*(.*)$`, "m");
812
+ const m = re.exec(block);
813
+ return m ? m[1].trim() : "";
814
+ };
815
+ axes.push({
816
+ name: field("m_Name"),
817
+ descriptive_name: field("descriptiveName"),
818
+ descriptive_negative_name: field("descriptiveNegativeName"),
819
+ negative_button: field("negativeButton"),
820
+ positive_button: field("positiveButton"),
821
+ alt_negative_button: field("altNegativeButton"),
822
+ alt_positive_button: field("altPositiveButton"),
823
+ gravity: parseFloat(field("gravity") || "0"),
824
+ dead: parseFloat(field("dead") || "0"),
825
+ sensitivity: parseFloat(field("sensitivity") || "0"),
826
+ snap: field("snap") === "1",
827
+ invert: field("invert") === "1",
828
+ type: parseInt(field("type") || "0", 10),
829
+ axis: parseInt(field("axis") || "0", 10),
830
+ joy_num: parseInt(field("joyNum") || "0", 10)
831
+ });
832
+ }
833
+ return { axes };
834
+ }
630
835
  function parse_generic_asset(content) {
631
836
  const result = {};
632
837
  const lines = content.split(`
@@ -683,6 +888,9 @@ function read_settings(options) {
683
888
  case "TimeManager":
684
889
  data = parse_time_manager(content);
685
890
  break;
891
+ case "InputManager":
892
+ data = parse_input_manager(content);
893
+ break;
686
894
  default:
687
895
  data = parse_generic_asset(content);
688
896
  break;
@@ -697,6 +905,14 @@ function read_settings(options) {
697
905
  }
698
906
  function edit_settings(options) {
699
907
  const { project_path, setting, property, value } = options;
908
+ if (value === undefined || value === null) {
909
+ return {
910
+ success: false,
911
+ project_path,
912
+ setting,
913
+ error: "Value cannot be empty. Provide a valid value with --value."
914
+ };
915
+ }
700
916
  const file_path = resolve_setting_path(project_path, setting);
701
917
  if (!import_fs5.existsSync(file_path)) {
702
918
  return {
@@ -730,17 +946,23 @@ function edit_settings(options) {
730
946
  if (pascalPattern.test(content)) {
731
947
  content = content.replace(pascalPattern, `$1${value}`);
732
948
  } else {
733
- const spacedName = property.replace(/_/g, " ").replace(/(^| )([a-z])/g, (_, sp, c) => sp + c.toUpperCase());
734
- const spacedPattern = new RegExp(`(^\\s*${spacedName}:\\s*)(.*)$`, "m");
735
- if (spacedPattern.test(content)) {
736
- content = content.replace(spacedPattern, `$1${value}`);
949
+ const camelSpaced = property.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/(^| )([a-z])/g, (_, sp, c) => sp + c.toUpperCase());
950
+ const camelSpacedPattern = new RegExp(`(^\\s*${camelSpaced}:\\s*)(.*)$`, "m");
951
+ if (camelSpacedPattern.test(content)) {
952
+ content = content.replace(camelSpacedPattern, `$1${value}`);
737
953
  } else {
738
- return {
739
- success: false,
740
- project_path,
741
- setting,
742
- error: `Property "${property}" not found in ${setting}`
743
- };
954
+ const spacedName = property.replace(/_/g, " ").replace(/(^| )([a-z])/g, (_, sp, c) => sp + c.toUpperCase());
955
+ const spacedPattern = new RegExp(`(^\\s*${spacedName}:\\s*)(.*)$`, "m");
956
+ if (spacedPattern.test(content)) {
957
+ content = content.replace(spacedPattern, `$1${value}`);
958
+ } else {
959
+ return {
960
+ success: false,
961
+ project_path,
962
+ setting,
963
+ error: `Property "${property}" not found in ${setting}`
964
+ };
965
+ }
744
966
  }
745
967
  }
746
968
  }
@@ -992,7 +1214,8 @@ function edit_sorting_layer(options) {
992
1214
  `;
993
1215
  const sortingEnd = content.match(/(m_SortingLayers:\s*\n(?:\s+-\s+name:[\s\S]*?(?=\n[^\s]|\n*$)))/);
994
1216
  if (sortingEnd) {
995
- content = content.replace(sortingEnd[1], sortingEnd[1] + newEntry);
1217
+ content = content.replace(sortingEnd[1], sortingEnd[1] + `
1218
+ ` + newEntry);
996
1219
  } else {
997
1220
  content = content.trimEnd() + `
998
1221
  ` + newEntry;
@@ -1029,6 +1252,7 @@ function edit_sorting_layer(options) {
1029
1252
  };
1030
1253
  }
1031
1254
  // src/project-search.ts
1255
+ init_scanner();
1032
1256
  var import_fs6 = require("fs");
1033
1257
  var path2 = __toESM(require("path"));
1034
1258
  var BINARY_EXTENSIONS = new Set([
@@ -1119,6 +1343,8 @@ function walk_project_files_js(project_path, extensions, exclude_dirs) {
1119
1343
  const assetsDir = path2.join(project_path, "Assets");
1120
1344
  if (import_fs6.existsSync(assetsDir)) {
1121
1345
  walk(assetsDir);
1346
+ } else {
1347
+ walk(project_path);
1122
1348
  }
1123
1349
  if (extSet.has(".asset")) {
1124
1350
  const settingsDir = path2.join(project_path, "ProjectSettings");
@@ -1175,6 +1401,59 @@ function search_project(options) {
1175
1401
  error: `Project path not found: ${project_path}`
1176
1402
  };
1177
1403
  }
1404
+ const ASSET_TYPE_EXTENSIONS = {
1405
+ mat: [".mat"],
1406
+ anim: [".anim"],
1407
+ controller: [".controller"],
1408
+ asset: [".asset"]
1409
+ };
1410
+ const isAssetType = file_type in ASSET_TYPE_EXTENSIONS;
1411
+ if (isAssetType) {
1412
+ const extensions2 = ASSET_TYPE_EXTENSIONS[file_type];
1413
+ const files2 = walk_project_files(project_path, extensions2);
1414
+ const matches2 = [];
1415
+ for (const file of files2) {
1416
+ const relPath = path2.relative(project_path, file);
1417
+ const fileName = path2.basename(file, path2.extname(file));
1418
+ if (name) {
1419
+ const nameLower = name.toLowerCase();
1420
+ const hasWildcard = name.includes("*") || name.includes("?");
1421
+ if (hasWildcard) {
1422
+ if (!glob_match(name, fileName))
1423
+ continue;
1424
+ } else if (exact) {
1425
+ if (fileName !== name)
1426
+ continue;
1427
+ } else {
1428
+ if (!fileName.toLowerCase().includes(nameLower))
1429
+ continue;
1430
+ }
1431
+ }
1432
+ let display_name = fileName;
1433
+ try {
1434
+ const content = import_fs6.readFileSync(file, "utf-8");
1435
+ const nameMatch = /^\s*m_Name:\s*(.+)$/m.exec(content.slice(0, 2000));
1436
+ if (nameMatch)
1437
+ display_name = nameMatch[1].trim();
1438
+ } catch {}
1439
+ matches2.push({
1440
+ file: relPath,
1441
+ game_object: display_name,
1442
+ file_id: "0"
1443
+ });
1444
+ if (max_matches !== undefined && matches2.length >= max_matches)
1445
+ break;
1446
+ }
1447
+ return {
1448
+ success: true,
1449
+ project_path,
1450
+ total_files_scanned: files2.length,
1451
+ total_matches: matches2.length,
1452
+ cursor: 0,
1453
+ truncated: max_matches !== undefined && matches2.length >= max_matches,
1454
+ matches: matches2
1455
+ };
1456
+ }
1178
1457
  if (!isNativeModuleAvailable()) {
1179
1458
  return {
1180
1459
  success: false,
@@ -1198,12 +1477,30 @@ function search_project(options) {
1198
1477
  for (const file of files) {
1199
1478
  try {
1200
1479
  let gameObjects;
1201
- const needFullData = !!(component || tag || layer !== undefined);
1202
- if (name && !needFullData) {
1480
+ const needComponents = !!component;
1481
+ const needMetadata = !!(tag || layer !== undefined);
1482
+ if (name && !needMetadata && !needComponents) {
1203
1483
  gameObjects = scanner.find_by_name(file, name, !exact);
1204
- } else if (needFullData) {
1484
+ } else if (needComponents) {
1205
1485
  gameObjects = scanner.scan_scene_with_components(file);
1206
- if (name) {
1486
+ if (name && !is_match_all(name)) {
1487
+ const nameLower = name.toLowerCase();
1488
+ const hasWildcard = name.includes("*") || name.includes("?");
1489
+ gameObjects = gameObjects.filter((go) => {
1490
+ if (!go.name)
1491
+ return false;
1492
+ if (hasWildcard) {
1493
+ return glob_match(name, go.name);
1494
+ }
1495
+ if (exact) {
1496
+ return go.name === name;
1497
+ }
1498
+ return go.name.toLowerCase().includes(nameLower);
1499
+ });
1500
+ }
1501
+ } else if (needMetadata) {
1502
+ gameObjects = scanner.scan_scene_metadata(file);
1503
+ if (name && !is_match_all(name)) {
1207
1504
  const nameLower = name.toLowerCase();
1208
1505
  const hasWildcard = name.includes("*") || name.includes("?");
1209
1506
  gameObjects = gameObjects.filter((go) => {
@@ -1344,11 +1641,54 @@ function grep_project_js(options) {
1344
1641
  }
1345
1642
  const EXTENSION_MAP = {
1346
1643
  cs: [".cs"],
1347
- yaml: [".yaml", ".yml", ".unity", ".prefab", ".asset"],
1644
+ yaml: [
1645
+ ".yaml",
1646
+ ".yml",
1647
+ ".unity",
1648
+ ".prefab",
1649
+ ".asset",
1650
+ ".mat",
1651
+ ".anim",
1652
+ ".controller",
1653
+ ".overrideController",
1654
+ ".mask",
1655
+ ".mixer",
1656
+ ".lighting",
1657
+ ".preset",
1658
+ ".signal",
1659
+ ".playable",
1660
+ ".renderTexture",
1661
+ ".flare",
1662
+ ".guiskin",
1663
+ ".terrainlayer",
1664
+ ".cubemap"
1665
+ ],
1348
1666
  unity: [".unity"],
1349
1667
  prefab: [".prefab"],
1350
1668
  asset: [".asset"],
1351
- all: [".cs", ".unity", ".prefab", ".asset", ".yaml", ".yml", ".txt", ".json", ".xml", ".shader", ".cginc", ".hlsl", ".compute", ".asmdef", ".asmref"]
1669
+ mat: [".mat"],
1670
+ anim: [".anim"],
1671
+ controller: [".controller"],
1672
+ all: [
1673
+ ".cs",
1674
+ ".unity",
1675
+ ".prefab",
1676
+ ".asset",
1677
+ ".mat",
1678
+ ".anim",
1679
+ ".controller",
1680
+ ".yaml",
1681
+ ".yml",
1682
+ ".txt",
1683
+ ".json",
1684
+ ".xml",
1685
+ ".shader",
1686
+ ".cginc",
1687
+ ".hlsl",
1688
+ ".compute",
1689
+ ".asmdef",
1690
+ ".asmref"
1691
+ ]
1352
1692
  };
1353
1693
  const extensions = EXTENSION_MAP[file_type] || EXTENSION_MAP.all;
1354
1694
  const files = walk_project_files(project_path, extensions);
@@ -1417,7 +1757,77 @@ function grep_project_js(options) {
1417
1757
  };
1418
1758
  }
1419
1759
  // src/editor/create.ts
1760
+ var import_fs10 = require("fs");
1761
+ var path5 = __toESM(require("path"));
1762
+
1763
+ // src/guid-cache.ts
1420
1764
  var import_fs7 = require("fs");
1765
+ var import_path5 = require("path");
1766
+ var _cache_store = new Map;
1767
+ function build_guid_cache(project_path, raw) {
1768
+ return {
1769
+ project_path,
1770
+ cache: raw,
1771
+ count: Object.keys(raw).length,
1772
+ resolve(guid) {
1773
+ return raw[guid] ?? null;
1774
+ },
1775
+ resolve_absolute(guid) {
1776
+ const rel = raw[guid];
1777
+ if (!rel)
1778
+ return null;
1779
+ if (import_path5.isAbsolute(rel))
1780
+ return rel;
1781
+ return import_path5.join(project_path, rel);
1782
+ },
1783
+ resolve_many(guids) {
1784
+ const result = {};
1785
+ for (const guid of guids) {
1786
+ result[guid] = raw[guid] ?? null;
1787
+ }
1788
+ return result;
1789
+ },
1790
+ find_by_name(name, extension) {
1791
+ const nameLower = name.toLowerCase().replace(/\.[^.]+$/, "");
1792
+ let substringMatch = null;
1793
+ for (const [guid, assetPath] of Object.entries(raw)) {
1794
+ if (extension && !assetPath.endsWith(extension))
1795
+ continue;
1796
+ const fileName = import_path5.basename(assetPath, import_path5.extname(assetPath)).toLowerCase();
1797
+ if (fileName === nameLower) {
1798
+ return { guid, path: assetPath };
1799
+ }
1800
+ if (!substringMatch && assetPath.toLowerCase().includes(nameLower)) {
1801
+ substringMatch = { guid, path: assetPath };
1802
+ }
1803
+ }
1804
+ return substringMatch;
1805
+ }
1806
+ };
1807
+ }
1808
+ function load_guid_cache(project_path) {
1809
+ const resolved = project_path;
1810
+ const existing = _cache_store.get(resolved);
1811
+ if (existing)
1812
+ return existing;
1813
+ const cachePath = import_path5.join(resolved, ".unity-agentic", "guid-cache.json");
1814
+ if (!import_fs7.existsSync(cachePath))
1815
+ return null;
1816
+ try {
1817
+ const raw = JSON.parse(import_fs7.readFileSync(cachePath, "utf-8"));
1818
+ const cache = build_guid_cache(resolved, raw);
1819
+ _cache_store.set(resolved, cache);
1820
+ return cache;
1821
+ } catch {
1822
+ return null;
1823
+ }
1824
+ }
1825
+ function load_guid_cache_for_file(file_path, explicit_project) {
1826
+ const project_path = explicit_project || find_unity_project_root(import_path5.dirname(file_path));
1827
+ if (!project_path)
1828
+ return null;
1829
+ return load_guid_cache(project_path);
1830
+ }
1421
1831
 
1422
1832
  // src/class-ids.ts
1423
1833
  var UNITY_CLASS_IDS = {
@@ -1623,12 +2033,394 @@ var UNITY_CLASS_NAMES = Object.fromEntries(Object.entries(UNITY_CLASS_IDS).map((
1623
2033
  function get_class_id_name(class_id) {
1624
2034
  return UNITY_CLASS_IDS[class_id] || `Unknown_${class_id}`;
1625
2035
  }
2036
+ function get_class_id(component_name) {
2037
+ if (UNITY_CLASS_NAMES[component_name] !== undefined) {
2038
+ return UNITY_CLASS_NAMES[component_name];
2039
+ }
2040
+ const lowerName = component_name.toLowerCase();
2041
+ for (const [name, id] of Object.entries(UNITY_CLASS_NAMES)) {
2042
+ if (name.toLowerCase() === lowerName) {
2043
+ return id;
2044
+ }
2045
+ }
2046
+ return null;
2047
+ }
1626
2048
 
1627
- // src/editor/unity-block.ts
1628
- var HEADER_PATTERN = /^--- !u!(\d+) &(\d+)(\s+stripped)?/;
1629
- function parse_header(raw) {
1630
- const first_newline = raw.indexOf(`
1631
- `);
2049
+ // src/editor/shared.ts
2050
+ var import_fs8 = require("fs");
2051
+ var path3 = __toESM(require("path"));
2052
+ function validateUnityYAML(content) {
2053
+ if (!content.startsWith("%YAML 1.1")) {
2054
+ return "Missing or invalid YAML header";
2055
+ }
2056
+ const invalidGuids = content.match(/guid:\s*[a-f0-9]{1,29}\b/g);
2057
+ if (invalidGuids) {
2058
+ return "Found invalid GUID format (missing characters)";
2059
+ }
2060
+ const blockOpens = (content.match(/--- !u!/g) || []).length;
2061
+ const blockCloses = (content.match(/\n---(?!u!)/g) || []).length;
2062
+ if (Math.abs(blockOpens - blockCloses) > 1) {
2063
+ return "Unbalanced YAML block markers";
2064
+ }
2065
+ return null;
2066
+ }
2067
+ function extractGuidFromMeta(metaPath) {
2068
+ if (!import_fs8.existsSync(metaPath)) {
2069
+ return null;
2070
+ }
2071
+ try {
2072
+ const content = import_fs8.readFileSync(metaPath, "utf-8");
2073
+ const match = content.match(/guid:\s*([a-f0-9]{32})/);
2074
+ return match ? match[1] : null;
2075
+ } catch {
2076
+ return null;
2077
+ }
2078
+ }
2079
+ function resolveScriptGuid(script, projectPath) {
2080
+ if (/^[a-f0-9]{32}$/i.test(script)) {
2081
+ return { guid: script.toLowerCase(), path: null };
2082
+ }
2083
+ if (script.endsWith(".cs")) {
2084
+ const metaPath = script + ".meta";
2085
+ if (import_fs8.existsSync(metaPath)) {
2086
+ const guid = extractGuidFromMeta(metaPath);
2087
+ if (guid) {
2088
+ return { guid, path: script };
2089
+ }
2090
+ }
2091
+ if (projectPath) {
2092
+ const fullPath = path3.join(projectPath, script);
2093
+ const fullMetaPath = fullPath + ".meta";
2094
+ if (import_fs8.existsSync(fullMetaPath)) {
2095
+ const guid = extractGuidFromMeta(fullMetaPath);
2096
+ if (guid) {
2097
+ return { guid, path: script };
2098
+ }
2099
+ }
2100
+ }
2101
+ }
2102
+ if (projectPath) {
2103
+ const guidCache = load_guid_cache(projectPath);
2104
+ if (guidCache) {
2105
+ const result = guidCache.find_by_name(script, ".cs");
2106
+ if (result)
2107
+ return result;
2108
+ }
2109
+ const registryPath = path3.join(projectPath, ".unity-agentic", "type-registry.json");
2110
+ if (import_fs8.existsSync(registryPath)) {
2111
+ try {
2112
+ const registry = JSON.parse(import_fs8.readFileSync(registryPath, "utf-8"));
2113
+ let targetName = script;
2114
+ let targetNamespace = null;
2115
+ const dotIndex = script.lastIndexOf(".");
2116
+ if (dotIndex > 0) {
2117
+ targetNamespace = script.substring(0, dotIndex);
2118
+ targetName = script.substring(dotIndex + 1);
2119
+ }
2120
+ const targetNameLower = targetName.toLowerCase();
2121
+ const matches = registry.filter((t) => {
2122
+ if (t.name.toLowerCase() !== targetNameLower)
2123
+ return false;
2124
+ if (targetNamespace && t.namespace?.toLowerCase() !== targetNamespace.toLowerCase())
2125
+ return false;
2126
+ return true;
2127
+ });
2128
+ if (matches.length === 1 && matches[0].guid) {
2129
+ return { guid: matches[0].guid, path: matches[0].filePath };
2130
+ }
2131
+ if (matches.length > 1) {
2132
+ const withGuid = matches.filter((m) => m.guid);
2133
+ if (withGuid.length === 1) {
2134
+ return { guid: withGuid[0].guid, path: withGuid[0].filePath };
2135
+ }
2136
+ }
2137
+ } catch {}
2138
+ }
2139
+ const packageCachePath = path3.join(projectPath, ".unity-agentic", "package-cache.json");
2140
+ if (import_fs8.existsSync(packageCachePath)) {
2141
+ try {
2142
+ const packageCache = JSON.parse(import_fs8.readFileSync(packageCachePath, "utf-8"));
2143
+ const scriptNameLower = script.toLowerCase().replace(/\.cs$/, "");
2144
+ for (const [guid, assetPath] of Object.entries(packageCache)) {
2145
+ if (!assetPath.endsWith(".cs"))
2146
+ continue;
2147
+ const fileName = path3.basename(assetPath, ".cs").toLowerCase();
2148
+ if (fileName === scriptNameLower) {
2149
+ return { guid, path: assetPath };
2150
+ }
2151
+ }
2152
+ } catch {}
2153
+ }
2154
+ }
2155
+ return null;
2156
+ }
2157
+ function resolve_script_with_fields(script, project_path) {
2158
+ const resolved = resolveScriptGuid(script, project_path);
2159
+ if (!resolved)
2160
+ return null;
2161
+ const result = {
2162
+ guid: resolved.guid,
2163
+ path: resolved.path
2164
+ };
2165
+ if (!project_path)
2166
+ return result;
2167
+ if (resolved.path) {
2168
+ try {
2169
+ const full_path = resolved.path.startsWith("/") ? resolved.path : path3.join(project_path, resolved.path);
2170
+ if (!import_fs8.existsSync(full_path)) {
2171
+ result.extraction_error = `Script file not found at resolved path: ${full_path}`;
2172
+ } else if (resolved.path.endsWith(".cs")) {
2173
+ const { getNativeExtractSerializedFields: getNativeExtractSerializedFields2 } = (init_scanner(), __toCommonJS(exports_scanner));
2174
+ const extract = getNativeExtractSerializedFields2();
2175
+ if (extract) {
2176
+ const type_infos = extract(full_path);
2177
+ if (type_infos && type_infos.length > 0) {
2178
+ const mono = type_infos.find((t) => t.baseClass === "MonoBehaviour" || t.baseClass === "ScriptableObject" || t.baseClass === "NetworkBehaviour" || t.baseClass === "StateMachineBehaviour");
2179
+ const chosen = mono || type_infos[0];
2180
+ result.fields = chosen.fields;
2181
+ result.base_class = chosen.baseClass ?? undefined;
2182
+ result.kind = chosen.kind;
2183
+ }
2184
+ } else {
2185
+ result.extraction_error = "Native extractSerializedFields function not available";
2186
+ }
2187
+ } else if (resolved.path.endsWith(".dll")) {
2188
+ const { getNativeExtractDllFields: getNativeExtractDllFields2 } = (init_scanner(), __toCommonJS(exports_scanner));
2189
+ const extract = getNativeExtractDllFields2();
2190
+ if (extract) {
2191
+ const type_infos = extract(full_path);
2192
+ if (type_infos && type_infos.length > 0) {
2193
+ const script_name = script.includes(".") ? script.split(".").pop() : script;
2194
+ const match = type_infos.find((t) => t.name === script_name);
2195
+ const chosen = match || type_infos[0];
2196
+ result.fields = chosen.fields;
2197
+ result.base_class = chosen.baseClass ?? undefined;
2198
+ result.kind = chosen.kind;
2199
+ }
2200
+ } else {
2201
+ result.extraction_error = "Native extractDllFields function not available";
2202
+ }
2203
+ }
2204
+ } catch (err) {
2205
+ result.extraction_error = `Field extraction failed: ${err instanceof Error ? err.message : String(err)}`;
2206
+ }
2207
+ }
2208
+ if (result.fields && result.fields.length > 0) {
2209
+ resolve_enum_fields(result.fields, project_path);
2210
+ }
2211
+ return result;
2212
+ }
2213
+ function resolve_enum_fields(fields, project_path) {
2214
+ const registry_path = path3.join(project_path, ".unity-agentic", "type-registry.json");
2215
+ if (!import_fs8.existsSync(registry_path))
2216
+ return;
2217
+ try {
2218
+ const registry = JSON.parse(import_fs8.readFileSync(registry_path, "utf-8"));
2219
+ const enum_names = new Set;
2220
+ for (const entry of registry) {
2221
+ if (entry.kind === "enum") {
2222
+ enum_names.add(entry.name);
2223
+ }
2224
+ }
2225
+ if (enum_names.size === 0)
2226
+ return;
2227
+ for (const field of fields) {
2228
+ let typeName = field.typeName;
2229
+ const nullable = typeName.endsWith("?");
2230
+ if (nullable) {
2231
+ typeName = typeName.slice(0, -1);
2232
+ }
2233
+ if (enum_names.has(typeName)) {
2234
+ field.typeName = nullable ? "int?" : "int";
2235
+ continue;
2236
+ }
2237
+ const dotIdx = typeName.lastIndexOf(".");
2238
+ if (dotIdx > 0) {
2239
+ const shortName = typeName.substring(dotIdx + 1);
2240
+ if (enum_names.has(shortName)) {
2241
+ field.typeName = nullable ? "int?" : "int";
2242
+ }
2243
+ }
2244
+ }
2245
+ } catch {}
2246
+ }
2247
+
2248
+ // src/editor/yaml-fields.ts
2249
+ var PRIMITIVE_DEFAULTS = {
2250
+ int: "0",
2251
+ float: "0",
2252
+ double: "0",
2253
+ bool: "0",
2254
+ string: "",
2255
+ byte: "0",
2256
+ sbyte: "0",
2257
+ short: "0",
2258
+ ushort: "0",
2259
+ uint: "0",
2260
+ long: "0",
2261
+ ulong: "0",
2262
+ char: "0",
2263
+ Int32: "0",
2264
+ Single: "0",
2265
+ Double: "0",
2266
+ Boolean: "0",
2267
+ String: "",
2268
+ Byte: "0",
2269
+ SByte: "0",
2270
+ Int16: "0",
2271
+ UInt16: "0",
2272
+ UInt32: "0",
2273
+ Int64: "0",
2274
+ UInt64: "0",
2275
+ Char: "0"
2276
+ };
2277
+ var STRUCT_DEFAULTS = {
2278
+ Vector2: "{x: 0, y: 0}",
2279
+ Vector3: "{x: 0, y: 0, z: 0}",
2280
+ Vector4: "{x: 0, y: 0, z: 0, w: 0}",
2281
+ Vector2Int: "{x: 0, y: 0}",
2282
+ Vector3Int: "{x: 0, y: 0, z: 0}",
2283
+ Quaternion: "{x: 0, y: 0, z: 0, w: 1}",
2284
+ Color: "{r: 0, g: 0, b: 0, a: 0}",
2285
+ Color32: "{r: 0, g: 0, b: 0, a: 0}",
2286
+ Rect: `serializedVersion: 2
2287
+ x: 0
2288
+ y: 0
2289
+ width: 0
2290
+ height: 0`,
2291
+ RectInt: "{x: 0, y: 0, width: 0, height: 0}",
2292
+ RectOffset: "{m_Left: 0, m_Right: 0, m_Top: 0, m_Bottom: 0}",
2293
+ Matrix4x4: "{e00: 1, e01: 0, e02: 0, e03: 0, e10: 0, e11: 1, e12: 0, e13: 0, e20: 0, e21: 0, e22: 1, e23: 0, e30: 0, e31: 0, e32: 0, e33: 1}",
2294
+ LayerMask: `serializedVersion: 2
2295
+ m_Bits: 0`
2296
+ };
2297
+ var VERSION_GATED_STRUCT_DEFAULTS = {
2298
+ Hash128: { value: `serializedVersion: 2
2299
+ Hash: 00000000000000000000000000000000`, min_major: 2021, min_minor: 1 },
2300
+ RenderingLayerMask: { value: `serializedVersion: 2
2301
+ m_Bits: 0`, min_major: 6000, min_minor: 0 }
2302
+ };
2303
+ var BLOCK_STRUCT_DEFAULTS = {
2304
+ Bounds: `m_Center: {x: 0, y: 0, z: 0}
2305
+ m_Extent: {x: 0, y: 0, z: 0}`,
2306
+ BoundsInt: `m_Position: {x: 0, y: 0, z: 0}
2307
+ m_Size: {x: 0, y: 0, z: 0}`
2308
+ };
2309
+ var OBJECT_REF_TYPES = new Set([
2310
+ "GameObject",
2311
+ "Transform",
2312
+ "RectTransform",
2313
+ "Material",
2314
+ "Texture",
2315
+ "Texture2D",
2316
+ "Texture3D",
2317
+ "RenderTexture",
2318
+ "Sprite",
2319
+ "AudioClip",
2320
+ "VideoClip",
2321
+ "Mesh",
2322
+ "Shader",
2323
+ "ComputeShader",
2324
+ "AnimationClip",
2325
+ "AnimatorController",
2326
+ "RuntimeAnimatorController",
2327
+ "PhysicMaterial",
2328
+ "PhysicsMaterial",
2329
+ "PhysicsMaterial2D",
2330
+ "Font",
2331
+ "TMP_FontAsset",
2332
+ "Object",
2333
+ "Component",
2334
+ "Behaviour",
2335
+ "MonoBehaviour",
2336
+ "ScriptableObject",
2337
+ "Rigidbody",
2338
+ "Rigidbody2D",
2339
+ "Collider",
2340
+ "Collider2D",
2341
+ "Camera",
2342
+ "Light",
2343
+ "Canvas",
2344
+ "CanvasGroup",
2345
+ "EventSystem",
2346
+ "AudioSource",
2347
+ "ParticleSystem",
2348
+ "TextAsset",
2349
+ "TerrainData"
2350
+ ]);
2351
+ function version_at_least(version, min_major, min_minor) {
2352
+ if (!version)
2353
+ return false;
2354
+ if (version.major > min_major)
2355
+ return true;
2356
+ if (version.major === min_major)
2357
+ return version.minor >= min_minor;
2358
+ return false;
2359
+ }
2360
+ function yaml_default_for_type(csharp_type, version) {
2361
+ if (csharp_type.endsWith("?")) {
2362
+ return null;
2363
+ }
2364
+ if (csharp_type in PRIMITIVE_DEFAULTS) {
2365
+ return PRIMITIVE_DEFAULTS[csharp_type];
2366
+ }
2367
+ if (csharp_type in STRUCT_DEFAULTS) {
2368
+ return STRUCT_DEFAULTS[csharp_type];
2369
+ }
2370
+ if (csharp_type in VERSION_GATED_STRUCT_DEFAULTS) {
2371
+ const gated = VERSION_GATED_STRUCT_DEFAULTS[csharp_type];
2372
+ if (version_at_least(version, gated.min_major, gated.min_minor)) {
2373
+ return gated.value;
2374
+ }
2375
+ return null;
2376
+ }
2377
+ if (csharp_type in BLOCK_STRUCT_DEFAULTS) {
2378
+ return BLOCK_STRUCT_DEFAULTS[csharp_type];
2379
+ }
2380
+ if (csharp_type.endsWith("[]")) {
2381
+ return "[]";
2382
+ }
2383
+ if (csharp_type.startsWith("List<") && csharp_type.endsWith(">")) {
2384
+ return "[]";
2385
+ }
2386
+ if (OBJECT_REF_TYPES.has(csharp_type)) {
2387
+ return "{fileID: 0}";
2388
+ }
2389
+ return "{fileID: 0}";
2390
+ }
2391
+ function generate_field_yaml(fields, version, indent = " ") {
2392
+ const lines = [];
2393
+ for (const field of fields) {
2394
+ const default_value = yaml_default_for_type(field.typeName, version);
2395
+ if (default_value === null) {
2396
+ continue;
2397
+ }
2398
+ if (default_value.includes(`
2399
+ `)) {
2400
+ lines.push(`${indent}${field.name}:`);
2401
+ for (const sub_line of default_value.split(`
2402
+ `)) {
2403
+ lines.push(`${indent}${sub_line}`);
2404
+ }
2405
+ } else {
2406
+ lines.push(`${indent}${field.name}: ${default_value}`);
2407
+ }
2408
+ }
2409
+ return lines.length > 0 ? `
2410
+ ` + lines.join(`
2411
+ `) + `
2412
+ ` : `
2413
+ `;
2414
+ }
2415
+
2416
+ // src/editor/unity-document.ts
2417
+ var import_fs9 = require("fs");
2418
+
2419
+ // src/editor/unity-block.ts
2420
+ var HEADER_PATTERN = /^--- !u!(\d+) &(-?\d+)(\s+stripped)?/;
2421
+ function parse_header(raw) {
2422
+ const first_newline = raw.indexOf(`
2423
+ `);
1632
2424
  const first_line = first_newline === -1 ? raw : raw.slice(0, first_newline);
1633
2425
  const match = first_line.match(HEADER_PATTERN);
1634
2426
  if (!match)
@@ -1676,12 +2468,12 @@ class UnityBlock {
1676
2468
  get dirty() {
1677
2469
  return this._dirty;
1678
2470
  }
1679
- get_property(path3) {
1680
- if (path3.includes("Array.data[")) {
1681
- return this._get_array_element(path3);
2471
+ get_property(path4) {
2472
+ if (path4.includes("Array.data[")) {
2473
+ return this._get_array_element(path4);
1682
2474
  }
1683
- if (path3.includes(".")) {
1684
- const parts = path3.split(".");
2475
+ if (path4.includes(".")) {
2476
+ const parts = path4.split(".");
1685
2477
  const root_prop = parts[0];
1686
2478
  const root_pattern = new RegExp(`^\\s*${escape_regex(root_prop)}:\\s*(.*)$`, "m");
1687
2479
  const root_match = this._raw.match(root_pattern);
@@ -1698,23 +2490,23 @@ class UnityBlock {
1698
2490
  return block_value;
1699
2491
  return null;
1700
2492
  }
1701
- const prop_pattern = new RegExp(`^\\s*${escape_regex(path3)}:\\s*(.*)$`, "m");
2493
+ const prop_pattern = new RegExp(`^\\s*${escape_regex(path4)}:\\s*(.*)$`, "m");
1702
2494
  const match = this._raw.match(prop_pattern);
1703
2495
  return match ? match[1].trim() : null;
1704
2496
  }
1705
- set_property(path3, value, object_reference) {
2497
+ set_property(path4, value, object_reference) {
1706
2498
  const old_raw = this._raw;
1707
2499
  const effective_ref = object_reference && object_reference !== "{fileID: 0}" ? object_reference : undefined;
1708
- if (!path3.includes(".") && !path3.includes("Array")) {
1709
- const prop_pattern = new RegExp(`(^\\s*${escape_regex(path3)}:\\s*)(.*)$`, "m");
2500
+ if (!path4.includes(".") && !path4.includes("Array")) {
2501
+ const prop_pattern = new RegExp(`(^\\s*${escape_regex(path4)}:\\s*)(.*)$`, "m");
1710
2502
  if (prop_pattern.test(this._raw)) {
1711
2503
  const replacement_value = effective_ref ?? value;
1712
2504
  this._raw = this._raw.replace(prop_pattern, `$1${replacement_value}`);
1713
2505
  }
1714
2506
  return this._check_dirty(old_raw);
1715
2507
  }
1716
- if (path3.includes(".") && !path3.includes("Array")) {
1717
- const parts = path3.split(".");
2508
+ if (path4.includes(".") && !path4.includes("Array")) {
2509
+ const parts = path4.split(".");
1718
2510
  const parent_prop = parts[0];
1719
2511
  const sub_field = parts[1];
1720
2512
  const inline_pattern = new RegExp(`(${escape_regex(parent_prop)}:\\s*\\{)([^}]*)(\\})`, "m");
@@ -1738,8 +2530,8 @@ class UnityBlock {
1738
2530
  }
1739
2531
  return this._check_dirty(old_raw);
1740
2532
  }
1741
- if (path3.includes("Array.data[")) {
1742
- const array_match = path3.match(/^(.+)\.Array\.data\[(\d+)\]$/);
2533
+ if (path4.includes("Array.data[")) {
2534
+ const array_match = path4.match(/^(.+)\.Array\.data\[(\d+)\]$/);
1743
2535
  if (array_match) {
1744
2536
  const array_prop = array_match[1];
1745
2537
  const index = parseInt(array_match[2], 10);
@@ -1760,8 +2552,8 @@ class UnityBlock {
1760
2552
  }
1761
2553
  return false;
1762
2554
  }
1763
- has_property(path3) {
1764
- return this.get_property(path3) !== null;
2555
+ has_property(path4) {
2556
+ return this.get_property(path4) !== null;
1765
2557
  }
1766
2558
  detect_format(property_name) {
1767
2559
  const cached = this._format_cache.get(property_name);
@@ -1773,7 +2565,8 @@ class UnityBlock {
1773
2565
  return result;
1774
2566
  }
1775
2567
  get_array_length(array_property) {
1776
- const empty_pattern = new RegExp(`^\\s*${escape_regex(array_property)}:\\s*\\[\\]\\s*$`, "m");
2568
+ const clean_prop = array_property.replace(/\.Array$/, "");
2569
+ const empty_pattern = new RegExp(`^\\s*${escape_regex(clean_prop)}:\\s*\\[\\]\\s*$`, "m");
1777
2570
  if (empty_pattern.test(this._raw))
1778
2571
  return 0;
1779
2572
  const lines = this._raw.split(`
@@ -1783,7 +2576,7 @@ class UnityBlock {
1783
2576
  let count = 0;
1784
2577
  for (const line of lines) {
1785
2578
  if (!in_array) {
1786
- const header_match = line.match(new RegExp(`^(\\s*)${escape_regex(array_property)}:\\s*$`));
2579
+ const header_match = line.match(new RegExp(`^(\\s*)${escape_regex(clean_prop)}:\\s*$`));
1787
2580
  if (header_match) {
1788
2581
  in_array = true;
1789
2582
  array_indent = header_match[1].length;
@@ -1807,8 +2600,9 @@ class UnityBlock {
1807
2600
  return count;
1808
2601
  }
1809
2602
  insert_array_element(array_property, index, value) {
2603
+ const clean_prop = array_property.replace(/\.Array$/, "");
1810
2604
  const old_raw = this._raw;
1811
- const empty_pattern = new RegExp(`(^(\\s*)${escape_regex(array_property)}:\\s*)\\[\\]`, "m");
2605
+ const empty_pattern = new RegExp(`(^(\\s*)${escape_regex(clean_prop)}:\\s*)\\[\\]`, "m");
1812
2606
  const empty_match = this._raw.match(empty_pattern);
1813
2607
  if (empty_match) {
1814
2608
  const base_indent = empty_match[2];
@@ -1824,7 +2618,7 @@ ${element_indent2}- ${value}`);
1824
2618
  const element_indices = [];
1825
2619
  for (let i = 0;i < lines.length; i++) {
1826
2620
  if (array_header_idx === -1) {
1827
- const header_match = lines[i].match(new RegExp(`^(\\s*)${escape_regex(array_property)}:\\s*$`));
2621
+ const header_match = lines[i].match(new RegExp(`^(\\s*)${escape_regex(clean_prop)}:\\s*$`));
1828
2622
  if (header_match) {
1829
2623
  array_header_idx = i;
1830
2624
  array_indent = header_match[1].length;
@@ -1868,6 +2662,7 @@ ${element_indent2}- ${value}`);
1868
2662
  return this._check_dirty(old_raw);
1869
2663
  }
1870
2664
  remove_array_element(array_property, index) {
2665
+ const clean_prop = array_property.replace(/\.Array$/, "");
1871
2666
  const old_raw = this._raw;
1872
2667
  const lines = this._raw.split(`
1873
2668
  `);
@@ -1876,7 +2671,7 @@ ${element_indent2}- ${value}`);
1876
2671
  const element_indices = [];
1877
2672
  for (let i = 0;i < lines.length; i++) {
1878
2673
  if (array_header_idx === -1) {
1879
- const header_match = lines[i].match(new RegExp(`^(\\s*)${escape_regex(array_property)}:\\s*$`));
2674
+ const header_match = lines[i].match(new RegExp(`^(\\s*)${escape_regex(clean_prop)}:\\s*$`));
1880
2675
  if (header_match) {
1881
2676
  array_header_idx = i;
1882
2677
  array_indent = header_match[1].length;
@@ -1904,7 +2699,7 @@ ${element_indent2}- ${value}`);
1904
2699
  if (element_indices.length === 1) {
1905
2700
  const header_indent_match = lines[array_header_idx].match(/^(\s*)/);
1906
2701
  const header_indent = header_indent_match ? header_indent_match[1] : "";
1907
- lines[array_header_idx] = `${header_indent}${array_property}: []`;
2702
+ lines[array_header_idx] = `${header_indent}${clean_prop}: []`;
1908
2703
  }
1909
2704
  this._raw = lines.join(`
1910
2705
  `);
@@ -1958,8 +2753,8 @@ ${element_indent2}- ${value}`);
1958
2753
  }
1959
2754
  return false;
1960
2755
  }
1961
- _get_array_element(path3) {
1962
- const array_match = path3.match(/^(.+)\.Array\.data\[(\d+)\]$/);
2756
+ _get_array_element(path4) {
2757
+ const array_match = path4.match(/^(.+)\.Array\.data\[(\d+)\]$/);
1963
2758
  if (!array_match)
1964
2759
  return null;
1965
2760
  const array_prop = array_match[1];
@@ -2119,249 +2914,1137 @@ ${element_indent2}- ${value}`);
2119
2914
  }
2120
2915
  }
2121
2916
 
2122
- // src/editor/create.ts
2123
- function createScene(options) {
2124
- const { output_path, include_defaults, scene_guid } = options;
2125
- const pathError = validate_file_path(output_path, "write");
2126
- if (pathError) {
2127
- return { success: false, output_path, error: pathError };
2917
+ // src/editor/unity-document.ts
2918
+ class UnityDocument {
2919
+ file_path;
2920
+ _header;
2921
+ _blocks;
2922
+ _id_index;
2923
+ _structure_dirty;
2924
+ static from_file(file_path, options) {
2925
+ const content = import_fs9.readFileSync(file_path, "utf-8");
2926
+ const doc = UnityDocument._parse(content, file_path);
2927
+ if (options?.validate && !doc.validate()) {
2928
+ throw new Error(`Invalid Unity YAML file: ${file_path}`);
2929
+ }
2930
+ return doc;
2128
2931
  }
2129
- if (!output_path.endsWith(".unity")) {
2130
- return {
2131
- success: false,
2132
- output_path,
2133
- error: "Output path must have .unity extension"
2134
- };
2932
+ static from_string(content, options) {
2933
+ const doc = UnityDocument._parse(content, null);
2934
+ if (options?.validate && !doc.validate()) {
2935
+ throw new Error("Invalid Unity YAML content");
2936
+ }
2937
+ return doc;
2135
2938
  }
2136
- if (import_fs7.existsSync(output_path)) {
2137
- return {
2138
- success: false,
2139
- output_path,
2140
- error: `File already exists: ${output_path}. Delete it first or choose a different path.`
2141
- };
2939
+ static _parse(content, file_path) {
2940
+ const normalized = content.replace(/\r\n/g, `
2941
+ `);
2942
+ const parts = normalized.split(/(?=--- !u!)/);
2943
+ let header = "";
2944
+ const raw_blocks = [];
2945
+ if (parts.length > 0 && !parts[0].startsWith("--- !u!")) {
2946
+ header = parts.shift();
2947
+ }
2948
+ for (const part of parts) {
2949
+ raw_blocks.push(part);
2950
+ }
2951
+ const blocks = raw_blocks.map((raw) => new UnityBlock(raw));
2952
+ return new UnityDocument(file_path, header, blocks);
2142
2953
  }
2143
- const guid = scene_guid || generateGuid();
2144
- let yaml = `%YAML 1.1
2145
- %TAG !u! tag:unity3d.com,2011:
2146
- --- !u!29 &1
2147
- OcclusionCullingSettings:
2148
- m_ObjectHideFlags: 0
2149
- serializedVersion: 2
2150
- m_OcclusionBakeSettings:
2151
- smallestOccluder: 5
2152
- smallestHole: 0.25
2153
- backfaceThreshold: 100
2154
- m_SceneGUID: 00000000000000000000000000000000
2155
- m_OcclusionCullingData: {fileID: 0}
2156
- --- !u!104 &2
2157
- RenderSettings:
2158
- m_ObjectHideFlags: 0
2159
- serializedVersion: 9
2160
- m_Fog: 0
2161
- m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
2162
- m_FogMode: 3
2163
- m_FogDensity: 0.01
2164
- m_LinearFogStart: 0
2165
- m_LinearFogEnd: 300
2166
- m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
2167
- m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
2168
- m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
2169
- m_AmbientIntensity: 1
2170
- m_AmbientMode: 0
2171
- m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
2172
- m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
2173
- m_HaloStrength: 0.5
2174
- m_FlareStrength: 1
2175
- m_FlareFadeSpeed: 3
2176
- m_HaloTexture: {fileID: 0}
2177
- m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
2178
- m_DefaultReflectionMode: 0
2179
- m_DefaultReflectionResolution: 128
2180
- m_ReflectionBounces: 1
2181
- m_ReflectionIntensity: 1
2182
- m_CustomReflection: {fileID: 0}
2183
- m_Sun: {fileID: 0}
2184
- m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1}
2185
- m_UseRadianceAmbientProbe: 0
2186
- --- !u!157 &3
2187
- LightmapSettings:
2188
- m_ObjectHideFlags: 0
2189
- serializedVersion: 12
2190
- m_GIWorkflowMode: 1
2191
- m_GISettings:
2192
- serializedVersion: 2
2193
- m_BounceScale: 1
2194
- m_IndirectOutputScale: 1
2195
- m_AlbedoBoost: 1
2196
- m_EnvironmentLightingMode: 0
2197
- m_EnableBakedLightmaps: 1
2198
- m_EnableRealtimeLightmaps: 0
2199
- m_LightmapEditorSettings:
2200
- serializedVersion: 12
2201
- m_Resolution: 2
2202
- m_BakeResolution: 40
2203
- m_AtlasSize: 1024
2204
- m_AO: 0
2205
- m_AOMaxDistance: 1
2206
- m_CompAOExponent: 1
2207
- m_CompAOExponentDirect: 0
2208
- m_ExtractAmbientOcclusion: 0
2209
- m_Padding: 2
2210
- m_LightmapParameters: {fileID: 0}
2211
- m_LightmapsBakeMode: 1
2212
- m_TextureCompression: 1
2213
- m_FinalGather: 0
2214
- m_FinalGatherFiltering: 1
2215
- m_FinalGatherRayCount: 256
2216
- m_ReflectionCompression: 2
2217
- m_MixedBakeMode: 2
2218
- m_BakeBackend: 1
2219
- m_PVRSampling: 1
2220
- m_PVRDirectSampleCount: 32
2221
- m_PVRSampleCount: 512
2222
- m_PVRBounces: 2
2223
- m_PVREnvironmentSampleCount: 256
2224
- m_PVREnvironmentReferencePointCount: 2048
2225
- m_PVRFilteringMode: 1
2226
- m_PVRDenoiserTypeDirect: 1
2227
- m_PVRDenoiserTypeIndirect: 1
2228
- m_PVRDenoiserTypeAO: 1
2229
- m_PVRFilterTypeDirect: 0
2230
- m_PVRFilterTypeIndirect: 0
2231
- m_PVRFilterTypeAO: 0
2232
- m_PVREnvironmentMIS: 1
2233
- m_PVRCulling: 1
2234
- m_PVRFilteringGaussRadiusDirect: 1
2235
- m_PVRFilteringGaussRadiusIndirect: 5
2236
- m_PVRFilteringGaussRadiusAO: 2
2237
- m_PVRFilteringAtrousPositionSigmaDirect: 0.5
2238
- m_PVRFilteringAtrousPositionSigmaIndirect: 2
2239
- m_PVRFilteringAtrousPositionSigmaAO: 1
2240
- m_ExportTrainingData: 0
2241
- m_TrainingDataDestination: TrainingData
2242
- m_LightProbeSampleCountMultiplier: 4
2243
- m_LightingDataAsset: {fileID: 0}
2244
- m_LightingSettings: {fileID: 0}
2245
- --- !u!196 &4
2246
- NavMeshSettings:
2247
- serializedVersion: 2
2248
- m_ObjectHideFlags: 0
2249
- m_BuildSettings:
2250
- serializedVersion: 3
2251
- agentTypeID: 0
2252
- agentRadius: 0.5
2253
- agentHeight: 2
2254
- agentSlope: 45
2255
- agentClimb: 0.4
2256
- ledgeDropHeight: 0
2257
- maxJumpAcrossDistance: 0
2258
- minRegionArea: 2
2259
- manualCellSize: 0
2260
- cellSize: 0.16666667
2261
- manualTileSize: 0
2262
- tileSize: 256
2263
- buildHeightMesh: 0
2264
- maxJobWorkers: 0
2265
- preserveTilesOutsideBounds: 0
2266
- debug:
2267
- m_Flags: 0
2268
- m_NavMeshData: {fileID: 0}
2269
- `;
2270
- if (include_defaults) {
2271
- yaml += `--- !u!1 &519420028
2272
- GameObject:
2273
- m_ObjectHideFlags: 0
2274
- m_CorrespondingSourceObject: {fileID: 0}
2275
- m_PrefabInstance: {fileID: 0}
2954
+ constructor(file_path, header, blocks) {
2955
+ this.file_path = file_path;
2956
+ this._header = header;
2957
+ this._blocks = blocks;
2958
+ this._id_index = new Map;
2959
+ this._structure_dirty = false;
2960
+ this._rebuild_index();
2961
+ }
2962
+ _rebuild_index() {
2963
+ this._id_index.clear();
2964
+ for (let i = 0;i < this._blocks.length; i++) {
2965
+ const id = this._blocks[i].file_id;
2966
+ if (id !== "0") {
2967
+ this._id_index.set(id, i);
2968
+ }
2969
+ }
2970
+ }
2971
+ get blocks() {
2972
+ return this._blocks;
2973
+ }
2974
+ get count() {
2975
+ return this._blocks.length;
2976
+ }
2977
+ get dirty() {
2978
+ if (this._structure_dirty)
2979
+ return true;
2980
+ for (const block of this._blocks) {
2981
+ if (block.dirty)
2982
+ return true;
2983
+ }
2984
+ return false;
2985
+ }
2986
+ find_by_file_id(file_id) {
2987
+ const index = this._id_index.get(file_id);
2988
+ if (index === undefined)
2989
+ return null;
2990
+ return this._blocks[index];
2991
+ }
2992
+ find_by_class_id(class_id) {
2993
+ const results = [];
2994
+ for (const block of this._blocks) {
2995
+ if (block.class_id === class_id) {
2996
+ results.push(block);
2997
+ }
2998
+ }
2999
+ return results;
3000
+ }
3001
+ find_game_objects_by_name(name) {
3002
+ const results = [];
3003
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3004
+ const pattern = new RegExp(`^\\s*m_Name:\\s*${escaped}\\s*$`, "m");
3005
+ for (const block of this._blocks) {
3006
+ if (block.class_id === 1 && pattern.test(block.raw)) {
3007
+ results.push(block);
3008
+ }
3009
+ }
3010
+ return results;
3011
+ }
3012
+ find_transforms_by_name(name) {
3013
+ const game_objects = this.find_game_objects_by_name(name);
3014
+ const transform_ids = [];
3015
+ for (const go of game_objects) {
3016
+ const comp_matches = go.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(\d+)\}/g);
3017
+ for (const cm of comp_matches) {
3018
+ const comp_block = this.find_by_file_id(cm[1]);
3019
+ if (comp_block && (comp_block.class_id === 4 || comp_block.class_id === 224)) {
3020
+ transform_ids.push(cm[1]);
3021
+ break;
3022
+ }
3023
+ }
3024
+ }
3025
+ return transform_ids;
3026
+ }
3027
+ require_unique_game_object(name_or_id) {
3028
+ if (/^\d+$/.test(name_or_id)) {
3029
+ const block = this.find_by_file_id(name_or_id);
3030
+ if (!block) {
3031
+ return { error: `GameObject with fileID ${name_or_id} not found` };
3032
+ }
3033
+ if (block.class_id !== 1) {
3034
+ return { error: `fileID ${name_or_id} is not a GameObject (class ${block.class_id})` };
3035
+ }
3036
+ return block;
3037
+ }
3038
+ const matches = this.find_game_objects_by_name(name_or_id);
3039
+ if (matches.length === 0) {
3040
+ return { error: `GameObject "${name_or_id}" not found` };
3041
+ }
3042
+ if (matches.length > 1) {
3043
+ const ids = matches.map((b) => b.file_id).join(", ");
3044
+ return { error: `Multiple GameObjects named "${name_or_id}" found (fileIDs: ${ids}). Use numeric fileID to specify which one.` };
3045
+ }
3046
+ return matches[0];
3047
+ }
3048
+ require_unique_transform(name_or_id) {
3049
+ if (/^\d+$/.test(name_or_id)) {
3050
+ const block = this.find_by_file_id(name_or_id);
3051
+ if (!block) {
3052
+ return { error: `Block with fileID ${name_or_id} not found` };
3053
+ }
3054
+ if (block.class_id === 4) {
3055
+ return block;
3056
+ }
3057
+ if (block.class_id === 224) {
3058
+ return block;
3059
+ }
3060
+ if (block.class_id === 1) {
3061
+ const comp_matches = block.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(\d+)\}/g);
3062
+ for (const cm of comp_matches) {
3063
+ const comp_block = this.find_by_file_id(cm[1]);
3064
+ if (comp_block && (comp_block.class_id === 4 || comp_block.class_id === 224)) {
3065
+ return comp_block;
3066
+ }
3067
+ }
3068
+ return { error: `Transform for GameObject fileID ${name_or_id} not found` };
3069
+ }
3070
+ return { error: `fileID ${name_or_id} is not a GameObject or Transform (class ${block.class_id})` };
3071
+ }
3072
+ const transform_ids = this.find_transforms_by_name(name_or_id);
3073
+ if (transform_ids.length === 0) {
3074
+ return { error: `GameObject "${name_or_id}" not found` };
3075
+ }
3076
+ if (transform_ids.length > 1) {
3077
+ const go_ids = this.find_game_objects_by_name(name_or_id).map((b) => b.file_id).join(", ");
3078
+ return { error: `Multiple GameObjects named "${name_or_id}" found (fileIDs: ${go_ids}). Use numeric fileID to specify which one.` };
3079
+ }
3080
+ const transform = this.find_by_file_id(transform_ids[0]);
3081
+ if (!transform) {
3082
+ return { error: `Transform for "${name_or_id}" not found` };
3083
+ }
3084
+ return transform;
3085
+ }
3086
+ find_prefab_root() {
3087
+ for (const block of this._blocks) {
3088
+ if (block.class_id === 4 && !block.is_stripped && /m_Father:\s*\{fileID:\s*0\}/.test(block.raw)) {
3089
+ const go_match = block.raw.match(/m_GameObject:\s*\{fileID:\s*(\d+)\}/);
3090
+ if (go_match) {
3091
+ const go_block = this.find_by_file_id(go_match[1]);
3092
+ if (go_block) {
3093
+ const name_match = go_block.raw.match(/m_Name:\s*(.+)/);
3094
+ const name = name_match ? name_match[1].trim() : "Prefab";
3095
+ return { game_object: go_block, transform: block, name };
3096
+ }
3097
+ }
3098
+ }
3099
+ }
3100
+ for (const block of this._blocks) {
3101
+ if (block.class_id === 1 && block.is_stripped) {
3102
+ for (const t_block of this._blocks) {
3103
+ if (t_block.class_id === 4 && t_block.is_stripped) {
3104
+ let name = "Variant";
3105
+ for (const pi_block of this._blocks) {
3106
+ if (pi_block.class_id === 1001) {
3107
+ const name_mod = pi_block.raw.match(/propertyPath: m_Name\s*\n\s*value:\s*(.+)/);
3108
+ if (name_mod) {
3109
+ name = name_mod[1].trim();
3110
+ }
3111
+ break;
3112
+ }
3113
+ }
3114
+ return { game_object: block, transform: t_block, name };
3115
+ }
3116
+ }
3117
+ }
3118
+ }
3119
+ return null;
3120
+ }
3121
+ all_file_ids() {
3122
+ const ids = new Set;
3123
+ for (const block of this._blocks) {
3124
+ const id = block.file_id;
3125
+ if (id !== "0") {
3126
+ ids.add(id);
3127
+ }
3128
+ }
3129
+ return ids;
3130
+ }
3131
+ trace_references(file_id, direction = "both", max_depth = 3) {
3132
+ const edges = [];
3133
+ const visited = new Set;
3134
+ let reverseIndex = null;
3135
+ if (direction === "in" || direction === "both") {
3136
+ reverseIndex = new Map;
3137
+ for (const block of this._blocks) {
3138
+ const refs = block.extract_file_id_refs();
3139
+ for (const ref of refs) {
3140
+ if (!reverseIndex.has(ref))
3141
+ reverseIndex.set(ref, []);
3142
+ reverseIndex.get(ref).push(block.file_id);
3143
+ }
3144
+ }
3145
+ }
3146
+ const queue = [];
3147
+ if (direction === "out" || direction === "both") {
3148
+ queue.push({ id: file_id, depth: 0, dir: "out" });
3149
+ }
3150
+ if (direction === "in" || direction === "both") {
3151
+ queue.push({ id: file_id, depth: 0, dir: "in" });
3152
+ }
3153
+ while (queue.length > 0) {
3154
+ const { id, depth, dir } = queue.shift();
3155
+ if (depth >= max_depth)
3156
+ continue;
3157
+ const visitKey = `${id}:${dir}`;
3158
+ if (visited.has(visitKey))
3159
+ continue;
3160
+ visited.add(visitKey);
3161
+ if (dir === "out") {
3162
+ const block = this.find_by_file_id(id);
3163
+ if (!block)
3164
+ continue;
3165
+ const refs = block.extract_file_id_refs();
3166
+ for (const ref of refs) {
3167
+ const targetBlock = this.find_by_file_id(ref);
3168
+ if (!targetBlock)
3169
+ continue;
3170
+ edges.push({
3171
+ source_file_id: id,
3172
+ target_file_id: ref,
3173
+ source_class_id: block.class_id,
3174
+ target_class_id: targetBlock.class_id,
3175
+ depth: depth + 1
3176
+ });
3177
+ queue.push({ id: ref, depth: depth + 1, dir: "out" });
3178
+ }
3179
+ } else {
3180
+ const sources = reverseIndex?.get(id) || [];
3181
+ for (const sourceId of sources) {
3182
+ const sourceBlock = this.find_by_file_id(sourceId);
3183
+ const targetBlock = this.find_by_file_id(id);
3184
+ if (!sourceBlock || !targetBlock)
3185
+ continue;
3186
+ edges.push({
3187
+ source_file_id: sourceId,
3188
+ target_file_id: id,
3189
+ source_class_id: sourceBlock.class_id,
3190
+ target_class_id: targetBlock.class_id,
3191
+ depth: depth + 1
3192
+ });
3193
+ queue.push({ id: sourceId, depth: depth + 1, dir: "in" });
3194
+ }
3195
+ }
3196
+ }
3197
+ return edges;
3198
+ }
3199
+ append_block(block) {
3200
+ const index = this._blocks.length;
3201
+ this._blocks.push(block);
3202
+ const id = block.file_id;
3203
+ if (id !== "0") {
3204
+ this._id_index.set(id, index);
3205
+ }
3206
+ this._structure_dirty = true;
3207
+ }
3208
+ append_raw(yaml_text) {
3209
+ const parts = yaml_text.split(/(?=--- !u!)/);
3210
+ const new_blocks = [];
3211
+ for (const part of parts) {
3212
+ if (!part.startsWith("--- !u!"))
3213
+ continue;
3214
+ const block = new UnityBlock(part);
3215
+ this.append_block(block);
3216
+ new_blocks.push(block);
3217
+ }
3218
+ return new_blocks;
3219
+ }
3220
+ remove_blocks(file_ids) {
3221
+ const original_count = this._blocks.length;
3222
+ this._blocks = this._blocks.filter((block) => {
3223
+ const id = block.file_id;
3224
+ return !file_ids.has(id);
3225
+ });
3226
+ const removed = original_count - this._blocks.length;
3227
+ if (removed > 0) {
3228
+ this._rebuild_index();
3229
+ this._structure_dirty = true;
3230
+ }
3231
+ return removed;
3232
+ }
3233
+ remove_block(file_id) {
3234
+ const index = this._id_index.get(file_id);
3235
+ if (index === undefined)
3236
+ return false;
3237
+ this._blocks.splice(index, 1);
3238
+ this._rebuild_index();
3239
+ this._structure_dirty = true;
3240
+ return true;
3241
+ }
3242
+ replace_block(index, block) {
3243
+ if (index < 0 || index >= this._blocks.length) {
3244
+ throw new Error(`Block index ${index} out of range (0..${this._blocks.length - 1})`);
3245
+ }
3246
+ const old_id = this._blocks[index].file_id;
3247
+ this._blocks[index] = block;
3248
+ if (old_id !== "0") {
3249
+ this._id_index.delete(old_id);
3250
+ }
3251
+ const new_id = block.file_id;
3252
+ if (new_id !== "0") {
3253
+ this._id_index.set(new_id, index);
3254
+ }
3255
+ this._structure_dirty = true;
3256
+ }
3257
+ add_child_to_parent(parent_id, child_id) {
3258
+ const parent = this.find_by_file_id(parent_id);
3259
+ if (!parent || parent.class_id !== 4)
3260
+ return false;
3261
+ let raw = parent.raw;
3262
+ const inline_empty = /m_Children:\s*\[\]/;
3263
+ if (inline_empty.test(raw)) {
3264
+ raw = raw.replace(inline_empty, `m_Children:
3265
+ - {fileID: ${child_id}}`);
3266
+ parent.replace_raw(raw);
3267
+ return true;
3268
+ }
3269
+ const inline_pattern = /m_Children:\s*\[(.*?)\]/;
3270
+ const inline_match = raw.match(inline_pattern);
3271
+ if (inline_match) {
3272
+ const trimmed = inline_match[1].trim();
3273
+ if (trimmed === "") {
3274
+ raw = raw.replace(inline_pattern, `m_Children:
3275
+ - {fileID: ${child_id}}`);
3276
+ } else {
3277
+ raw = raw.replace(inline_pattern, (match) => {
3278
+ return match.replace("]", "") + `
3279
+ - {fileID: ${child_id}}]`;
3280
+ });
3281
+ }
3282
+ parent.replace_raw(raw);
3283
+ return true;
3284
+ }
3285
+ if (raw.includes("m_Children:") && !raw.includes(`fileID: ${child_id}`)) {
3286
+ raw = raw.replace(/(m_Children:\s*\n(?:\s*-\s*\{fileID:\s*\d+\}\s*\n)*)/, `$1 - {fileID: ${child_id}}
3287
+ `);
3288
+ parent.replace_raw(raw);
3289
+ return true;
3290
+ }
3291
+ return false;
3292
+ }
3293
+ remove_child_from_parent(parent_id, child_id) {
3294
+ const parent = this.find_by_file_id(parent_id);
3295
+ if (!parent || parent.class_id !== 4)
3296
+ return false;
3297
+ let raw = parent.raw;
3298
+ const child_line = new RegExp(`\\n[ \\t]*- \\{fileID: ${child_id}\\}`);
3299
+ if (!child_line.test(raw))
3300
+ return false;
3301
+ raw = raw.replace(child_line, "");
3302
+ if (/m_Children:\s*\n\s*m_Father:/.test(raw) || /m_Children:\s*\n\s*m_RootOrder:/.test(raw)) {
3303
+ raw = raw.replace(/m_Children:\s*\n/, `m_Children: []
3304
+ `);
3305
+ }
3306
+ parent.replace_raw(raw);
3307
+ return true;
3308
+ }
3309
+ collect_hierarchy(transform_id) {
3310
+ const result = new Set;
3311
+ this._collect_hierarchy_recursive(transform_id, result);
3312
+ return result;
3313
+ }
3314
+ _collect_hierarchy_recursive(transform_id, result) {
3315
+ const transform = this.find_by_file_id(transform_id);
3316
+ if (!transform || transform.class_id !== 4)
3317
+ return;
3318
+ const children_section = transform.raw.match(/m_Children:[\s\S]*?(?=\s*m_Father:)/);
3319
+ if (!children_section)
3320
+ return;
3321
+ const child_ids = [];
3322
+ const child_matches = children_section[0].matchAll(/\{fileID:\s*(\d+)\}/g);
3323
+ for (const m of child_matches) {
3324
+ if (m[1] !== "0")
3325
+ child_ids.push(m[1]);
3326
+ }
3327
+ for (const child_transform_id of child_ids) {
3328
+ result.add(child_transform_id);
3329
+ const child_transform = this.find_by_file_id(child_transform_id);
3330
+ if (child_transform) {
3331
+ const go_match = child_transform.raw.match(/m_GameObject:\s*\{fileID:\s*(\d+)\}/);
3332
+ if (go_match) {
3333
+ const go_id = go_match[1];
3334
+ result.add(go_id);
3335
+ const go_block = this.find_by_file_id(go_id);
3336
+ if (go_block) {
3337
+ const comp_matches = go_block.raw.matchAll(/component:\s*\{fileID:\s*(\d+)\}/g);
3338
+ for (const cm of comp_matches) {
3339
+ result.add(cm[1]);
3340
+ }
3341
+ }
3342
+ }
3343
+ }
3344
+ this._collect_hierarchy_recursive(child_transform_id, result);
3345
+ }
3346
+ }
3347
+ calculate_root_order(parent_id) {
3348
+ if (parent_id === "0") {
3349
+ let count = 0;
3350
+ for (const block of this._blocks) {
3351
+ if (block.class_id === 4 && /m_Father:\s*\{fileID:\s*0\}/.test(block.raw)) {
3352
+ count++;
3353
+ }
3354
+ }
3355
+ return count;
3356
+ }
3357
+ const parent = this.find_by_file_id(parent_id);
3358
+ if (!parent || parent.class_id !== 4)
3359
+ return 0;
3360
+ const children_match = parent.raw.match(/m_Children:[\s\S]*?(?=\s*m_Father:)/);
3361
+ if (children_match) {
3362
+ const entries = children_match[0].match(/\{fileID:\s*\d+\}/g);
3363
+ return entries ? entries.length : 0;
3364
+ }
3365
+ return 0;
3366
+ }
3367
+ reorder_children(parent_id, ordered_child_ids) {
3368
+ const parent = this.find_by_file_id(parent_id);
3369
+ if (!parent || parent.class_id !== 4)
3370
+ return false;
3371
+ let raw = parent.raw;
3372
+ const new_children = ordered_child_ids.map((id) => ` - {fileID: ${id}}`).join(`
3373
+ `);
3374
+ const multiline = /m_Children:\n(?:[ \t]*-[^\n]*\n)*/;
3375
+ if (multiline.test(raw)) {
3376
+ raw = raw.replace(multiline, `m_Children:
3377
+ ${new_children}
3378
+ `);
3379
+ parent.replace_raw(raw);
3380
+ return true;
3381
+ }
3382
+ const inline = /m_Children:\s*\[.*?\]/;
3383
+ if (inline.test(raw)) {
3384
+ raw = raw.replace(inline, `m_Children:
3385
+ ${new_children}`);
3386
+ parent.replace_raw(raw);
3387
+ return true;
3388
+ }
3389
+ return false;
3390
+ }
3391
+ reorder_entities(ordered_transform_ids) {
3392
+ const entity_groups = [];
3393
+ for (const transform_id of ordered_transform_ids) {
3394
+ const transform = this.find_by_file_id(transform_id);
3395
+ if (!transform)
3396
+ continue;
3397
+ const go_match = transform.raw.match(/m_GameObject:\s*\{fileID:\s*(\d+)\}/);
3398
+ if (!go_match)
3399
+ continue;
3400
+ const go_id = go_match[1];
3401
+ const go = this.find_by_file_id(go_id);
3402
+ if (!go)
3403
+ continue;
3404
+ const comp_ids = [];
3405
+ const comp_matches = go.raw.matchAll(/component:\s*\{fileID:\s*(\d+)\}/g);
3406
+ for (const m of comp_matches) {
3407
+ comp_ids.push(m[1]);
3408
+ }
3409
+ entity_groups.push([go_id, ...comp_ids]);
3410
+ }
3411
+ const all_entity_ids = new Set(entity_groups.flat());
3412
+ if (all_entity_ids.size === 0)
3413
+ return;
3414
+ let insert_pos = -1;
3415
+ for (let i = 0;i < this._blocks.length; i++) {
3416
+ if (all_entity_ids.has(this._blocks[i].file_id)) {
3417
+ insert_pos = i;
3418
+ break;
3419
+ }
3420
+ }
3421
+ if (insert_pos === -1)
3422
+ return;
3423
+ const removed = new Map;
3424
+ this._blocks = this._blocks.filter((b) => {
3425
+ if (all_entity_ids.has(b.file_id)) {
3426
+ removed.set(b.file_id, b);
3427
+ return false;
3428
+ }
3429
+ return true;
3430
+ });
3431
+ const ordered_blocks = [];
3432
+ for (const group of entity_groups) {
3433
+ for (const id of group) {
3434
+ const block = removed.get(id);
3435
+ if (block)
3436
+ ordered_blocks.push(block);
3437
+ }
3438
+ }
3439
+ this._blocks.splice(insert_pos, 0, ...ordered_blocks);
3440
+ this._rebuild_index();
3441
+ this._structure_dirty = true;
3442
+ }
3443
+ generate_file_id() {
3444
+ const existing = this.all_file_ids();
3445
+ let id;
3446
+ do {
3447
+ const numeric = Math.floor(Math.random() * 9000000000) + 1e9;
3448
+ id = String(numeric);
3449
+ } while (existing.has(id) || id === "0");
3450
+ return id;
3451
+ }
3452
+ remap_file_ids(id_map, block_ids) {
3453
+ for (const block of this._blocks) {
3454
+ if (block_ids.has(block.file_id)) {
3455
+ for (const [old_id, new_id] of id_map) {
3456
+ block.remap_file_id(old_id, new_id);
3457
+ }
3458
+ }
3459
+ }
3460
+ this._rebuild_index();
3461
+ this._structure_dirty = true;
3462
+ }
3463
+ serialize() {
3464
+ return this._header + this._blocks.map((b) => b.raw).join("");
3465
+ }
3466
+ validate() {
3467
+ const content = this.serialize();
3468
+ if (!content.startsWith("%YAML 1.1")) {
3469
+ return false;
3470
+ }
3471
+ const invalid_guids = content.match(/guid:\s*[a-f0-9]{1,29}\b/g);
3472
+ if (invalid_guids) {
3473
+ return false;
3474
+ }
3475
+ const block_opens = (content.match(/--- !u!/g) || []).length;
3476
+ const block_closes = (content.match(/\n---(?!u!)/g) || []).length;
3477
+ if (Math.abs(block_opens - block_closes) > 1) {
3478
+ return false;
3479
+ }
3480
+ return true;
3481
+ }
3482
+ save(file_path) {
3483
+ const target = file_path ?? this.file_path;
3484
+ if (!target) {
3485
+ return { success: false, error: "No file path specified and document was not loaded from a file" };
3486
+ }
3487
+ const content = this.serialize();
3488
+ const result = atomicWrite(target, content);
3489
+ return {
3490
+ success: result.success,
3491
+ bytes_written: result.bytes_written,
3492
+ error: result.error
3493
+ };
3494
+ }
3495
+ }
3496
+
3497
+ // src/build-version.ts
3498
+ var fs = __toESM(require("fs"));
3499
+ var path4 = __toESM(require("path"));
3500
+ function parse_version(versionString) {
3501
+ const match = versionString.match(/^(\d+)\.(\d+)\.(\d+)([abfp])(\d+)$/);
3502
+ if (!match) {
3503
+ throw new Error(`Invalid Unity version format: ${versionString}`);
3504
+ }
3505
+ return {
3506
+ raw: versionString,
3507
+ major: parseInt(match[1], 10),
3508
+ minor: parseInt(match[2], 10),
3509
+ patch: parseInt(match[3], 10),
3510
+ releaseType: match[4],
3511
+ revision: parseInt(match[5], 10)
3512
+ };
3513
+ }
3514
+ function read_project_version(projectPath) {
3515
+ const versionFile = path4.join(projectPath, "ProjectSettings", "ProjectVersion.txt");
3516
+ if (!fs.existsSync(versionFile)) {
3517
+ throw new Error(`ProjectVersion.txt not found at: ${versionFile}`);
3518
+ }
3519
+ const content = fs.readFileSync(versionFile, "utf-8").replace(/\r\n/g, `
3520
+ `);
3521
+ const versionMatch = content.match(/m_EditorVersion:\s*(.+)/);
3522
+ if (!versionMatch) {
3523
+ throw new Error("Could not parse m_EditorVersion from ProjectVersion.txt");
3524
+ }
3525
+ const version = parse_version(versionMatch[1].trim());
3526
+ const revisionMatch = content.match(/m_EditorVersionWithRevision:\s*(.+)/);
3527
+ if (revisionMatch) {
3528
+ version.fullRevision = revisionMatch[1].trim();
3529
+ }
3530
+ return version;
3531
+ }
3532
+
3533
+ // src/editor/create.ts
3534
+ function get_layer_from_parent(doc, parentTransformId) {
3535
+ if (parentTransformId === "0")
3536
+ return 0;
3537
+ const parentTransform = doc.find_by_file_id(parentTransformId);
3538
+ if (!parentTransform)
3539
+ return 0;
3540
+ const goMatch = parentTransform.raw.match(/m_GameObject:\s*\{fileID:\s*(\d+)\}/);
3541
+ if (!goMatch)
3542
+ return 0;
3543
+ const parentGo = doc.find_by_file_id(goMatch[1]);
3544
+ if (!parentGo)
3545
+ return 0;
3546
+ const layerMatch = parentGo.raw.match(/m_Layer:\s*(\d+)/);
3547
+ return layerMatch ? parseInt(layerMatch[1], 10) : 0;
3548
+ }
3549
+ function createGameObjectYAML(gameObjectId, transformId, name, parentTransformId = 0, rootOrder = 0, layer = 0) {
3550
+ return `--- !u!1 &${gameObjectId}
3551
+ GameObject:
3552
+ m_ObjectHideFlags: 0
3553
+ m_CorrespondingSourceObject: {fileID: 0}
3554
+ m_PrefabInstance: {fileID: 0}
2276
3555
  m_PrefabAsset: {fileID: 0}
2277
3556
  serializedVersion: 6
2278
3557
  m_Component:
2279
- - component: {fileID: 519420032}
2280
- - component: {fileID: 519420031}
2281
- - component: {fileID: 519420029}
2282
- m_Layer: 0
2283
- m_Name: Main Camera
2284
- m_TagString: MainCamera
3558
+ - component: {fileID: ${transformId}}
3559
+ m_Layer: ${layer}
3560
+ m_Name: ${name}
3561
+ m_TagString: Untagged
2285
3562
  m_Icon: {fileID: 0}
2286
3563
  m_NavMeshLayer: 0
2287
3564
  m_StaticEditorFlags: 0
2288
3565
  m_IsActive: 1
2289
- --- !u!4 &519420032
3566
+ --- !u!4 &${transformId}
2290
3567
  Transform:
2291
3568
  m_ObjectHideFlags: 0
2292
3569
  m_CorrespondingSourceObject: {fileID: 0}
2293
3570
  m_PrefabInstance: {fileID: 0}
2294
3571
  m_PrefabAsset: {fileID: 0}
2295
- m_GameObject: {fileID: 519420028}
3572
+ m_GameObject: {fileID: ${gameObjectId}}
2296
3573
  serializedVersion: 2
2297
3574
  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
2298
- m_LocalPosition: {x: 0, y: 1, z: -10}
3575
+ m_LocalPosition: {x: 0, y: 0, z: 0}
2299
3576
  m_LocalScale: {x: 1, y: 1, z: 1}
2300
3577
  m_ConstrainProportionsScale: 0
2301
3578
  m_Children: []
2302
- m_Father: {fileID: 0}
2303
- m_RootOrder: 0
3579
+ m_Father: {fileID: ${parentTransformId}}
3580
+ m_RootOrder: ${rootOrder}
2304
3581
  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
2305
- --- !u!20 &519420031
2306
- Camera:
2307
- m_ObjectHideFlags: 0
2308
- m_CorrespondingSourceObject: {fileID: 0}
2309
- m_PrefabInstance: {fileID: 0}
2310
- m_PrefabAsset: {fileID: 0}
2311
- m_GameObject: {fileID: 519420028}
2312
- m_Enabled: 1
2313
- serializedVersion: 2
3582
+ `;
3583
+ }
3584
+ function findTransformIdByName(doc, objectName) {
3585
+ const transformIds = doc.find_transforms_by_name(objectName);
3586
+ if (transformIds.length === 0) {
3587
+ for (const block of doc.blocks) {
3588
+ if (block.class_id !== 1001)
3589
+ continue;
3590
+ const nameInMods = block.raw.match(/propertyPath:\s*m_Name\s*\n\s*value:\s*(.+)/);
3591
+ if (nameInMods && nameInMods[1].trim() === objectName) {
3592
+ const stripped = findStrippedRootTransform(doc, block.file_id);
3593
+ if (stripped)
3594
+ return stripped.transformId;
3595
+ }
3596
+ }
3597
+ return null;
3598
+ }
3599
+ if (transformIds.length > 1) {
3600
+ const gameObjects = doc.find_game_objects_by_name(objectName);
3601
+ const ids = gameObjects.map((go) => go.file_id).join(", ");
3602
+ return { error: `Multiple GameObjects named "${objectName}" found (fileIDs: ${ids}). Use numeric fileID to specify which one.` };
3603
+ }
3604
+ return transformIds[0];
3605
+ }
3606
+ function findStrippedRootTransform(doc, prefabInstanceId) {
3607
+ for (const block of doc.blocks) {
3608
+ if (block.class_id !== 4 || !block.is_stripped)
3609
+ continue;
3610
+ const piMatch = block.raw.match(/m_PrefabInstance:\s*\{fileID:\s*(\d+)\}/);
3611
+ if (piMatch && piMatch[1] === prefabInstanceId) {
3612
+ const sourceMatch = block.raw.match(/m_CorrespondingSourceObject:\s*(\{[^}]+\})/);
3613
+ if (sourceMatch) {
3614
+ return {
3615
+ transformId: block.file_id,
3616
+ sourceRef: sourceMatch[1]
3617
+ };
3618
+ }
3619
+ }
3620
+ }
3621
+ return null;
3622
+ }
3623
+ function appendToAddedGameObjects(doc, piId, targetSourceRef, newGoId) {
3624
+ const piBlock = doc.find_by_file_id(piId);
3625
+ if (!piBlock)
3626
+ return;
3627
+ const entry = `
3628
+ - targetCorrespondingSourceObject: ${targetSourceRef}
3629
+ insertIndex: -1
3630
+ addedObject: {fileID: ${newGoId}}`;
3631
+ let raw = piBlock.raw;
3632
+ const emptyPattern = /m_AddedGameObjects:\s*\[\]/;
3633
+ if (emptyPattern.test(raw)) {
3634
+ raw = raw.replace(emptyPattern, `m_AddedGameObjects:${entry}`);
3635
+ piBlock.replace_raw(raw);
3636
+ return;
3637
+ }
3638
+ const existingPattern = /(m_AddedGameObjects:\s*\n(?:\s+-[\s\S]*?(?=\n\s+m_|$))*)/;
3639
+ if (existingPattern.test(raw)) {
3640
+ raw = raw.replace(existingPattern, `$1${entry}`);
3641
+ piBlock.replace_raw(raw);
3642
+ }
3643
+ }
3644
+ var COMPONENT_DEFAULTS = {
3645
+ 20: ` serializedVersion: 2
2314
3646
  m_ClearFlags: 1
2315
3647
  m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
2316
3648
  m_projectionMatrixMode: 1
2317
- m_GateFitMode: 2
2318
3649
  m_FOVAxisMode: 0
2319
- m_Iso: 200
2320
- m_ShutterSpeed: 0.005
2321
- m_Aperture: 16
2322
- m_FocusDistance: 10
2323
- m_FocalLength: 50
2324
- m_BladeCount: 5
2325
- m_Curvature: {x: 2, y: 11}
2326
- m_BarrelClipping: 0.25
2327
- m_Anamorphism: 0
2328
- m_SensorSize: {x: 36, y: 24}
2329
- m_LensShift: {x: 0, y: 0}
2330
- m_NormalizedViewPortRect:
2331
- serializedVersion: 2
2332
- x: 0
2333
- y: 0
2334
- width: 1
2335
- height: 1
2336
3650
  near clip plane: 0.3
2337
3651
  far clip plane: 1000
2338
3652
  field of view: 60
2339
3653
  orthographic: 0
2340
3654
  orthographic size: 5
2341
- m_Depth: -1
2342
- m_CullingMask:
2343
- serializedVersion: 2
2344
- m_Bits: 4294967295
2345
- m_RenderingPath: -1
2346
- m_TargetTexture: {fileID: 0}
2347
- m_TargetDisplay: 0
2348
- m_TargetEye: 3
2349
- m_HDR: 1
2350
- m_AllowMSAA: 1
2351
- m_AllowDynamicResolution: 0
2352
- m_ForceIntoRT: 0
2353
- m_OcclusionCulling: 1
2354
- m_StereoConvergence: 10
2355
- m_StereoSeparation: 0.022
2356
- --- !u!81 &519420029
2357
- AudioListener:
3655
+ m_Depth: -1`,
3656
+ 23: ` m_CastShadows: 1
3657
+ m_ReceiveShadows: 1
3658
+ m_Materials:
3659
+ - {fileID: 0}`,
3660
+ 33: ` m_Mesh: {fileID: 0}`,
3661
+ 54: ` m_Mass: 1
3662
+ m_Drag: 0
3663
+ m_AngularDrag: 0.05
3664
+ m_UseGravity: 1
3665
+ m_IsKinematic: 0`,
3666
+ 64: ` m_IsTrigger: 0
3667
+ m_Convex: 0
3668
+ m_CookingOptions: 30
3669
+ m_Mesh: {fileID: 0}`,
3670
+ 65: ` m_IsTrigger: 0
3671
+ m_Material: {fileID: 0}
3672
+ m_Center: {x: 0, y: 0, z: 0}
3673
+ m_Size: {x: 1, y: 1, z: 1}`,
3674
+ 82: ` m_PlayOnAwake: 1
3675
+ m_Volume: 1
3676
+ m_Pitch: 1
3677
+ m_Loop: 0
3678
+ m_Mute: 0
3679
+ m_Priority: 128`,
3680
+ 96: ` m_CastShadows: 1
3681
+ m_ReceiveShadows: 1
3682
+ m_Materials:
3683
+ - {fileID: 0}
3684
+ m_Time: 5
3685
+ m_MinVertexDistance: 0.1`,
3686
+ 108: ` m_LightType: 1
3687
+ m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
3688
+ m_Intensity: 1
3689
+ m_Range: 10
3690
+ m_SpotAngle: 30
3691
+ m_Shadows: 2`,
3692
+ 111: ` m_Controller: {fileID: 0}`,
3693
+ 121: ` m_CastShadows: 1
3694
+ m_ReceiveShadows: 1
3695
+ m_Materials:
3696
+ - {fileID: 0}`,
3697
+ 135: ` m_IsTrigger: 0
3698
+ m_Material: {fileID: 0}
3699
+ m_Center: {x: 0, y: 0, z: 0}
3700
+ m_Radius: 0.5`,
3701
+ 136: ` m_IsTrigger: 0
3702
+ m_Material: {fileID: 0}
3703
+ m_Center: {x: 0, y: 0, z: 0}
3704
+ m_Radius: 0.5
3705
+ m_Height: 2
3706
+ m_Direction: 1`,
3707
+ 137: ` m_CastShadows: 1
3708
+ m_ReceiveShadows: 1
3709
+ m_Quality: 0
3710
+ m_Materials:
3711
+ - {fileID: 0}`,
3712
+ 143: ` m_Height: 2
3713
+ m_Radius: 0.5
3714
+ m_SlopeLimit: 45
3715
+ m_StepOffset: 0.3
3716
+ m_SkinWidth: 0.08
3717
+ m_Center: {x: 0, y: 0, z: 0}`,
3718
+ 198: ` m_PlayOnAwake: 1`,
3719
+ 205: ` serializedVersion: 2
3720
+ m_FadeMode: 0
3721
+ m_AnimateCrossFading: 0`,
3722
+ 212: ` m_CastShadows: 0
3723
+ m_ReceiveShadows: 0
3724
+ m_Materials:
3725
+ - {fileID: 0}
3726
+ m_Color: {r: 1, g: 1, b: 1, a: 1}`,
3727
+ 222: ``,
3728
+ 223: ` m_RenderMode: 0
3729
+ m_PixelPerfect: 0
3730
+ m_SortingOrder: 0`,
3731
+ 225: ` m_Alpha: 1
3732
+ m_Interactable: 1
3733
+ m_BlocksRaycasts: 1
3734
+ m_IgnoreParentGroups: 0`
3735
+ };
3736
+ function createGenericComponentYAML(componentName, classId, componentId, gameObjectId) {
3737
+ const defaults = COMPONENT_DEFAULTS[classId] ? `
3738
+ ` + COMPONENT_DEFAULTS[classId] + `
3739
+ ` : "";
3740
+ return `--- !u!${classId} &${componentId}
3741
+ ${componentName}:
2358
3742
  m_ObjectHideFlags: 0
2359
3743
  m_CorrespondingSourceObject: {fileID: 0}
2360
3744
  m_PrefabInstance: {fileID: 0}
2361
3745
  m_PrefabAsset: {fileID: 0}
2362
- m_GameObject: {fileID: 519420028}
3746
+ m_GameObject: {fileID: ${gameObjectId}}
2363
3747
  m_Enabled: 1
2364
- --- !u!1 &705507993
3748
+ ${defaults}`;
3749
+ }
3750
+ function addComponentToGameObject(doc, gameObjectId, componentId) {
3751
+ const goBlock = doc.find_by_file_id(gameObjectId);
3752
+ if (!goBlock)
3753
+ return;
3754
+ let raw = goBlock.raw;
3755
+ raw = raw.replace(/(m_Component:\s*\n(?:\s*-\s*component:\s*\{fileID:\s*\d+\}\s*\n)*)/, `$1 - component: {fileID: ${componentId}}
3756
+ `);
3757
+ goBlock.replace_raw(raw);
3758
+ }
3759
+ function createMonoBehaviourYAML(componentId, gameObjectId, scriptGuid, fields, version) {
3760
+ const field_yaml = fields && fields.length > 0 ? generate_field_yaml(fields, version) : `
3761
+ `;
3762
+ return `--- !u!114 &${componentId}
3763
+ MonoBehaviour:
3764
+ m_ObjectHideFlags: 0
3765
+ m_CorrespondingSourceObject: {fileID: 0}
3766
+ m_PrefabInstance: {fileID: 0}
3767
+ m_PrefabAsset: {fileID: 0}
3768
+ m_GameObject: {fileID: ${gameObjectId}}
3769
+ m_Enabled: 1
3770
+ m_EditorHideFlags: 0
3771
+ m_Script: {fileID: 11500000, guid: ${scriptGuid}, type: 3}
3772
+ m_Name:
3773
+ m_EditorClassIdentifier:${field_yaml}`;
3774
+ }
3775
+ function createGameObject(options) {
3776
+ const { file_path, name, parent } = options;
3777
+ const pathError = validate_file_path(file_path, "write");
3778
+ if (pathError) {
3779
+ return { success: false, file_path, error: pathError };
3780
+ }
3781
+ if (!name || name.trim() === "") {
3782
+ return {
3783
+ success: false,
3784
+ file_path,
3785
+ error: "GameObject name cannot be empty"
3786
+ };
3787
+ }
3788
+ const nameError = validate_name(name, "GameObject name");
3789
+ if (nameError) {
3790
+ return { success: false, file_path, error: nameError };
3791
+ }
3792
+ if (!import_fs10.existsSync(file_path)) {
3793
+ return {
3794
+ success: false,
3795
+ file_path,
3796
+ error: `File not found: ${file_path}`
3797
+ };
3798
+ }
3799
+ let doc;
3800
+ try {
3801
+ doc = UnityDocument.from_file(file_path);
3802
+ } catch (err) {
3803
+ return {
3804
+ success: false,
3805
+ file_path,
3806
+ error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
3807
+ };
3808
+ }
3809
+ let parentTransformIdStr = "0";
3810
+ if (parent !== undefined) {
3811
+ if (typeof parent === "number") {
3812
+ const parentIdStr = String(parent);
3813
+ const parentBlock = doc.find_by_file_id(parentIdStr);
3814
+ if (!parentBlock) {
3815
+ return {
3816
+ success: false,
3817
+ file_path,
3818
+ error: `Parent with fileID ${parent} not found. Provide a GameObject or Transform fileID.`
3819
+ };
3820
+ }
3821
+ if (parentBlock.class_id === 4) {
3822
+ parentTransformIdStr = parentIdStr;
3823
+ } else if (parentBlock.class_id === 1) {
3824
+ const compMatch = parentBlock.raw.match(/m_Component:\s*\n\s*-\s*component:\s*\{fileID:\s*(\d+)\}/);
3825
+ if (compMatch) {
3826
+ parentTransformIdStr = compMatch[1];
3827
+ } else {
3828
+ return {
3829
+ success: false,
3830
+ file_path,
3831
+ error: `Parent with fileID ${parent} not found. Provide a GameObject or Transform fileID.`
3832
+ };
3833
+ }
3834
+ } else {
3835
+ return {
3836
+ success: false,
3837
+ file_path,
3838
+ error: `Parent with fileID ${parent} is not a GameObject or Transform.`
3839
+ };
3840
+ }
3841
+ } else {
3842
+ const foundResult = findTransformIdByName(doc, parent);
3843
+ if (foundResult === null) {
3844
+ return {
3845
+ success: false,
3846
+ file_path,
3847
+ error: `Parent GameObject "${parent}" not found`
3848
+ };
3849
+ }
3850
+ if (typeof foundResult === "object") {
3851
+ return { success: false, file_path, error: foundResult.error };
3852
+ }
3853
+ parentTransformIdStr = foundResult;
3854
+ }
3855
+ }
3856
+ let variantPiId;
3857
+ let strippedSourceRef;
3858
+ const prefabInstanceBlocks = doc.find_by_class_id(1001);
3859
+ if (prefabInstanceBlocks.length > 0 && parentTransformIdStr === "0") {
3860
+ const piId = prefabInstanceBlocks[0].file_id;
3861
+ const strippedInfo = findStrippedRootTransform(doc, piId);
3862
+ if (strippedInfo) {
3863
+ parentTransformIdStr = strippedInfo.transformId;
3864
+ strippedSourceRef = strippedInfo.sourceRef;
3865
+ variantPiId = piId;
3866
+ }
3867
+ }
3868
+ const rootOrder = doc.calculate_root_order(parentTransformIdStr);
3869
+ const layer = get_layer_from_parent(doc, parentTransformIdStr);
3870
+ const gameObjectIdStr = doc.generate_file_id();
3871
+ const transformIdStr = doc.generate_file_id();
3872
+ const gameObjectId = parseInt(gameObjectIdStr, 10);
3873
+ const transformId = parseInt(transformIdStr, 10);
3874
+ const parentTransformId = parseInt(parentTransformIdStr, 10);
3875
+ const newBlocks = createGameObjectYAML(gameObjectId, transformId, name.trim(), parentTransformId, rootOrder, layer);
3876
+ doc.append_raw(newBlocks);
3877
+ if (parentTransformIdStr !== "0" && !variantPiId) {
3878
+ doc.add_child_to_parent(parentTransformIdStr, transformIdStr);
3879
+ }
3880
+ if (variantPiId && strippedSourceRef) {
3881
+ appendToAddedGameObjects(doc, variantPiId, strippedSourceRef, gameObjectIdStr);
3882
+ }
3883
+ const saveResult = doc.save();
3884
+ if (!saveResult.success) {
3885
+ return {
3886
+ success: false,
3887
+ file_path,
3888
+ error: saveResult.error
3889
+ };
3890
+ }
3891
+ return {
3892
+ success: true,
3893
+ file_path,
3894
+ game_object_id: gameObjectId,
3895
+ transform_id: transformId,
3896
+ prefab_instance_id: variantPiId ? parseInt(variantPiId, 10) : undefined
3897
+ };
3898
+ }
3899
+ function createScene(options) {
3900
+ const { output_path, include_defaults, scene_guid } = options;
3901
+ const pathError = validate_file_path(output_path, "write");
3902
+ if (pathError) {
3903
+ return { success: false, output_path, error: pathError };
3904
+ }
3905
+ if (!output_path.endsWith(".unity")) {
3906
+ return {
3907
+ success: false,
3908
+ output_path,
3909
+ error: "Output path must have .unity extension"
3910
+ };
3911
+ }
3912
+ if (import_fs10.existsSync(output_path)) {
3913
+ return {
3914
+ success: false,
3915
+ output_path,
3916
+ error: `File already exists: ${output_path}. Delete it first or choose a different path.`
3917
+ };
3918
+ }
3919
+ const guid = scene_guid || generateGuid();
3920
+ let yaml = `%YAML 1.1
3921
+ %TAG !u! tag:unity3d.com,2011:
3922
+ --- !u!29 &1
3923
+ OcclusionCullingSettings:
3924
+ m_ObjectHideFlags: 0
3925
+ serializedVersion: 2
3926
+ m_OcclusionBakeSettings:
3927
+ smallestOccluder: 5
3928
+ smallestHole: 0.25
3929
+ backfaceThreshold: 100
3930
+ m_SceneGUID: 00000000000000000000000000000000
3931
+ m_OcclusionCullingData: {fileID: 0}
3932
+ --- !u!104 &2
3933
+ RenderSettings:
3934
+ m_ObjectHideFlags: 0
3935
+ serializedVersion: 9
3936
+ m_Fog: 0
3937
+ m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
3938
+ m_FogMode: 3
3939
+ m_FogDensity: 0.01
3940
+ m_LinearFogStart: 0
3941
+ m_LinearFogEnd: 300
3942
+ m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
3943
+ m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
3944
+ m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
3945
+ m_AmbientIntensity: 1
3946
+ m_AmbientMode: 0
3947
+ m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
3948
+ m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
3949
+ m_HaloStrength: 0.5
3950
+ m_FlareStrength: 1
3951
+ m_FlareFadeSpeed: 3
3952
+ m_HaloTexture: {fileID: 0}
3953
+ m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
3954
+ m_DefaultReflectionMode: 0
3955
+ m_DefaultReflectionResolution: 128
3956
+ m_ReflectionBounces: 1
3957
+ m_ReflectionIntensity: 1
3958
+ m_CustomReflection: {fileID: 0}
3959
+ m_Sun: {fileID: 0}
3960
+ m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1}
3961
+ m_UseRadianceAmbientProbe: 0
3962
+ --- !u!157 &3
3963
+ LightmapSettings:
3964
+ m_ObjectHideFlags: 0
3965
+ serializedVersion: 12
3966
+ m_GIWorkflowMode: 1
3967
+ m_GISettings:
3968
+ serializedVersion: 2
3969
+ m_BounceScale: 1
3970
+ m_IndirectOutputScale: 1
3971
+ m_AlbedoBoost: 1
3972
+ m_EnvironmentLightingMode: 0
3973
+ m_EnableBakedLightmaps: 1
3974
+ m_EnableRealtimeLightmaps: 0
3975
+ m_LightmapEditorSettings:
3976
+ serializedVersion: 12
3977
+ m_Resolution: 2
3978
+ m_BakeResolution: 40
3979
+ m_AtlasSize: 1024
3980
+ m_AO: 0
3981
+ m_AOMaxDistance: 1
3982
+ m_CompAOExponent: 1
3983
+ m_CompAOExponentDirect: 0
3984
+ m_ExtractAmbientOcclusion: 0
3985
+ m_Padding: 2
3986
+ m_LightmapParameters: {fileID: 0}
3987
+ m_LightmapsBakeMode: 1
3988
+ m_TextureCompression: 1
3989
+ m_FinalGather: 0
3990
+ m_FinalGatherFiltering: 1
3991
+ m_FinalGatherRayCount: 256
3992
+ m_ReflectionCompression: 2
3993
+ m_MixedBakeMode: 2
3994
+ m_BakeBackend: 1
3995
+ m_PVRSampling: 1
3996
+ m_PVRDirectSampleCount: 32
3997
+ m_PVRSampleCount: 512
3998
+ m_PVRBounces: 2
3999
+ m_PVREnvironmentSampleCount: 256
4000
+ m_PVREnvironmentReferencePointCount: 2048
4001
+ m_PVRFilteringMode: 1
4002
+ m_PVRDenoiserTypeDirect: 1
4003
+ m_PVRDenoiserTypeIndirect: 1
4004
+ m_PVRDenoiserTypeAO: 1
4005
+ m_PVRFilterTypeDirect: 0
4006
+ m_PVRFilterTypeIndirect: 0
4007
+ m_PVRFilterTypeAO: 0
4008
+ m_PVREnvironmentMIS: 1
4009
+ m_PVRCulling: 1
4010
+ m_PVRFilteringGaussRadiusDirect: 1
4011
+ m_PVRFilteringGaussRadiusIndirect: 5
4012
+ m_PVRFilteringGaussRadiusAO: 2
4013
+ m_PVRFilteringAtrousPositionSigmaDirect: 0.5
4014
+ m_PVRFilteringAtrousPositionSigmaIndirect: 2
4015
+ m_PVRFilteringAtrousPositionSigmaAO: 1
4016
+ m_ExportTrainingData: 0
4017
+ m_TrainingDataDestination: TrainingData
4018
+ m_LightProbeSampleCountMultiplier: 4
4019
+ m_LightingDataAsset: {fileID: 0}
4020
+ m_LightingSettings: {fileID: 0}
4021
+ --- !u!196 &4
4022
+ NavMeshSettings:
4023
+ serializedVersion: 2
4024
+ m_ObjectHideFlags: 0
4025
+ m_BuildSettings:
4026
+ serializedVersion: 3
4027
+ agentTypeID: 0
4028
+ agentRadius: 0.5
4029
+ agentHeight: 2
4030
+ agentSlope: 45
4031
+ agentClimb: 0.4
4032
+ ledgeDropHeight: 0
4033
+ maxJumpAcrossDistance: 0
4034
+ minRegionArea: 2
4035
+ manualCellSize: 0
4036
+ cellSize: 0.16666667
4037
+ manualTileSize: 0
4038
+ tileSize: 256
4039
+ buildHeightMesh: 0
4040
+ maxJobWorkers: 0
4041
+ preserveTilesOutsideBounds: 0
4042
+ debug:
4043
+ m_Flags: 0
4044
+ m_NavMeshData: {fileID: 0}
4045
+ `;
4046
+ if (include_defaults) {
4047
+ yaml += `--- !u!1 &519420028
2365
4048
  GameObject:
2366
4049
  m_ObjectHideFlags: 0
2367
4050
  m_CorrespondingSourceObject: {fileID: 0}
@@ -2369,50 +4052,143 @@ GameObject:
2369
4052
  m_PrefabAsset: {fileID: 0}
2370
4053
  serializedVersion: 6
2371
4054
  m_Component:
2372
- - component: {fileID: 705507995}
2373
- - component: {fileID: 705507994}
4055
+ - component: {fileID: 519420032}
4056
+ - component: {fileID: 519420031}
4057
+ - component: {fileID: 519420029}
2374
4058
  m_Layer: 0
2375
- m_Name: Directional Light
2376
- m_TagString: Untagged
4059
+ m_Name: Main Camera
4060
+ m_TagString: MainCamera
2377
4061
  m_Icon: {fileID: 0}
2378
4062
  m_NavMeshLayer: 0
2379
4063
  m_StaticEditorFlags: 0
2380
4064
  m_IsActive: 1
2381
- --- !u!4 &705507995
4065
+ --- !u!4 &519420032
2382
4066
  Transform:
2383
4067
  m_ObjectHideFlags: 0
2384
4068
  m_CorrespondingSourceObject: {fileID: 0}
2385
4069
  m_PrefabInstance: {fileID: 0}
2386
4070
  m_PrefabAsset: {fileID: 0}
2387
- m_GameObject: {fileID: 705507993}
4071
+ m_GameObject: {fileID: 519420028}
2388
4072
  serializedVersion: 2
2389
- m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
2390
- m_LocalPosition: {x: 0, y: 3, z: 0}
4073
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
4074
+ m_LocalPosition: {x: 0, y: 1, z: -10}
2391
4075
  m_LocalScale: {x: 1, y: 1, z: 1}
2392
4076
  m_ConstrainProportionsScale: 0
2393
4077
  m_Children: []
2394
4078
  m_Father: {fileID: 0}
2395
- m_RootOrder: 1
2396
- m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
2397
- --- !u!108 &705507994
2398
- Light:
4079
+ m_RootOrder: 0
4080
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
4081
+ --- !u!20 &519420031
4082
+ Camera:
2399
4083
  m_ObjectHideFlags: 0
2400
4084
  m_CorrespondingSourceObject: {fileID: 0}
2401
4085
  m_PrefabInstance: {fileID: 0}
2402
4086
  m_PrefabAsset: {fileID: 0}
2403
- m_GameObject: {fileID: 705507993}
4087
+ m_GameObject: {fileID: 519420028}
2404
4088
  m_Enabled: 1
2405
- serializedVersion: 10
2406
- m_Type: 1
2407
- m_Shape: 0
2408
- m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
2409
- m_Intensity: 1
2410
- m_Range: 10
2411
- m_SpotAngle: 30
2412
- m_InnerSpotAngle: 21.80208
2413
- m_CookieSize: 10
2414
- m_Shadows:
2415
- m_Type: 2
4089
+ serializedVersion: 2
4090
+ m_ClearFlags: 1
4091
+ m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
4092
+ m_projectionMatrixMode: 1
4093
+ m_GateFitMode: 2
4094
+ m_FOVAxisMode: 0
4095
+ m_Iso: 200
4096
+ m_ShutterSpeed: 0.005
4097
+ m_Aperture: 16
4098
+ m_FocusDistance: 10
4099
+ m_FocalLength: 50
4100
+ m_BladeCount: 5
4101
+ m_Curvature: {x: 2, y: 11}
4102
+ m_BarrelClipping: 0.25
4103
+ m_Anamorphism: 0
4104
+ m_SensorSize: {x: 36, y: 24}
4105
+ m_LensShift: {x: 0, y: 0}
4106
+ m_NormalizedViewPortRect:
4107
+ serializedVersion: 2
4108
+ x: 0
4109
+ y: 0
4110
+ width: 1
4111
+ height: 1
4112
+ near clip plane: 0.3
4113
+ far clip plane: 1000
4114
+ field of view: 60
4115
+ orthographic: 0
4116
+ orthographic size: 5
4117
+ m_Depth: -1
4118
+ m_CullingMask:
4119
+ serializedVersion: 2
4120
+ m_Bits: 4294967295
4121
+ m_RenderingPath: -1
4122
+ m_TargetTexture: {fileID: 0}
4123
+ m_TargetDisplay: 0
4124
+ m_TargetEye: 3
4125
+ m_HDR: 1
4126
+ m_AllowMSAA: 1
4127
+ m_AllowDynamicResolution: 0
4128
+ m_ForceIntoRT: 0
4129
+ m_OcclusionCulling: 1
4130
+ m_StereoConvergence: 10
4131
+ m_StereoSeparation: 0.022
4132
+ --- !u!81 &519420029
4133
+ AudioListener:
4134
+ m_ObjectHideFlags: 0
4135
+ m_CorrespondingSourceObject: {fileID: 0}
4136
+ m_PrefabInstance: {fileID: 0}
4137
+ m_PrefabAsset: {fileID: 0}
4138
+ m_GameObject: {fileID: 519420028}
4139
+ m_Enabled: 1
4140
+ --- !u!1 &705507993
4141
+ GameObject:
4142
+ m_ObjectHideFlags: 0
4143
+ m_CorrespondingSourceObject: {fileID: 0}
4144
+ m_PrefabInstance: {fileID: 0}
4145
+ m_PrefabAsset: {fileID: 0}
4146
+ serializedVersion: 6
4147
+ m_Component:
4148
+ - component: {fileID: 705507995}
4149
+ - component: {fileID: 705507994}
4150
+ m_Layer: 0
4151
+ m_Name: Directional Light
4152
+ m_TagString: Untagged
4153
+ m_Icon: {fileID: 0}
4154
+ m_NavMeshLayer: 0
4155
+ m_StaticEditorFlags: 0
4156
+ m_IsActive: 1
4157
+ --- !u!4 &705507995
4158
+ Transform:
4159
+ m_ObjectHideFlags: 0
4160
+ m_CorrespondingSourceObject: {fileID: 0}
4161
+ m_PrefabInstance: {fileID: 0}
4162
+ m_PrefabAsset: {fileID: 0}
4163
+ m_GameObject: {fileID: 705507993}
4164
+ serializedVersion: 2
4165
+ m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
4166
+ m_LocalPosition: {x: 0, y: 3, z: 0}
4167
+ m_LocalScale: {x: 1, y: 1, z: 1}
4168
+ m_ConstrainProportionsScale: 0
4169
+ m_Children: []
4170
+ m_Father: {fileID: 0}
4171
+ m_RootOrder: 1
4172
+ m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
4173
+ --- !u!108 &705507994
4174
+ Light:
4175
+ m_ObjectHideFlags: 0
4176
+ m_CorrespondingSourceObject: {fileID: 0}
4177
+ m_PrefabInstance: {fileID: 0}
4178
+ m_PrefabAsset: {fileID: 0}
4179
+ m_GameObject: {fileID: 705507993}
4180
+ m_Enabled: 1
4181
+ serializedVersion: 10
4182
+ m_Type: 1
4183
+ m_Shape: 0
4184
+ m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
4185
+ m_Intensity: 1
4186
+ m_Range: 10
4187
+ m_SpotAngle: 30
4188
+ m_InnerSpotAngle: 21.80208
4189
+ m_CookieSize: 10
4190
+ m_Shadows:
4191
+ m_Type: 2
2416
4192
  m_Resolution: -1
2417
4193
  m_CustomResolution: -1
2418
4194
  m_Strength: 1
@@ -2459,7 +4235,7 @@ Light:
2459
4235
  `;
2460
4236
  }
2461
4237
  try {
2462
- import_fs7.writeFileSync(output_path, yaml, "utf-8");
4238
+ import_fs10.writeFileSync(output_path, yaml, "utf-8");
2463
4239
  } catch (err) {
2464
4240
  return {
2465
4241
  success: false,
@@ -2477,11 +4253,11 @@ DefaultImporter:
2477
4253
  `;
2478
4254
  const metaPath = output_path + ".meta";
2479
4255
  try {
2480
- import_fs7.writeFileSync(metaPath, metaContent, "utf-8");
4256
+ import_fs10.writeFileSync(metaPath, metaContent, "utf-8");
2481
4257
  } catch (err) {
2482
4258
  try {
2483
- const fs = require("fs");
2484
- fs.unlinkSync(output_path);
4259
+ const fs2 = require("fs");
4260
+ fs2.unlinkSync(output_path);
2485
4261
  } catch {}
2486
4262
  return {
2487
4263
  success: false,
@@ -2496,4 +4272,2049 @@ DefaultImporter:
2496
4272
  meta_path: metaPath
2497
4273
  };
2498
4274
  }
4275
+ function createPrefabVariant(options) {
4276
+ const { source_prefab, output_path, variant_name } = options;
4277
+ if (!import_fs10.existsSync(source_prefab)) {
4278
+ return {
4279
+ success: false,
4280
+ output_path,
4281
+ error: `Source prefab not found: ${source_prefab}`
4282
+ };
4283
+ }
4284
+ if (!source_prefab.endsWith(".prefab")) {
4285
+ return {
4286
+ success: false,
4287
+ output_path,
4288
+ error: "Source file must be a .prefab file"
4289
+ };
4290
+ }
4291
+ if (!output_path.endsWith(".prefab")) {
4292
+ return {
4293
+ success: false,
4294
+ output_path,
4295
+ error: "Output path must have .prefab extension"
4296
+ };
4297
+ }
4298
+ if (import_fs10.existsSync(output_path)) {
4299
+ return {
4300
+ success: false,
4301
+ output_path,
4302
+ error: `File already exists: ${output_path}. Delete it first or choose a different path.`
4303
+ };
4304
+ }
4305
+ if (import_fs10.existsSync(output_path + ".meta")) {
4306
+ return {
4307
+ success: false,
4308
+ output_path,
4309
+ error: `Meta file already exists: ${output_path}.meta. Delete it first or choose a different path.`
4310
+ };
4311
+ }
4312
+ const metaPath = source_prefab + ".meta";
4313
+ const sourceGuid = extractGuidFromMeta(metaPath);
4314
+ if (!sourceGuid) {
4315
+ return {
4316
+ success: false,
4317
+ output_path,
4318
+ error: `Could not find or read .meta file for source prefab: ${metaPath}`
4319
+ };
4320
+ }
4321
+ let sourceDoc;
4322
+ try {
4323
+ sourceDoc = UnityDocument.from_file(source_prefab);
4324
+ } catch (err) {
4325
+ return {
4326
+ success: false,
4327
+ output_path,
4328
+ error: `Failed to read source prefab: ${err instanceof Error ? err.message : String(err)}`
4329
+ };
4330
+ }
4331
+ const rootInfo = sourceDoc.find_prefab_root();
4332
+ if (!rootInfo) {
4333
+ return {
4334
+ success: false,
4335
+ output_path,
4336
+ error: "Could not find root GameObject in source prefab"
4337
+ };
4338
+ }
4339
+ const tempDoc = UnityDocument.from_string(`%YAML 1.1
4340
+ %TAG !u! tag:unity3d.com,2011:
4341
+ `);
4342
+ const prefabInstanceId = parseInt(tempDoc.generate_file_id(), 10);
4343
+ const strippedGoId = parseInt(tempDoc.generate_file_id(), 10);
4344
+ const strippedTransformId = parseInt(tempDoc.generate_file_id(), 10);
4345
+ const finalName = variant_name || `${rootInfo.name} Variant`;
4346
+ const variantYaml = `%YAML 1.1
4347
+ %TAG !u! tag:unity3d.com,2011:
4348
+ --- !u!1 &${strippedGoId} stripped
4349
+ GameObject:
4350
+ m_CorrespondingSourceObject: {fileID: ${rootInfo.game_object.file_id}, guid: ${sourceGuid}, type: 3}
4351
+ m_PrefabInstance: {fileID: ${prefabInstanceId}}
4352
+ m_PrefabAsset: {fileID: 0}
4353
+ --- !u!4 &${strippedTransformId} stripped
4354
+ Transform:
4355
+ m_CorrespondingSourceObject: {fileID: ${rootInfo.transform.file_id}, guid: ${sourceGuid}, type: 3}
4356
+ m_PrefabInstance: {fileID: ${prefabInstanceId}}
4357
+ m_PrefabAsset: {fileID: 0}
4358
+ --- !u!1001 &${prefabInstanceId}
4359
+ PrefabInstance:
4360
+ m_ObjectHideFlags: 0
4361
+ serializedVersion: 2
4362
+ m_Modification:
4363
+ m_TransformParent: {fileID: 0}
4364
+ m_Modifications:
4365
+ - target: {fileID: ${rootInfo.game_object.file_id}, guid: ${sourceGuid}, type: 3}
4366
+ propertyPath: m_Name
4367
+ value: ${finalName}
4368
+ objectReference: {fileID: 0}
4369
+ m_RemovedComponents: []
4370
+ m_RemovedGameObjects: []
4371
+ m_AddedGameObjects: []
4372
+ m_AddedComponents: []
4373
+ m_SourcePrefab: {fileID: 100100000, guid: ${sourceGuid}, type: 3}
4374
+ `;
4375
+ try {
4376
+ import_fs10.writeFileSync(output_path, variantYaml, "utf-8");
4377
+ } catch (err) {
4378
+ return {
4379
+ success: false,
4380
+ output_path,
4381
+ error: `Failed to write variant prefab: ${err instanceof Error ? err.message : String(err)}`
4382
+ };
4383
+ }
4384
+ const variantGuid = generateGuid();
4385
+ const variantMetaContent = `fileFormatVersion: 2
4386
+ guid: ${variantGuid}
4387
+ PrefabImporter:
4388
+ externalObjects: {}
4389
+ userData:
4390
+ assetBundleName:
4391
+ assetBundleVariant:
4392
+ `;
4393
+ try {
4394
+ import_fs10.writeFileSync(output_path + ".meta", variantMetaContent, "utf-8");
4395
+ } catch (err) {
4396
+ try {
4397
+ const fs2 = require("fs");
4398
+ fs2.unlinkSync(output_path);
4399
+ } catch {}
4400
+ return {
4401
+ success: false,
4402
+ output_path,
4403
+ error: `Failed to write .meta file: ${err instanceof Error ? err.message : String(err)}`
4404
+ };
4405
+ }
4406
+ return {
4407
+ success: true,
4408
+ output_path,
4409
+ source_guid: sourceGuid,
4410
+ prefab_instance_id: prefabInstanceId
4411
+ };
4412
+ }
4413
+ function createScriptableObject(options) {
4414
+ const { output_path, script } = options;
4415
+ const project_path = options.project_path || find_unity_project_root(path5.dirname(output_path)) || undefined;
4416
+ if (!output_path.endsWith(".asset")) {
4417
+ return { success: false, output_path, error: "Output path must have .asset extension" };
4418
+ }
4419
+ if (import_fs10.existsSync(output_path)) {
4420
+ return { success: false, output_path, error: `File already exists: ${output_path}. Delete it first or choose a different path.` };
4421
+ }
4422
+ if (import_fs10.existsSync(output_path + ".meta")) {
4423
+ return { success: false, output_path, error: `Meta file already exists: ${output_path}.meta. Delete it first or choose a different path.` };
4424
+ }
4425
+ const builtInClassId = get_class_id(script);
4426
+ if (builtInClassId !== null) {
4427
+ return { success: false, output_path, error: `"${script}" is a built-in Unity class (class ${builtInClassId}), not a custom script. ScriptableObjects require a custom script that derives from ScriptableObject. Provide a script GUID, .cs file path, or script name with --project.` };
4428
+ }
4429
+ const resolved = resolve_script_with_fields(script, project_path);
4430
+ if (!resolved) {
4431
+ const hints = [];
4432
+ if (project_path) {
4433
+ const cacheExists = load_guid_cache(project_path) !== null;
4434
+ const registryExists = import_fs10.existsSync(path5.join(project_path, ".unity-agentic", "type-registry.json"));
4435
+ if (!cacheExists && !registryExists) {
4436
+ hints.push(`No GUID cache or type registry found at ${path5.join(project_path, ".unity-agentic/")}. Run "unity-agentic-tools setup" first.`);
4437
+ } else if (!registryExists) {
4438
+ hints.push('Type registry not found. Re-run "unity-agentic-tools setup" to rebuild.');
4439
+ }
4440
+ } else {
4441
+ hints.push("No Unity project detected. Provide --project or run from inside a Unity project directory.");
4442
+ }
4443
+ return { success: false, output_path, error: `Script not found: "${script}". Provide a GUID, script path, or script name.${hints.length > 0 ? " " + hints.join(" ") : ""}` };
4444
+ }
4445
+ if (resolved.kind === "enum" || resolved.kind === "interface") {
4446
+ return { success: false, output_path, error: `"${script}" is ${resolved.kind === "enum" ? "an enum" : "an interface"}, not a ScriptableObject.` };
4447
+ }
4448
+ if (resolved.base_class && resolved.base_class !== "ScriptableObject") {
4449
+ return { success: false, output_path, error: `"${script}" extends ${resolved.base_class}, not ScriptableObject. Cannot create as a ScriptableObject asset.` };
4450
+ }
4451
+ let version;
4452
+ if (project_path) {
4453
+ try {
4454
+ version = read_project_version(project_path);
4455
+ } catch {}
4456
+ }
4457
+ const baseName = path5.basename(output_path, ".asset");
4458
+ const field_yaml = resolved.fields && resolved.fields.length > 0 ? generate_field_yaml(resolved.fields, version) : `
4459
+ `;
4460
+ const assetYaml = `%YAML 1.1
4461
+ %TAG !u! tag:unity3d.com,2011:
4462
+ --- !u!114 &11400000
4463
+ MonoBehaviour:
4464
+ m_ObjectHideFlags: 0
4465
+ m_CorrespondingSourceObject: {fileID: 0}
4466
+ m_PrefabInstance: {fileID: 0}
4467
+ m_PrefabAsset: {fileID: 0}
4468
+ m_GameObject: {fileID: 0}
4469
+ m_Enabled: 1
4470
+ m_EditorHideFlags: 0
4471
+ m_Script: {fileID: 11500000, guid: ${resolved.guid}, type: 3}
4472
+ m_Name: ${baseName}
4473
+ m_EditorClassIdentifier:${field_yaml}`;
4474
+ try {
4475
+ import_fs10.writeFileSync(output_path, assetYaml, "utf-8");
4476
+ } catch (err) {
4477
+ return { success: false, output_path, error: `Failed to write asset file: ${err instanceof Error ? err.message : String(err)}` };
4478
+ }
4479
+ const assetGuid = generateGuid();
4480
+ const metaContent = `fileFormatVersion: 2
4481
+ guid: ${assetGuid}
4482
+ NativeFormatImporter:
4483
+ externalObjects: {}
4484
+ mainObjectFileID: 11400000
4485
+ userData:
4486
+ assetBundleName:
4487
+ assetBundleVariant:
4488
+ `;
4489
+ try {
4490
+ import_fs10.writeFileSync(output_path + ".meta", metaContent, "utf-8");
4491
+ } catch (err) {
4492
+ try {
4493
+ const fs2 = require("fs");
4494
+ fs2.unlinkSync(output_path);
4495
+ } catch {}
4496
+ return { success: false, output_path, error: `Failed to write .meta file: ${err instanceof Error ? err.message : String(err)}` };
4497
+ }
4498
+ const result = {
4499
+ success: true,
4500
+ output_path,
4501
+ script_guid: resolved.guid,
4502
+ asset_guid: assetGuid
4503
+ };
4504
+ if (resolved.extraction_error) {
4505
+ result.warning = resolved.extraction_error;
4506
+ }
4507
+ return result;
4508
+ }
4509
+ function createMetaFile(options) {
4510
+ const { script_path } = options;
4511
+ const metaPath = script_path + ".meta";
4512
+ if (!import_fs10.existsSync(script_path)) {
4513
+ return {
4514
+ success: false,
4515
+ meta_path: metaPath,
4516
+ error: `Source file not found: ${script_path}`
4517
+ };
4518
+ }
4519
+ if (import_fs10.existsSync(metaPath)) {
4520
+ return {
4521
+ success: false,
4522
+ meta_path: metaPath,
4523
+ error: `.meta file already exists: ${metaPath}`
4524
+ };
4525
+ }
4526
+ const guid = generateGuid();
4527
+ const metaContent = `fileFormatVersion: 2
4528
+ guid: ${guid}
4529
+ MonoImporter:
4530
+ externalObjects: {}
4531
+ serializedVersion: 2
4532
+ defaultReferences: []
4533
+ executionOrder: 0
4534
+ icon: {instanceID: 0}
4535
+ userData:
4536
+ assetBundleName:
4537
+ assetBundleVariant:
4538
+ `;
4539
+ try {
4540
+ import_fs10.writeFileSync(metaPath, metaContent, "utf-8");
4541
+ } catch (err) {
4542
+ return {
4543
+ success: false,
4544
+ meta_path: metaPath,
4545
+ error: `Failed to write .meta file: ${err instanceof Error ? err.message : String(err)}`
4546
+ };
4547
+ }
4548
+ return {
4549
+ success: true,
4550
+ meta_path: metaPath,
4551
+ guid
4552
+ };
4553
+ }
4554
+ function addComponent(options) {
4555
+ const { file_path, game_object_name, component_type } = options;
4556
+ const project_path = options.project_path || find_unity_project_root(path5.dirname(file_path)) || undefined;
4557
+ const pathError = validate_file_path(file_path, "write");
4558
+ if (pathError) {
4559
+ return { success: false, file_path, error: pathError };
4560
+ }
4561
+ if (!import_fs10.existsSync(file_path)) {
4562
+ return {
4563
+ success: false,
4564
+ file_path,
4565
+ error: `File not found: ${file_path}`
4566
+ };
4567
+ }
4568
+ let doc;
4569
+ try {
4570
+ doc = UnityDocument.from_file(file_path);
4571
+ } catch (err) {
4572
+ return {
4573
+ success: false,
4574
+ file_path,
4575
+ error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
4576
+ };
4577
+ }
4578
+ const goResult = doc.require_unique_game_object(game_object_name);
4579
+ if ("error" in goResult) {
4580
+ return { success: false, file_path, error: goResult.error };
4581
+ }
4582
+ const gameObjectIdStr = goResult.file_id;
4583
+ const gameObjectId = parseInt(gameObjectIdStr, 10);
4584
+ const classId = get_class_id(component_type);
4585
+ let duplicateWarning;
4586
+ const goBlock = doc.find_by_file_id(gameObjectIdStr);
4587
+ if (goBlock && classId !== null) {
4588
+ const compRefs = [...goBlock.raw.matchAll(/component:\s*\{fileID:\s*(\d+)\}/g)].map((m) => m[1]);
4589
+ for (const refId of compRefs) {
4590
+ const compBlock = doc.find_by_file_id(refId);
4591
+ if (compBlock && compBlock.class_id === classId) {
4592
+ duplicateWarning = `GameObject already has a ${component_type} component (fileID: ${refId}). Adding duplicate.`;
4593
+ break;
4594
+ }
4595
+ }
4596
+ }
4597
+ const componentIdStr = doc.generate_file_id();
4598
+ const componentId = parseInt(componentIdStr, 10);
4599
+ let componentYAML;
4600
+ let scriptGuid;
4601
+ let scriptPath;
4602
+ let extractionError;
4603
+ if (classId !== null) {
4604
+ const componentName = UNITY_CLASS_IDS[classId] || component_type;
4605
+ componentYAML = createGenericComponentYAML(componentName, classId, componentId, gameObjectId);
4606
+ } else {
4607
+ const resolved = resolve_script_with_fields(component_type, project_path);
4608
+ if (!resolved) {
4609
+ const hints = [];
4610
+ if (project_path) {
4611
+ const cacheExists = load_guid_cache(project_path) !== null;
4612
+ const registryExists = import_fs10.existsSync(path5.join(project_path, ".unity-agentic", "type-registry.json"));
4613
+ if (!cacheExists && !registryExists) {
4614
+ hints.push(`No GUID cache or type registry found at ${path5.join(project_path, ".unity-agentic/")}. Run "unity-agentic-tools setup" first.`);
4615
+ } else if (!registryExists) {
4616
+ hints.push('Type registry not found. Re-run "unity-agentic-tools setup" to rebuild.');
4617
+ }
4618
+ } else {
4619
+ hints.push("No Unity project detected. Provide --project or run from inside a Unity project directory.");
4620
+ }
4621
+ return {
4622
+ success: false,
4623
+ file_path,
4624
+ error: `Component or script not found: "${component_type}". Use a Unity component name (e.g., "MeshRenderer", "Animator") or provide a script name, path (Assets/Scripts/Foo.cs), or GUID.${hints.length > 0 ? " " + hints.join(" ") : ""}`
4625
+ };
4626
+ }
4627
+ if (resolved.kind === "enum" || resolved.kind === "interface") {
4628
+ return {
4629
+ success: false,
4630
+ file_path,
4631
+ error: `"${component_type}" is ${resolved.kind === "enum" ? "an enum" : "an interface"}, not a MonoBehaviour. Cannot add as a component.`
4632
+ };
4633
+ }
4634
+ if (resolved.base_class && !["MonoBehaviour", "NetworkBehaviour", "StateMachineBehaviour"].includes(resolved.base_class)) {
4635
+ return {
4636
+ success: false,
4637
+ file_path,
4638
+ error: `"${component_type}" extends ${resolved.base_class}, not MonoBehaviour. Cannot add as a component.`
4639
+ };
4640
+ }
4641
+ let version;
4642
+ if (project_path) {
4643
+ try {
4644
+ version = read_project_version(project_path);
4645
+ } catch {}
4646
+ }
4647
+ componentYAML = createMonoBehaviourYAML(componentId, gameObjectId, resolved.guid, resolved.fields, version);
4648
+ scriptGuid = resolved.guid;
4649
+ scriptPath = resolved.path || undefined;
4650
+ extractionError = resolved.extraction_error;
4651
+ }
4652
+ addComponentToGameObject(doc, gameObjectIdStr, componentIdStr);
4653
+ doc.append_raw(componentYAML);
4654
+ const saveResult = doc.save();
4655
+ if (!saveResult.success) {
4656
+ return {
4657
+ success: false,
4658
+ file_path,
4659
+ error: saveResult.error
4660
+ };
4661
+ }
4662
+ const warnings = [];
4663
+ if (duplicateWarning)
4664
+ warnings.push(duplicateWarning);
4665
+ if (extractionError)
4666
+ warnings.push(extractionError);
4667
+ const result = {
4668
+ success: true,
4669
+ file_path,
4670
+ component_id: componentId,
4671
+ script_guid: scriptGuid,
4672
+ script_path: scriptPath
4673
+ };
4674
+ if (warnings.length > 0) {
4675
+ result.warning = warnings.join(" ");
4676
+ }
4677
+ return result;
4678
+ }
4679
+ function copyComponent(options) {
4680
+ const { file_path, source_file_id, target_game_object_name } = options;
4681
+ if (!import_fs10.existsSync(file_path)) {
4682
+ return { success: false, file_path, error: `File not found: ${file_path}` };
4683
+ }
4684
+ let doc;
4685
+ try {
4686
+ doc = UnityDocument.from_file(file_path);
4687
+ } catch (err) {
4688
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
4689
+ }
4690
+ const sourceBlock = doc.find_by_file_id(source_file_id);
4691
+ if (!sourceBlock) {
4692
+ return { success: false, file_path, error: `Component with file ID ${source_file_id} not found` };
4693
+ }
4694
+ if (sourceBlock.class_id === 1) {
4695
+ return { success: false, file_path, error: "Cannot copy a GameObject. Use duplicate instead." };
4696
+ }
4697
+ if (sourceBlock.class_id === 4) {
4698
+ return { success: false, file_path, error: "Cannot copy a Transform component." };
4699
+ }
4700
+ const targetResult = doc.require_unique_game_object(target_game_object_name);
4701
+ if ("error" in targetResult) {
4702
+ return { success: false, file_path, error: targetResult.error };
4703
+ }
4704
+ const targetGoIdStr = targetResult.file_id;
4705
+ const targetGoId = parseInt(targetGoIdStr, 10);
4706
+ const newIdStr = doc.generate_file_id();
4707
+ const newId = parseInt(newIdStr, 10);
4708
+ let clonedBlock = sourceBlock.raw.replace(new RegExp(`^(--- !u!${sourceBlock.class_id} &)${source_file_id}`), `$1${newId}`);
4709
+ clonedBlock = clonedBlock.replace(/m_GameObject:\s*\{fileID:\s*\d+\}/, `m_GameObject: {fileID: ${targetGoId}}`);
4710
+ addComponentToGameObject(doc, targetGoIdStr, newIdStr);
4711
+ doc.append_raw(clonedBlock);
4712
+ if (!doc.validate()) {
4713
+ return { success: false, file_path, error: "Validation failed after copying component" };
4714
+ }
4715
+ const saveResult = doc.save();
4716
+ if (!saveResult.success) {
4717
+ return { success: false, file_path, error: saveResult.error };
4718
+ }
4719
+ return {
4720
+ success: true,
4721
+ file_path,
4722
+ source_file_id,
4723
+ new_component_id: newId,
4724
+ target_game_object: target_game_object_name
4725
+ };
4726
+ }
4727
+ // src/editor/update.ts
4728
+ var import_fs11 = require("fs");
4729
+ function eulerToQuaternion(euler) {
4730
+ const deg2rad = Math.PI / 180;
4731
+ const x = euler.x * deg2rad;
4732
+ const y = euler.y * deg2rad;
4733
+ const z = euler.z * deg2rad;
4734
+ const cx = Math.cos(x / 2);
4735
+ const sx = Math.sin(x / 2);
4736
+ const cy = Math.cos(y / 2);
4737
+ const sy = Math.sin(y / 2);
4738
+ const cz = Math.cos(z / 2);
4739
+ const sz = Math.sin(z / 2);
4740
+ return {
4741
+ x: sx * cy * cz + cx * sy * sz,
4742
+ y: cx * sy * cz - sx * cy * sz,
4743
+ z: cx * cy * sz - sx * sy * cz,
4744
+ w: cx * cy * cz + sx * sy * sz
4745
+ };
4746
+ }
4747
+ function validate_value_type(current_value, new_value) {
4748
+ const current = current_value.trim();
4749
+ const incoming = new_value.trim();
4750
+ if (/^\{fileID:/.test(current)) {
4751
+ if (!/^\{fileID:/.test(incoming)) {
4752
+ return `Expected a reference value ({fileID: ...}), got "${incoming}"`;
4753
+ }
4754
+ return null;
4755
+ }
4756
+ if (/^\{.+:.+\}$/.test(current)) {
4757
+ if (!incoming.startsWith("{") || !incoming.endsWith("}")) {
4758
+ return `Expected a compound value (e.g. {x: ..., y: ...}), got "${incoming}"`;
4759
+ }
4760
+ const inner = incoming.slice(1, -1).trim();
4761
+ if (inner.length > 0) {
4762
+ const fields = inner.split(",");
4763
+ for (const field of fields) {
4764
+ const kv = field.split(":");
4765
+ if (kv.length < 2) {
4766
+ return `Malformed struct field in "${incoming}" \u2014 expected "key: value" pairs`;
4767
+ }
4768
+ const val = kv.slice(1).join(":").trim();
4769
+ if (!/^-?\d+(\.\d+)?(e[+-]?\d+)?$/i.test(val) && !/^\{fileID:/.test(val)) {
4770
+ return `Non-numeric value "${val}" in struct "${incoming}" \u2014 expected numeric or {fileID: N}`;
4771
+ }
4772
+ }
4773
+ }
4774
+ return null;
4775
+ }
4776
+ if (/^\[/.test(current) || /^\n?\s*-\s/.test(current)) {
4777
+ if (!incoming.startsWith("[") && incoming !== "[]") {
4778
+ return `Expected an array value (e.g. [] or [...]), got "${incoming}"`;
4779
+ }
4780
+ return null;
4781
+ }
4782
+ if (/^-?\d+(\.\d+)?(e[+-]?\d+)?$/i.test(current)) {
4783
+ if (!/^-?\d+(\.\d+)?(e[+-]?\d+)?$/i.test(incoming)) {
4784
+ return `Expected numeric value, got "${incoming}"`;
4785
+ }
4786
+ return null;
4787
+ }
4788
+ return null;
4789
+ }
4790
+ function isAncestor(doc, childTransformId, candidateAncestorTransformId) {
4791
+ let currentId = candidateAncestorTransformId;
4792
+ const visited = new Set;
4793
+ while (currentId !== "0") {
4794
+ if (currentId === childTransformId)
4795
+ return true;
4796
+ if (visited.has(currentId))
4797
+ return false;
4798
+ visited.add(currentId);
4799
+ const block = doc.find_by_file_id(currentId);
4800
+ if (!block || block.class_id !== 4)
4801
+ break;
4802
+ const fatherMatch = block.raw.match(/m_Father:\s*\{fileID:\s*(\d+)\}/);
4803
+ currentId = fatherMatch ? fatherMatch[1] : "0";
4804
+ }
4805
+ return false;
4806
+ }
4807
+ function resolveTransformByGameObjectId(doc, gameObjectFileId) {
4808
+ const found = doc.find_by_file_id(gameObjectFileId);
4809
+ if (!found || found.class_id !== 1) {
4810
+ return { error: `GameObject with fileID ${gameObjectFileId} not found` };
4811
+ }
4812
+ const componentMatch = found.raw.match(/m_Component:\s*\n\s*-\s*component:\s*\{fileID:\s*(\d+)\}/);
4813
+ if (!componentMatch) {
4814
+ return { error: `GameObject fileID ${gameObjectFileId} has no Transform component` };
4815
+ }
4816
+ const transformId = componentMatch[1];
4817
+ const transformBlock = doc.find_by_file_id(transformId);
4818
+ if (!transformBlock || transformBlock.class_id !== 4 && transformBlock.class_id !== 224) {
4819
+ return { error: `First component of GameObject fileID ${gameObjectFileId} is not a Transform` };
4820
+ }
4821
+ return { id: transformId };
4822
+ }
4823
+ function safeUnityYAMLEdit(filePath, objectName, propertyName, newValue, projectPath) {
4824
+ if (!import_fs11.existsSync(filePath)) {
4825
+ return {
4826
+ success: false,
4827
+ file_path: filePath,
4828
+ error: `File not found: ${filePath}`
4829
+ };
4830
+ }
4831
+ let doc;
4832
+ try {
4833
+ doc = UnityDocument.from_file(filePath);
4834
+ } catch (err) {
4835
+ return {
4836
+ success: false,
4837
+ file_path: filePath,
4838
+ error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
4839
+ };
4840
+ }
4841
+ const normalizedProperty = propertyName.startsWith("m_") ? propertyName.slice(2) : propertyName;
4842
+ const VALID_GO_PROPERTIES = new Set([
4843
+ "Name",
4844
+ "TagString",
4845
+ "IsActive",
4846
+ "Layer",
4847
+ "StaticEditorFlags",
4848
+ "Icon",
4849
+ "NavMeshLayer"
4850
+ ]);
4851
+ if (!VALID_GO_PROPERTIES.has(normalizedProperty)) {
4852
+ const validList = [...VALID_GO_PROPERTIES].map((p) => `m_${p}`).join(", ");
4853
+ return {
4854
+ success: false,
4855
+ file_path: filePath,
4856
+ error: `Unknown GameObject property "m_${normalizedProperty}". Valid properties: ${validList}`
4857
+ };
4858
+ }
4859
+ const BUILTIN_TAGS = ["Untagged", "Respawn", "Finish", "EditorOnly", "MainCamera", "Player", "GameController"];
4860
+ const GO_PROP_VALIDATORS = {
4861
+ IsActive: (v) => v === "0" || v === "1" || v === "true" || v === "false" ? null : "must be 0, 1, true, or false",
4862
+ Layer: (v) => /^\d+$/.test(v) && parseInt(v) >= 0 && parseInt(v) <= 31 ? null : "must be an integer 0-31",
4863
+ StaticEditorFlags: (v) => /^\d+$/.test(v) ? null : "must be a non-negative integer",
4864
+ TagString: (v) => {
4865
+ if (BUILTIN_TAGS.includes(v))
4866
+ return null;
4867
+ if (projectPath) {
4868
+ try {
4869
+ const settings = read_settings({ project_path: projectPath, setting: "tags" });
4870
+ const tags = settings?.data?.tags || [];
4871
+ if (tags.includes(v))
4872
+ return null;
4873
+ return `tag "${v}" not found in project TagManager. Valid tags: ${[...BUILTIN_TAGS, ...tags].join(", ")}`;
4874
+ } catch {}
4875
+ }
4876
+ return null;
4877
+ }
4878
+ };
4879
+ const validator = GO_PROP_VALIDATORS[normalizedProperty];
4880
+ if (validator) {
4881
+ const error = validator(newValue);
4882
+ if (error) {
4883
+ return {
4884
+ success: false,
4885
+ file_path: filePath,
4886
+ error: `Invalid value "${newValue}" for m_${normalizedProperty}: ${error}`
4887
+ };
4888
+ }
4889
+ }
4890
+ if (normalizedProperty === "IsActive") {
4891
+ if (newValue === "true")
4892
+ newValue = "1";
4893
+ else if (newValue === "false")
4894
+ newValue = "0";
4895
+ }
4896
+ let targetBlock = null;
4897
+ if (/^\d+$/.test(objectName)) {
4898
+ targetBlock = doc.find_by_file_id(objectName);
4899
+ if (!targetBlock) {
4900
+ return {
4901
+ success: false,
4902
+ file_path: filePath,
4903
+ error: `GameObject with fileID ${objectName} not found`
4904
+ };
4905
+ }
4906
+ if (targetBlock.class_id !== 1) {
4907
+ return {
4908
+ success: false,
4909
+ file_path: filePath,
4910
+ error: `fileID ${objectName} is not a GameObject (class ${targetBlock.class_id})`
4911
+ };
4912
+ }
4913
+ } else {
4914
+ const matches = doc.find_game_objects_by_name(objectName);
4915
+ if (matches.length === 0) {
4916
+ return {
4917
+ success: false,
4918
+ file_path: filePath,
4919
+ error: `GameObject "${objectName}" not found in file`
4920
+ };
4921
+ }
4922
+ if (matches.length > 1) {
4923
+ const matchedIds = matches.map((b) => b.file_id).join(", ");
4924
+ return {
4925
+ success: false,
4926
+ file_path: filePath,
4927
+ error: `Multiple GameObjects named "${objectName}" found (fileIDs: ${matchedIds}). Use numeric fileID to specify which one.`
4928
+ };
4929
+ }
4930
+ targetBlock = matches[0];
4931
+ }
4932
+ const propertyPattern = new RegExp(`(^\\s*m_${normalizedProperty}:\\s*)([^\\n]*)`, "m");
4933
+ let updatedRaw = targetBlock.raw;
4934
+ if (propertyPattern.test(updatedRaw)) {
4935
+ updatedRaw = updatedRaw.replace(propertyPattern, `$1${newValue}`);
4936
+ } else {
4937
+ updatedRaw = updatedRaw.replace(/(\n)(--- !u!|$)/, `
4938
+ m_${normalizedProperty}: ${newValue}$1$2`);
4939
+ }
4940
+ targetBlock.replace_raw(updatedRaw);
4941
+ const saveResult = doc.save();
4942
+ return {
4943
+ ...saveResult,
4944
+ file_path: filePath
4945
+ };
4946
+ }
4947
+ function editProperty(options) {
4948
+ const pathError = validate_file_path(options.file_path, "write");
4949
+ if (pathError) {
4950
+ return { success: false, file_path: options.file_path, error: pathError };
4951
+ }
4952
+ const result = safeUnityYAMLEdit(options.file_path, options.object_name, options.property, options.new_value, options.project_path);
4953
+ if (!result.success) {
4954
+ return result;
4955
+ }
4956
+ return result;
4957
+ }
4958
+ function editComponentByFileId(options) {
4959
+ const { file_path, file_id, property, new_value } = options;
4960
+ const pathError = validate_file_path(file_path, "write");
4961
+ if (pathError) {
4962
+ return { success: false, file_path, error: pathError };
4963
+ }
4964
+ if (!import_fs11.existsSync(file_path)) {
4965
+ return {
4966
+ success: false,
4967
+ file_path,
4968
+ error: `File not found: ${file_path}`
4969
+ };
4970
+ }
4971
+ let doc;
4972
+ try {
4973
+ doc = UnityDocument.from_file(file_path);
4974
+ } catch (err) {
4975
+ return {
4976
+ success: false,
4977
+ file_path,
4978
+ error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
4979
+ };
4980
+ }
4981
+ const exactProperty = property;
4982
+ let prefixedProperty;
4983
+ if (property.includes(".") || property.includes("Array")) {
4984
+ const rootSegment = property.split(".")[0];
4985
+ prefixedProperty = rootSegment.startsWith("m_") ? property : "m_" + property;
4986
+ } else {
4987
+ prefixedProperty = property.startsWith("m_") ? property : "m_" + property;
4988
+ }
4989
+ const targetBlock = doc.find_by_file_id(file_id);
4990
+ if (!targetBlock) {
4991
+ const hasPrefabInstance = doc.find_by_class_id(1001).length > 0;
4992
+ if (hasPrefabInstance) {
4993
+ return {
4994
+ success: false,
4995
+ file_path,
4996
+ error: `Component with file ID ${file_id} not found. This file contains prefab instances \u2014 the fileID may be from the source prefab. Use \`update override\` to edit variant properties.`
4997
+ };
4998
+ }
4999
+ return {
5000
+ success: false,
5001
+ file_path,
5002
+ error: `Component with file ID ${file_id} not found`
5003
+ };
5004
+ }
5005
+ if (targetBlock.is_stripped) {
5006
+ return {
5007
+ success: false,
5008
+ file_path,
5009
+ error: `Component ${file_id} is a stripped reference in a prefab variant. Use \`update override <file> <prefab_instance_id> <property_path> <value>\` to modify overrides.`
5010
+ };
5011
+ }
5012
+ const classId = targetBlock.class_id;
5013
+ const sameFileRefMatch = new_value.match(/^\{fileID:\s*(\d+)\}$/);
5014
+ if (sameFileRefMatch) {
5015
+ const refId = sameFileRefMatch[1];
5016
+ if (refId !== "0" && !doc.find_by_file_id(refId)) {
5017
+ return {
5018
+ success: false,
5019
+ file_path,
5020
+ error: `fileID ${refId} does not exist in this file`
5021
+ };
5022
+ }
5023
+ }
5024
+ const currentValue = targetBlock.get_property(exactProperty) ?? (exactProperty !== prefixedProperty ? targetBlock.get_property(prefixedProperty) : null);
5025
+ if (currentValue !== null) {
5026
+ const typeError = validate_value_type(currentValue, new_value);
5027
+ if (typeError) {
5028
+ return {
5029
+ success: false,
5030
+ file_path,
5031
+ error: typeError
5032
+ };
5033
+ }
5034
+ }
5035
+ let modified = targetBlock.set_property(exactProperty, new_value, "{fileID: 0}");
5036
+ if (!modified && exactProperty !== prefixedProperty) {
5037
+ modified = targetBlock.set_property(prefixedProperty, new_value, "{fileID: 0}");
5038
+ }
5039
+ if (!modified) {
5040
+ if (currentValue !== null) {
5041
+ return {
5042
+ success: true,
5043
+ file_path,
5044
+ file_id,
5045
+ class_id: classId,
5046
+ bytes_written: 0,
5047
+ no_change: true,
5048
+ message: "Property already has the requested value"
5049
+ };
5050
+ }
5051
+ return {
5052
+ success: false,
5053
+ file_path,
5054
+ error: `Property "${property}" not found in component ${file_id} (class ${classId}). Unity only serializes properties that differ from defaults \u2014 newly created or unmodified components may not have this property in YAML yet. Use 'read gameobject --properties' to see available properties, or set the property in the Unity Editor first to make it serializable.`
5055
+ };
5056
+ }
5057
+ const saveResult = doc.save();
5058
+ if (!saveResult.success) {
5059
+ return {
5060
+ success: false,
5061
+ file_path,
5062
+ error: saveResult.error
5063
+ };
5064
+ }
5065
+ return {
5066
+ success: true,
5067
+ file_path,
5068
+ file_id,
5069
+ class_id: classId,
5070
+ bytes_written: saveResult.bytes_written
5071
+ };
5072
+ }
5073
+ function editPrefabOverride(options) {
5074
+ const { file_path, prefab_instance, property_path, new_value, object_reference, target } = options;
5075
+ const objRef = object_reference ?? "{fileID: 0}";
5076
+ if (!import_fs11.existsSync(file_path)) {
5077
+ return { success: false, file_path, error: `File not found: ${file_path}` };
5078
+ }
5079
+ let doc;
5080
+ try {
5081
+ doc = UnityDocument.from_file(file_path);
5082
+ } catch (err) {
5083
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
5084
+ }
5085
+ const targetBlock = findPrefabInstanceBlock(doc, prefab_instance);
5086
+ if (!targetBlock) {
5087
+ return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
5088
+ }
5089
+ const escapedPath = property_path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5090
+ const entryPattern = new RegExp(`(- target:\\s*\\{[^}]+\\}\\s*\\n\\s*propertyPath:\\s*)${escapedPath}(\\s*\\n\\s*value:\\s*)(.*)(\\s*\\n\\s*objectReference:\\s*)(.*)`, "m");
5091
+ const entryMatch = targetBlock.raw.match(entryPattern);
5092
+ if (entryMatch) {
5093
+ const updatedText2 = targetBlock.raw.replace(entryPattern, `$1${property_path}$2${new_value}$4${objRef}`);
5094
+ targetBlock.replace_raw(updatedText2);
5095
+ const saveResult2 = doc.save();
5096
+ if (!saveResult2.success) {
5097
+ return { success: false, file_path, error: saveResult2.error };
5098
+ }
5099
+ return {
5100
+ success: true,
5101
+ file_path,
5102
+ prefab_instance_id: prefab_instance,
5103
+ property_path,
5104
+ action: "updated"
5105
+ };
5106
+ }
5107
+ let targetRef = target;
5108
+ if (!targetRef) {
5109
+ const rootProp = property_path.split(".")[0];
5110
+ const siblingPattern = new RegExp(`- target:\\s*(\\{[^}]+\\})\\s*\\n\\s*propertyPath:\\s*${rootProp.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "m");
5111
+ const siblingMatch = targetBlock.raw.match(siblingPattern);
5112
+ if (siblingMatch) {
5113
+ targetRef = siblingMatch[1];
5114
+ }
5115
+ }
5116
+ if (!targetRef) {
5117
+ return {
5118
+ success: false,
5119
+ file_path,
5120
+ error: `Cannot infer target for new override "${property_path}". Provide --target (e.g., "{fileID: 400000, guid: ..., type: 3}").`
5121
+ };
5122
+ }
5123
+ if (!/^\{fileID:\s*-?\d+/.test(targetRef)) {
5124
+ return {
5125
+ success: false,
5126
+ file_path,
5127
+ error: `Invalid target reference "${targetRef}". Expected format: {fileID: N, guid: ..., type: T}`
5128
+ };
5129
+ }
5130
+ const newEntry = ` - target: ${targetRef}
5131
+ propertyPath: ${property_path}
5132
+ value: ${new_value}
5133
+ objectReference: ${objRef}`;
5134
+ const removedPattern = /(\n\s*m_RemovedComponents:)/m;
5135
+ const removedMatch = targetBlock.raw.match(removedPattern);
5136
+ let updatedText;
5137
+ if (removedMatch) {
5138
+ updatedText = targetBlock.raw.replace(removedPattern, `
5139
+ ${newEntry}$1`);
5140
+ } else {
5141
+ const lines = targetBlock.raw.split(`
5142
+ `);
5143
+ let lastObjRefIdx = -1;
5144
+ let inModifications = false;
5145
+ for (let i = 0;i < lines.length; i++) {
5146
+ if (/m_Modifications:/.test(lines[i])) {
5147
+ inModifications = true;
5148
+ continue;
5149
+ }
5150
+ if (inModifications && /^\s*objectReference:/.test(lines[i])) {
5151
+ lastObjRefIdx = i;
5152
+ }
5153
+ if (inModifications && /^\s*m_\w+:/.test(lines[i]) && !/objectReference/.test(lines[i]) && !/propertyPath/.test(lines[i])) {
5154
+ if (lastObjRefIdx !== -1)
5155
+ break;
5156
+ }
5157
+ }
5158
+ if (lastObjRefIdx !== -1) {
5159
+ lines.splice(lastObjRefIdx + 1, 0, newEntry);
5160
+ updatedText = lines.join(`
5161
+ `);
5162
+ } else {
5163
+ return { success: false, file_path, error: "Could not find insertion point in m_Modifications" };
5164
+ }
5165
+ }
5166
+ targetBlock.replace_raw(updatedText);
5167
+ const saveResult = doc.save();
5168
+ if (!saveResult.success) {
5169
+ return { success: false, file_path, error: saveResult.error };
5170
+ }
5171
+ return {
5172
+ success: true,
5173
+ file_path,
5174
+ prefab_instance_id: prefab_instance,
5175
+ property_path,
5176
+ action: "added"
5177
+ };
5178
+ }
5179
+ function editTransform(options) {
5180
+ const { file_path, transform_id, position, rotation, scale } = options;
5181
+ const pathError = validate_file_path(file_path, "write");
5182
+ if (pathError) {
5183
+ return { success: false, file_path, error: pathError };
5184
+ }
5185
+ if (!import_fs11.existsSync(file_path)) {
5186
+ return {
5187
+ success: false,
5188
+ file_path,
5189
+ error: `File not found: ${file_path}`
5190
+ };
5191
+ }
5192
+ let doc;
5193
+ try {
5194
+ doc = UnityDocument.from_file(file_path);
5195
+ } catch (err) {
5196
+ return {
5197
+ success: false,
5198
+ file_path,
5199
+ error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
5200
+ };
5201
+ }
5202
+ const targetBlock = doc.find_by_file_id(String(transform_id));
5203
+ if (!targetBlock || targetBlock.class_id !== 4) {
5204
+ return {
5205
+ success: false,
5206
+ file_path,
5207
+ error: `Transform with fileID ${transform_id} not found`
5208
+ };
5209
+ }
5210
+ if (targetBlock.is_stripped) {
5211
+ return {
5212
+ success: false,
5213
+ file_path,
5214
+ error: `Transform ${transform_id} is a stripped reference in a prefab variant. Use \`update prefab override <file> <prefab_instance_id> <property_path> <value>\` to modify transform overrides (e.g., "m_LocalPosition.x").`
5215
+ };
5216
+ }
5217
+ let blockText = targetBlock.raw;
5218
+ if (position) {
5219
+ blockText = blockText.replace(/m_LocalPosition:\s*\{[^}]+\}/, `m_LocalPosition: {x: ${position.x}, y: ${position.y}, z: ${position.z}}`);
5220
+ }
5221
+ if (rotation) {
5222
+ const quat = eulerToQuaternion(rotation);
5223
+ blockText = blockText.replace(/m_LocalRotation:\s*\{[^}]+\}/, `m_LocalRotation: {x: ${quat.x}, y: ${quat.y}, z: ${quat.z}, w: ${quat.w}}`);
5224
+ blockText = blockText.replace(/m_LocalEulerAnglesHint:\s*\{[^}]+\}/, `m_LocalEulerAnglesHint: {x: ${rotation.x}, y: ${rotation.y}, z: ${rotation.z}}`);
5225
+ }
5226
+ if (scale) {
5227
+ blockText = blockText.replace(/m_LocalScale:\s*\{[^}]+\}/, `m_LocalScale: {x: ${scale.x}, y: ${scale.y}, z: ${scale.z}}`);
5228
+ }
5229
+ targetBlock.replace_raw(blockText);
5230
+ const saveResult = doc.save();
5231
+ return {
5232
+ ...saveResult,
5233
+ file_path
5234
+ };
5235
+ }
5236
+ function batchEditProperties(filePath, edits) {
5237
+ if (!import_fs11.existsSync(filePath)) {
5238
+ return {
5239
+ success: false,
5240
+ file_path: filePath,
5241
+ error: `File not found: ${filePath}`
5242
+ };
5243
+ }
5244
+ let doc;
5245
+ try {
5246
+ doc = UnityDocument.from_file(filePath);
5247
+ } catch (err) {
5248
+ return {
5249
+ success: false,
5250
+ file_path: filePath,
5251
+ error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
5252
+ };
5253
+ }
5254
+ const editsByObject = new Map;
5255
+ for (const edit of edits) {
5256
+ const existing = editsByObject.get(edit.object_name) || [];
5257
+ existing.push(edit);
5258
+ editsByObject.set(edit.object_name, existing);
5259
+ }
5260
+ for (const [objectName, objectEdits] of editsByObject) {
5261
+ const matches = doc.find_game_objects_by_name(objectName);
5262
+ if (matches.length === 0) {
5263
+ return {
5264
+ success: false,
5265
+ file_path: filePath,
5266
+ error: `Failed to edit ${objectName}.${objectEdits[0].property}: GameObject "${objectName}" not found in file`
5267
+ };
5268
+ }
5269
+ const targetBlock = matches[0];
5270
+ for (const edit of objectEdits) {
5271
+ const normalizedProperty = edit.property.startsWith("m_") ? edit.property.slice(2) : edit.property;
5272
+ const propertyPattern = new RegExp(`(^\\s*m_${normalizedProperty}:\\s*)([^\\n]*)`, "m");
5273
+ let updatedRaw = targetBlock.raw;
5274
+ if (propertyPattern.test(updatedRaw)) {
5275
+ updatedRaw = updatedRaw.replace(propertyPattern, `$1${edit.new_value}`);
5276
+ } else {
5277
+ updatedRaw = updatedRaw.replace(/(\n)(--- !u!|$)/, `
5278
+ m_${normalizedProperty}: ${edit.new_value}$1$2`);
5279
+ }
5280
+ targetBlock.replace_raw(updatedRaw);
5281
+ }
5282
+ }
5283
+ if (!doc.validate()) {
5284
+ return {
5285
+ success: false,
5286
+ file_path: filePath,
5287
+ error: "Validation failed after batch edit"
5288
+ };
5289
+ }
5290
+ const saveResult = doc.save();
5291
+ return {
5292
+ ...saveResult,
5293
+ file_path: filePath
5294
+ };
5295
+ }
5296
+ function reparentGameObject(options) {
5297
+ const { file_path, object_name, new_parent, by_id } = options;
5298
+ if (!import_fs11.existsSync(file_path)) {
5299
+ return { success: false, file_path, error: `File not found: ${file_path}` };
5300
+ }
5301
+ let doc;
5302
+ try {
5303
+ doc = UnityDocument.from_file(file_path);
5304
+ } catch (err) {
5305
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
5306
+ }
5307
+ let childTransformId;
5308
+ if (by_id || /^\d+$/.test(object_name)) {
5309
+ if (isNaN(parseInt(object_name, 10))) {
5310
+ return { success: false, file_path, error: `Invalid fileID: "${object_name}" \u2014 expected a numeric value` };
5311
+ }
5312
+ const resolved = resolveTransformByGameObjectId(doc, object_name);
5313
+ if ("error" in resolved) {
5314
+ return { success: false, file_path, error: resolved.error };
5315
+ }
5316
+ childTransformId = resolved.id;
5317
+ } else {
5318
+ const childResult = doc.require_unique_transform(object_name);
5319
+ if ("error" in childResult) {
5320
+ return { success: false, file_path, error: childResult.error };
5321
+ }
5322
+ childTransformId = childResult.file_id;
5323
+ }
5324
+ const childBlock = doc.find_by_file_id(childTransformId);
5325
+ if (!childBlock) {
5326
+ return { success: false, file_path, error: `Transform ${childTransformId} not found` };
5327
+ }
5328
+ const fatherMatch = childBlock.raw.match(/m_Father:\s*\{fileID:\s*(\d+)\}/);
5329
+ const oldParentTransformId = fatherMatch ? fatherMatch[1] : "0";
5330
+ let newParentTransformId = "0";
5331
+ if (new_parent.toLowerCase() !== "root") {
5332
+ if (by_id || /^\d+$/.test(new_parent)) {
5333
+ if (isNaN(parseInt(new_parent, 10))) {
5334
+ return { success: false, file_path, error: `Invalid parent fileID: "${new_parent}" \u2014 expected a numeric value, or "root"` };
5335
+ }
5336
+ const resolved = resolveTransformByGameObjectId(doc, new_parent);
5337
+ if ("error" in resolved) {
5338
+ return { success: false, file_path, error: `Parent not found: ${resolved.error}` };
5339
+ }
5340
+ newParentTransformId = resolved.id;
5341
+ } else {
5342
+ const parentResult = doc.require_unique_transform(new_parent);
5343
+ if ("error" in parentResult) {
5344
+ return { success: false, file_path, error: `Parent not found: ${parentResult.error}` };
5345
+ }
5346
+ newParentTransformId = parentResult.file_id;
5347
+ }
5348
+ if (newParentTransformId === childTransformId) {
5349
+ return { success: false, file_path, error: "Cannot reparent a GameObject under itself" };
5350
+ }
5351
+ if (isAncestor(doc, childTransformId, newParentTransformId)) {
5352
+ return { success: false, file_path, error: "Cannot reparent: would create circular hierarchy" };
5353
+ }
5354
+ }
5355
+ if (oldParentTransformId !== "0") {
5356
+ doc.remove_child_from_parent(oldParentTransformId, childTransformId);
5357
+ }
5358
+ const fatherPattern = new RegExp(`(m_Father:\\s*)\\{fileID:\\s*\\d+\\}`);
5359
+ let updatedChildRaw = childBlock.raw.replace(fatherPattern, `$1{fileID: ${newParentTransformId}}`);
5360
+ childBlock.replace_raw(updatedChildRaw);
5361
+ {
5362
+ let newRootOrder;
5363
+ if (newParentTransformId === "0") {
5364
+ newRootOrder = doc.calculate_root_order("0") - 1;
5365
+ } else {
5366
+ newRootOrder = doc.calculate_root_order(newParentTransformId);
5367
+ }
5368
+ childBlock.set_property("m_RootOrder", String(newRootOrder));
5369
+ }
5370
+ if (newParentTransformId !== "0") {
5371
+ doc.add_child_to_parent(newParentTransformId, childTransformId);
5372
+ }
5373
+ if (!doc.validate()) {
5374
+ return { success: false, file_path, error: "Validation failed after reparent" };
5375
+ }
5376
+ const saveResult = doc.save();
5377
+ if (!saveResult.success) {
5378
+ return { success: false, file_path, error: saveResult.error };
5379
+ }
5380
+ return {
5381
+ success: true,
5382
+ file_path,
5383
+ child_transform_id: parseInt(childTransformId, 10),
5384
+ old_parent_transform_id: parseInt(oldParentTransformId, 10),
5385
+ new_parent_transform_id: parseInt(newParentTransformId, 10)
5386
+ };
5387
+ }
5388
+ function findPrefabInstanceBlock(doc, identifier) {
5389
+ const asId = doc.find_by_file_id(identifier);
5390
+ if (asId && asId.class_id === 1001)
5391
+ return asId;
5392
+ const piBlocks = doc.find_by_class_id(1001);
5393
+ const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5394
+ const namePattern = new RegExp(`propertyPath:\\s*m_Name\\s+value:\\s*${escaped}\\s`);
5395
+ for (const block of piBlocks) {
5396
+ if (namePattern.test(block.raw))
5397
+ return block;
5398
+ }
5399
+ return null;
5400
+ }
5401
+ function editArray(options) {
5402
+ const { file_path, file_id, array_property, action, value, index } = options;
5403
+ if (!import_fs11.existsSync(file_path)) {
5404
+ return {
5405
+ success: false,
5406
+ file_path,
5407
+ error: `File not found: ${file_path}`
5408
+ };
5409
+ }
5410
+ let doc;
5411
+ try {
5412
+ doc = UnityDocument.from_file(file_path);
5413
+ } catch (err) {
5414
+ return {
5415
+ success: false,
5416
+ file_path,
5417
+ error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
5418
+ };
5419
+ }
5420
+ const targetBlock = doc.find_by_file_id(file_id);
5421
+ if (!targetBlock) {
5422
+ return {
5423
+ success: false,
5424
+ file_path,
5425
+ error: `Component with file ID ${file_id} not found`
5426
+ };
5427
+ }
5428
+ try {
5429
+ if (action === "insert") {
5430
+ if (value === undefined) {
5431
+ return { success: false, file_path, error: "value is required for insert action" };
5432
+ }
5433
+ const ok = targetBlock.insert_array_element(array_property, index ?? 0, value);
5434
+ if (!ok) {
5435
+ const len = targetBlock.get_array_length(array_property);
5436
+ if (len < 0) {
5437
+ return { success: false, file_path, error: `Array property "${array_property}" not found in component ${file_id}` };
5438
+ }
5439
+ return { success: false, file_path, error: `Index ${index ?? 0} out of bounds for "${array_property}" (array length: ${len})` };
5440
+ }
5441
+ } else if (action === "append") {
5442
+ if (value === undefined) {
5443
+ return { success: false, file_path, error: "value is required for append action" };
5444
+ }
5445
+ const ok = targetBlock.insert_array_element(array_property, -1, value);
5446
+ if (!ok) {
5447
+ const len = targetBlock.get_array_length(array_property);
5448
+ if (len < 0) {
5449
+ return { success: false, file_path, error: `Array property "${array_property}" not found in component ${file_id}` };
5450
+ }
5451
+ return { success: false, file_path, error: `Failed to append to "${array_property}"` };
5452
+ }
5453
+ } else if (action === "remove") {
5454
+ const ok = targetBlock.remove_array_element(array_property, index ?? 0);
5455
+ if (!ok) {
5456
+ const len = targetBlock.get_array_length(array_property);
5457
+ if (len < 0) {
5458
+ return { success: false, file_path, error: `Array property "${array_property}" not found in component ${file_id}` };
5459
+ }
5460
+ return { success: false, file_path, error: `Index ${index ?? 0} out of bounds for "${array_property}" (array length: ${len})` };
5461
+ }
5462
+ } else {
5463
+ return { success: false, file_path, error: `Invalid action "${action}". Must be insert, append, or remove.` };
5464
+ }
5465
+ } catch (err) {
5466
+ return {
5467
+ success: false,
5468
+ file_path,
5469
+ error: `Failed to ${action} array element: ${err instanceof Error ? err.message : String(err)}`
5470
+ };
5471
+ }
5472
+ const new_length = targetBlock.get_array_length(array_property);
5473
+ if (!doc.validate()) {
5474
+ return { success: false, file_path, error: "Validation failed after array edit" };
5475
+ }
5476
+ const saveResult = doc.save();
5477
+ if (!saveResult.success) {
5478
+ return { success: false, file_path, error: saveResult.error };
5479
+ }
5480
+ return {
5481
+ success: true,
5482
+ file_path,
5483
+ file_id,
5484
+ array_property,
5485
+ action,
5486
+ new_length
5487
+ };
5488
+ }
5489
+ function batchEditComponentProperties(filePath, edits) {
5490
+ if (!import_fs11.existsSync(filePath)) {
5491
+ return {
5492
+ success: false,
5493
+ file_path: filePath,
5494
+ error: `File not found: ${filePath}`
5495
+ };
5496
+ }
5497
+ let doc;
5498
+ try {
5499
+ doc = UnityDocument.from_file(filePath);
5500
+ } catch (err) {
5501
+ return {
5502
+ success: false,
5503
+ file_path: filePath,
5504
+ error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`
5505
+ };
5506
+ }
5507
+ for (const edit of edits) {
5508
+ const targetBlock = doc.find_by_file_id(edit.file_id);
5509
+ if (!targetBlock) {
5510
+ return {
5511
+ success: false,
5512
+ file_path: filePath,
5513
+ error: `Component with file ID ${edit.file_id} not found`
5514
+ };
5515
+ }
5516
+ const exactProperty = edit.property;
5517
+ let prefixedProperty;
5518
+ if (edit.property.includes(".") || edit.property.includes("Array")) {
5519
+ const rootSegment = edit.property.split(".")[0];
5520
+ prefixedProperty = rootSegment.startsWith("m_") ? edit.property : "m_" + edit.property;
5521
+ } else {
5522
+ prefixedProperty = edit.property.startsWith("m_") ? edit.property : "m_" + edit.property;
5523
+ }
5524
+ let modified = targetBlock.set_property(exactProperty, edit.new_value, "{fileID: 0}");
5525
+ if (!modified && exactProperty !== prefixedProperty) {
5526
+ modified = targetBlock.set_property(prefixedProperty, edit.new_value, "{fileID: 0}");
5527
+ }
5528
+ if (!modified) {
5529
+ const currentValue = targetBlock.get_property(exactProperty) ?? (exactProperty !== prefixedProperty ? targetBlock.get_property(prefixedProperty) : null);
5530
+ if (currentValue !== null) {
5531
+ continue;
5532
+ }
5533
+ return {
5534
+ success: false,
5535
+ file_path: filePath,
5536
+ error: `Property "${edit.property}" not found in component ${edit.file_id}`
5537
+ };
5538
+ }
5539
+ }
5540
+ if (!doc.validate()) {
5541
+ return {
5542
+ success: false,
5543
+ file_path: filePath,
5544
+ error: "Validation failed after batch edit"
5545
+ };
5546
+ }
5547
+ const saveResult = doc.save();
5548
+ return {
5549
+ ...saveResult,
5550
+ file_path: filePath
5551
+ };
5552
+ }
5553
+ function removePrefabOverride(options) {
5554
+ const { file_path, prefab_instance, property_path, target } = options;
5555
+ if (!import_fs11.existsSync(file_path)) {
5556
+ return { success: false, file_path, error: `File not found: ${file_path}` };
5557
+ }
5558
+ let doc;
5559
+ try {
5560
+ doc = UnityDocument.from_file(file_path);
5561
+ } catch (err) {
5562
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
5563
+ }
5564
+ const targetBlock = findPrefabInstanceBlock(doc, prefab_instance);
5565
+ if (!targetBlock) {
5566
+ return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
5567
+ }
5568
+ const escapedPath = property_path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5569
+ let entryPattern;
5570
+ if (target) {
5571
+ const fileIdMatch = target.match(/fileID:\s*(-?\d+)/);
5572
+ if (fileIdMatch) {
5573
+ const fileId = fileIdMatch[1];
5574
+ entryPattern = new RegExp(`[ \\t]*- target:\\s*\\{fileID:\\s*${fileId}[^}]*\\}\\s*\\n\\s*propertyPath:\\s*${escapedPath}\\s*\\n\\s*value:[^\\n]*\\n\\s*objectReference:[^\\n]*\\n?`, "m");
5575
+ } else {
5576
+ const escapedTarget = target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5577
+ entryPattern = new RegExp(`[ \\t]*- target:\\s*${escapedTarget}\\s*\\n\\s*propertyPath:\\s*${escapedPath}\\s*\\n\\s*value:[^\\n]*\\n\\s*objectReference:[^\\n]*\\n?`, "m");
5578
+ }
5579
+ } else {
5580
+ entryPattern = new RegExp(`[ \\t]*- target:\\s*\\{[^}]+\\}\\s*\\n\\s*propertyPath:\\s*${escapedPath}\\s*\\n\\s*value:[^\\n]*\\n\\s*objectReference:[^\\n]*\\n?`, "m");
5581
+ }
5582
+ const entryMatch = targetBlock.raw.match(entryPattern);
5583
+ if (!entryMatch) {
5584
+ return {
5585
+ success: false,
5586
+ file_path,
5587
+ error: `Modification entry for property "${property_path}" not found${target ? ` with target ${target}` : ""}`
5588
+ };
5589
+ }
5590
+ const updatedRaw = targetBlock.raw.replace(entryPattern, "");
5591
+ targetBlock.replace_raw(updatedRaw);
5592
+ if (!doc.validate()) {
5593
+ return { success: false, file_path, error: "Validation failed after removing override" };
5594
+ }
5595
+ const saveResult = doc.save();
5596
+ if (!saveResult.success) {
5597
+ return { success: false, file_path, error: saveResult.error };
5598
+ }
5599
+ return {
5600
+ success: true,
5601
+ file_path,
5602
+ prefab_instance_id: targetBlock.file_id,
5603
+ property_path
5604
+ };
5605
+ }
5606
+ function addRemovedComponent(options) {
5607
+ const { file_path, prefab_instance, component_ref } = options;
5608
+ if (!import_fs11.existsSync(file_path)) {
5609
+ return { success: false, file_path, error: `File not found: ${file_path}` };
5610
+ }
5611
+ if (!/^\{fileID:\s*-?\d+/.test(component_ref)) {
5612
+ return { success: false, file_path, error: `Invalid component reference "${component_ref}". Expected format: {fileID: N, guid: ..., type: T}` };
5613
+ }
5614
+ let doc;
5615
+ try {
5616
+ doc = UnityDocument.from_file(file_path);
5617
+ } catch (err) {
5618
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
5619
+ }
5620
+ const targetBlock = findPrefabInstanceBlock(doc, prefab_instance);
5621
+ if (!targetBlock) {
5622
+ return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
5623
+ }
5624
+ let rawText = targetBlock.raw;
5625
+ if (/m_RemovedComponents:\s*\[\]/.test(rawText)) {
5626
+ rawText = rawText.replace(/m_RemovedComponents:\s*\[\]/, "m_RemovedComponents:");
5627
+ }
5628
+ const removedPattern = /m_RemovedComponents:\s*\n/;
5629
+ if (removedPattern.test(rawText)) {
5630
+ rawText = rawText.replace(removedPattern, `m_RemovedComponents:
5631
+ - ${component_ref}
5632
+ `);
5633
+ } else {
5634
+ return { success: false, file_path, error: "m_RemovedComponents property not found in PrefabInstance" };
5635
+ }
5636
+ targetBlock.replace_raw(rawText);
5637
+ if (!doc.validate()) {
5638
+ return { success: false, file_path, error: "Validation failed after adding removed component" };
5639
+ }
5640
+ const saveResult = doc.save();
5641
+ if (!saveResult.success) {
5642
+ return { success: false, file_path, error: saveResult.error };
5643
+ }
5644
+ return {
5645
+ success: true,
5646
+ file_path,
5647
+ prefab_instance_id: targetBlock.file_id
5648
+ };
5649
+ }
5650
+ function removeRemovedComponent(options) {
5651
+ const { file_path, prefab_instance, component_ref } = options;
5652
+ if (!import_fs11.existsSync(file_path)) {
5653
+ return { success: false, file_path, error: `File not found: ${file_path}` };
5654
+ }
5655
+ let doc;
5656
+ try {
5657
+ doc = UnityDocument.from_file(file_path);
5658
+ } catch (err) {
5659
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
5660
+ }
5661
+ const targetBlock = findPrefabInstanceBlock(doc, prefab_instance);
5662
+ if (!targetBlock) {
5663
+ return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
5664
+ }
5665
+ const escapedRef = component_ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5666
+ const refPattern = new RegExp(`\\s*- ${escapedRef}\\n?`, "m");
5667
+ if (!refPattern.test(targetBlock.raw)) {
5668
+ return { success: false, file_path, error: `Component reference "${component_ref}" not found in m_RemovedComponents` };
5669
+ }
5670
+ const updatedRaw = targetBlock.raw.replace(refPattern, "");
5671
+ targetBlock.replace_raw(updatedRaw);
5672
+ if (!doc.validate()) {
5673
+ return { success: false, file_path, error: "Validation failed after removing component" };
5674
+ }
5675
+ const saveResult = doc.save();
5676
+ if (!saveResult.success) {
5677
+ return { success: false, file_path, error: saveResult.error };
5678
+ }
5679
+ return {
5680
+ success: true,
5681
+ file_path,
5682
+ prefab_instance_id: targetBlock.file_id
5683
+ };
5684
+ }
5685
+ function addRemovedGameObject(options) {
5686
+ const { file_path, prefab_instance, component_ref } = options;
5687
+ if (!import_fs11.existsSync(file_path)) {
5688
+ return { success: false, file_path, error: `File not found: ${file_path}` };
5689
+ }
5690
+ if (!/^\{fileID:\s*-?\d+/.test(component_ref)) {
5691
+ return { success: false, file_path, error: `Invalid GameObject reference "${component_ref}". Expected format: {fileID: N, guid: ..., type: T}` };
5692
+ }
5693
+ let doc;
5694
+ try {
5695
+ doc = UnityDocument.from_file(file_path);
5696
+ } catch (err) {
5697
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
5698
+ }
5699
+ const targetBlock = findPrefabInstanceBlock(doc, prefab_instance);
5700
+ if (!targetBlock) {
5701
+ return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
5702
+ }
5703
+ let rawText = targetBlock.raw;
5704
+ if (/m_RemovedGameObjects:\s*\[\]/.test(rawText)) {
5705
+ rawText = rawText.replace(/m_RemovedGameObjects:\s*\[\]/, "m_RemovedGameObjects:");
5706
+ }
5707
+ const removedPattern = /m_RemovedGameObjects:\s*\n/;
5708
+ if (removedPattern.test(rawText)) {
5709
+ rawText = rawText.replace(removedPattern, `m_RemovedGameObjects:
5710
+ - ${component_ref}
5711
+ `);
5712
+ } else {
5713
+ return { success: false, file_path, error: "m_RemovedGameObjects property not found in PrefabInstance" };
5714
+ }
5715
+ targetBlock.replace_raw(rawText);
5716
+ if (!doc.validate()) {
5717
+ return { success: false, file_path, error: "Validation failed after adding removed GameObject" };
5718
+ }
5719
+ const saveResult = doc.save();
5720
+ if (!saveResult.success) {
5721
+ return { success: false, file_path, error: saveResult.error };
5722
+ }
5723
+ return {
5724
+ success: true,
5725
+ file_path,
5726
+ prefab_instance_id: targetBlock.file_id
5727
+ };
5728
+ }
5729
+ function removeRemovedGameObject(options) {
5730
+ const { file_path, prefab_instance, component_ref } = options;
5731
+ if (!import_fs11.existsSync(file_path)) {
5732
+ return { success: false, file_path, error: `File not found: ${file_path}` };
5733
+ }
5734
+ let doc;
5735
+ try {
5736
+ doc = UnityDocument.from_file(file_path);
5737
+ } catch (err) {
5738
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
5739
+ }
5740
+ const targetBlock = findPrefabInstanceBlock(doc, prefab_instance);
5741
+ if (!targetBlock) {
5742
+ return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
5743
+ }
5744
+ const escapedRef = component_ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5745
+ const refPattern = new RegExp(`\\s*- ${escapedRef}\\n?`, "m");
5746
+ if (!refPattern.test(targetBlock.raw)) {
5747
+ return { success: false, file_path, error: `GameObject reference "${component_ref}" not found in m_RemovedGameObjects` };
5748
+ }
5749
+ const updatedRaw = targetBlock.raw.replace(refPattern, "");
5750
+ targetBlock.replace_raw(updatedRaw);
5751
+ if (!doc.validate()) {
5752
+ return { success: false, file_path, error: "Validation failed after removing GameObject" };
5753
+ }
5754
+ const saveResult = doc.save();
5755
+ if (!saveResult.success) {
5756
+ return { success: false, file_path, error: saveResult.error };
5757
+ }
5758
+ return {
5759
+ success: true,
5760
+ file_path,
5761
+ prefab_instance_id: targetBlock.file_id
5762
+ };
5763
+ }
5764
+ // src/editor/delete.ts
5765
+ var import_fs12 = require("fs");
5766
+ function removeComponent(options) {
5767
+ const { file_path, file_id } = options;
5768
+ const pathError = validate_file_path(file_path, "write");
5769
+ if (pathError) {
5770
+ return { success: false, file_path, error: pathError };
5771
+ }
5772
+ if (!import_fs12.existsSync(file_path)) {
5773
+ return { success: false, file_path, error: `File not found: ${file_path}` };
5774
+ }
5775
+ let doc;
5776
+ try {
5777
+ doc = UnityDocument.from_file(file_path);
5778
+ } catch (err) {
5779
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
5780
+ }
5781
+ const found = doc.find_by_file_id(file_id);
5782
+ if (!found) {
5783
+ return { success: false, file_path, error: `Component with file ID ${file_id} not found` };
5784
+ }
5785
+ if (found.class_id === 1) {
5786
+ return { success: false, file_path, error: "Cannot remove a GameObject with remove-component. Use delete instead." };
5787
+ }
5788
+ if (found.class_id === 4) {
5789
+ return { success: false, file_path, error: "Cannot remove a Transform with remove-component. Use delete to remove the entire GameObject." };
5790
+ }
5791
+ const goMatch = found.raw.match(/m_GameObject:\s*\{fileID:\s*(\d+)\}/);
5792
+ if (goMatch) {
5793
+ const parentGoId = goMatch[1];
5794
+ const goBlock = doc.find_by_file_id(parentGoId);
5795
+ if (goBlock) {
5796
+ const compLinePattern = new RegExp(`\\s*- component: \\{fileID: ${file_id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\}\\n?`);
5797
+ const modifiedRaw = goBlock.raw.replace(compLinePattern, "");
5798
+ goBlock.replace_raw(modifiedRaw);
5799
+ }
5800
+ }
5801
+ doc.remove_block(file_id);
5802
+ if (!doc.validate()) {
5803
+ return { success: false, file_path, error: "Validation failed after removing component" };
5804
+ }
5805
+ const writeResult = doc.save();
5806
+ if (!writeResult.success) {
5807
+ return { success: false, file_path, error: writeResult.error };
5808
+ }
5809
+ return {
5810
+ success: true,
5811
+ file_path,
5812
+ removed_file_id: file_id,
5813
+ removed_class_id: found.class_id
5814
+ };
5815
+ }
5816
+ function deleteGameObject(options) {
5817
+ const { file_path, object_name } = options;
5818
+ const pathError = validate_file_path(file_path, "write");
5819
+ if (pathError) {
5820
+ return { success: false, file_path, error: pathError };
5821
+ }
5822
+ if (!import_fs12.existsSync(file_path)) {
5823
+ return { success: false, file_path, error: `File not found: ${file_path}` };
5824
+ }
5825
+ let doc;
5826
+ try {
5827
+ doc = UnityDocument.from_file(file_path);
5828
+ } catch (err) {
5829
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
5830
+ }
5831
+ const goResult = doc.require_unique_game_object(object_name);
5832
+ if ("error" in goResult) {
5833
+ return { success: false, file_path, error: goResult.error };
5834
+ }
5835
+ const goBlock = goResult;
5836
+ const goId = goBlock.file_id;
5837
+ const componentIds = new Set;
5838
+ const compMatches = goBlock.raw.matchAll(/component:\s*\{fileID:\s*(\d+)\}/g);
5839
+ for (const cm of compMatches) {
5840
+ componentIds.add(cm[1]);
5841
+ }
5842
+ let transformId = null;
5843
+ let fatherId = "0";
5844
+ for (const compId of componentIds) {
5845
+ const block = doc.find_by_file_id(compId);
5846
+ if (block && block.class_id === 4) {
5847
+ transformId = compId;
5848
+ const fatherMatch = block.raw.match(/m_Father:\s*\{fileID:\s*(\d+)\}/);
5849
+ if (fatherMatch) {
5850
+ fatherId = fatherMatch[1];
5851
+ }
5852
+ break;
5853
+ }
5854
+ }
5855
+ const allIds = new Set([goId]);
5856
+ for (const id of componentIds) {
5857
+ allIds.add(id);
5858
+ }
5859
+ if (transformId !== null) {
5860
+ const descendants = doc.collect_hierarchy(transformId);
5861
+ for (const id of descendants) {
5862
+ allIds.add(id);
5863
+ }
5864
+ }
5865
+ if (fatherId !== "0" && transformId !== null) {
5866
+ doc.remove_child_from_parent(fatherId, transformId);
5867
+ }
5868
+ doc.remove_blocks(allIds);
5869
+ if (!doc.validate()) {
5870
+ return { success: false, file_path, error: "Validation failed after deleting GameObject" };
5871
+ }
5872
+ const writeResult = doc.save();
5873
+ if (!writeResult.success) {
5874
+ return { success: false, file_path, error: writeResult.error };
5875
+ }
5876
+ return {
5877
+ success: true,
5878
+ file_path,
5879
+ deleted_count: allIds.size
5880
+ };
5881
+ }
5882
+ function findPrefabInstanceBlock2(doc, identifier) {
5883
+ const asId = doc.find_by_file_id(identifier);
5884
+ if (asId && asId.class_id === 1001)
5885
+ return asId;
5886
+ const piBlocks = doc.find_by_class_id(1001);
5887
+ const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5888
+ const namePattern = new RegExp(`propertyPath:\\s*m_Name\\s+value:\\s*${escaped}\\s`);
5889
+ for (const block of piBlocks) {
5890
+ if (namePattern.test(block.raw))
5891
+ return block;
5892
+ }
5893
+ return null;
5894
+ }
5895
+ function deletePrefabInstance(options) {
5896
+ const { file_path, prefab_instance } = options;
5897
+ const pathError = validate_file_path(file_path, "write");
5898
+ if (pathError) {
5899
+ return { success: false, file_path, error: pathError };
5900
+ }
5901
+ if (!import_fs12.existsSync(file_path)) {
5902
+ return { success: false, file_path, error: `File not found: ${file_path}` };
5903
+ }
5904
+ let doc;
5905
+ try {
5906
+ doc = UnityDocument.from_file(file_path);
5907
+ } catch (err) {
5908
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
5909
+ }
5910
+ const piBlock = findPrefabInstanceBlock2(doc, prefab_instance);
5911
+ if (!piBlock) {
5912
+ return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
5913
+ }
5914
+ const piId = piBlock.file_id;
5915
+ const allToRemove = new Set([piId]);
5916
+ const piRefPattern = new RegExp(`m_PrefabInstance:\\s*\\{fileID:\\s*${piId}\\}`);
5917
+ for (const block of doc.blocks) {
5918
+ if (block.is_stripped && piRefPattern.test(block.raw)) {
5919
+ allToRemove.add(block.file_id);
5920
+ }
5921
+ }
5922
+ const addedGOPattern = /m_AddedGameObjects:\s*\n((?:\s*- \{fileID: \d+\}\n)*)/m;
5923
+ const addedGOMatch = piBlock.raw.match(addedGOPattern);
5924
+ if (addedGOMatch) {
5925
+ const addedGOSection = addedGOMatch[1];
5926
+ const goMatches = addedGOSection.matchAll(/fileID:\s*(\d+)/g);
5927
+ for (const match of goMatches) {
5928
+ const goId = match[1];
5929
+ allToRemove.add(goId);
5930
+ const goBlock = doc.find_by_file_id(goId);
5931
+ if (goBlock) {
5932
+ const compMatch = goBlock.raw.match(/component:\s*\{fileID:\s*(\d+)\}/);
5933
+ if (compMatch) {
5934
+ const transformId = compMatch[1];
5935
+ allToRemove.add(transformId);
5936
+ const descendants = doc.collect_hierarchy(transformId);
5937
+ for (const id of descendants) {
5938
+ allToRemove.add(id);
5939
+ }
5940
+ }
5941
+ }
5942
+ }
5943
+ }
5944
+ const addedCompPattern = /m_AddedComponents:\s*\n((?:\s*- \{fileID: \d+\}\n)*)/m;
5945
+ const addedCompMatch = piBlock.raw.match(addedCompPattern);
5946
+ if (addedCompMatch) {
5947
+ const addedCompSection = addedCompMatch[1];
5948
+ const compMatches = addedCompSection.matchAll(/fileID:\s*(\d+)/g);
5949
+ for (const match of compMatches) {
5950
+ allToRemove.add(match[1]);
5951
+ }
5952
+ }
5953
+ const parentMatch = piBlock.raw.match(/m_TransformParent:\s*\{fileID:\s*(\d+)\}/);
5954
+ const parentTransformId = parentMatch ? parentMatch[1] : "0";
5955
+ let strippedRootTransformId = null;
5956
+ for (const id of allToRemove) {
5957
+ const block = doc.find_by_file_id(id);
5958
+ if (block && block.class_id === 4 && block.is_stripped) {
5959
+ strippedRootTransformId = id;
5960
+ break;
5961
+ }
5962
+ }
5963
+ if (parentTransformId !== "0" && strippedRootTransformId !== null) {
5964
+ doc.remove_child_from_parent(parentTransformId, strippedRootTransformId);
5965
+ }
5966
+ doc.remove_blocks(allToRemove);
5967
+ if (!doc.validate()) {
5968
+ return { success: false, file_path, error: "Validation failed after deleting PrefabInstance" };
5969
+ }
5970
+ const writeResult = doc.save();
5971
+ if (!writeResult.success) {
5972
+ return { success: false, file_path, error: writeResult.error };
5973
+ }
5974
+ return {
5975
+ success: true,
5976
+ file_path,
5977
+ deleted_count: allToRemove.size
5978
+ };
5979
+ }
5980
+ // src/editor/duplicate.ts
5981
+ var import_fs13 = require("fs");
5982
+ var path6 = __toESM(require("path"));
5983
+ init_scanner();
5984
+ function findPrefabInstanceByName(doc, name) {
5985
+ const piBlocks = doc.find_by_class_id(1001);
5986
+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5987
+ const namePattern = new RegExp(`propertyPath:\\s*m_Name\\s+value:\\s*${escapedName}\\s`);
5988
+ for (const block of piBlocks) {
5989
+ if (namePattern.test(block.raw)) {
5990
+ return { id: parseInt(block.file_id, 10) };
5991
+ }
5992
+ }
5993
+ return null;
5994
+ }
5995
+ function duplicateGameObject(options) {
5996
+ const { file_path, object_name, new_name } = options;
5997
+ if (!import_fs13.existsSync(file_path)) {
5998
+ return { success: false, file_path, error: `File not found: ${file_path}` };
5999
+ }
6000
+ let doc;
6001
+ try {
6002
+ doc = UnityDocument.from_file(file_path);
6003
+ } catch (err) {
6004
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
6005
+ }
6006
+ const goResult = doc.require_unique_game_object(object_name);
6007
+ if ("error" in goResult) {
6008
+ const piMatch = findPrefabInstanceByName(doc, object_name);
6009
+ if (piMatch) {
6010
+ return { success: false, file_path, error: `"${object_name}" is a PrefabInstance (fileID: ${piMatch.id}). Cloning PrefabInstances is not yet supported. Consider unpacking it first with \`update prefab\`.` };
6011
+ }
6012
+ return { success: false, file_path, error: goResult.error };
6013
+ }
6014
+ const goBlock = goResult;
6015
+ const goFileId = goBlock.file_id;
6016
+ const componentIds = [];
6017
+ const compMatches = goBlock.raw.matchAll(/component:\s*\{fileID:\s*(\d+)\}/g);
6018
+ for (const cm of compMatches) {
6019
+ componentIds.push(cm[1]);
6020
+ }
6021
+ let transformId = null;
6022
+ let fatherId = "0";
6023
+ for (const compId of componentIds) {
6024
+ const compBlock = doc.find_by_file_id(compId);
6025
+ if (compBlock && compBlock.class_id === 4) {
6026
+ transformId = compId;
6027
+ const fatherMatch = compBlock.raw.match(/m_Father:\s*\{fileID:\s*(\d+)\}/);
6028
+ if (fatherMatch) {
6029
+ fatherId = fatherMatch[1];
6030
+ }
6031
+ break;
6032
+ }
6033
+ }
6034
+ const allOldIds = new Set([goFileId, ...componentIds]);
6035
+ if (transformId !== null) {
6036
+ const descendants = doc.collect_hierarchy(transformId);
6037
+ for (const id of descendants) {
6038
+ allOldIds.add(id);
6039
+ }
6040
+ }
6041
+ const existing = doc.all_file_ids();
6042
+ const idMap = new Map;
6043
+ for (const oldId of allOldIds) {
6044
+ let newId;
6045
+ do {
6046
+ newId = String(Math.floor(Math.random() * 9000000000) + 1e9);
6047
+ } while (existing.has(newId) || newId === "0");
6048
+ existing.add(newId);
6049
+ idMap.set(oldId, newId);
6050
+ }
6051
+ const rootOrder = transformId !== null ? doc.calculate_root_order(fatherId) : 0;
6052
+ for (const block of doc.blocks) {
6053
+ if (allOldIds.has(block.file_id)) {
6054
+ const cloned = block.clone();
6055
+ for (const [oldId, newId] of idMap) {
6056
+ cloned.remap_file_id(oldId, newId);
6057
+ }
6058
+ doc.append_block(cloned);
6059
+ }
6060
+ }
6061
+ const actualName = goBlock.get_property("m_Name") || object_name;
6062
+ const finalName = new_name || `${actualName} (1)`;
6063
+ const clonedGoBlock = doc.find_by_file_id(idMap.get(goFileId));
6064
+ if (clonedGoBlock) {
6065
+ clonedGoBlock.set_property("m_Name", finalName);
6066
+ }
6067
+ if (transformId !== null) {
6068
+ const clonedTransformBlock = doc.find_by_file_id(idMap.get(transformId));
6069
+ if (clonedTransformBlock) {
6070
+ let raw = clonedTransformBlock.raw;
6071
+ if (/m_RootOrder:\s*\d+/.test(raw)) {
6072
+ raw = raw.replace(/m_RootOrder:\s*\d+/, `m_RootOrder: ${rootOrder}`);
6073
+ } else {
6074
+ raw = raw.replace(/(m_Father:\s*\{fileID:\s*\d+\})/, `$1
6075
+ m_RootOrder: ${rootOrder}`);
6076
+ }
6077
+ clonedTransformBlock.replace_raw(raw);
6078
+ }
6079
+ }
6080
+ const clonedObjects = [];
6081
+ for (const oldId of allOldIds) {
6082
+ const originalBlock = doc.find_by_file_id(oldId);
6083
+ if (originalBlock && originalBlock.class_id === 1) {
6084
+ const newId = idMap.get(oldId);
6085
+ if (newId) {
6086
+ const clonedBlock = doc.find_by_file_id(newId);
6087
+ if (clonedBlock) {
6088
+ const name = clonedBlock.get_property("m_Name") || "";
6089
+ clonedObjects.push({ name, file_id: parseInt(newId, 10) });
6090
+ }
6091
+ }
6092
+ }
6093
+ }
6094
+ const newTransformId = transformId !== null ? idMap.get(transformId) : null;
6095
+ if (fatherId !== "0" && newTransformId !== null) {
6096
+ doc.add_child_to_parent(fatherId, newTransformId);
6097
+ }
6098
+ const warnings = [];
6099
+ const allGoBlocks = doc.find_by_class_id(1);
6100
+ const sceneNameCounts = new Map;
6101
+ for (const block of allGoBlocks) {
6102
+ const n = block.get_property("m_Name") || "";
6103
+ sceneNameCounts.set(n, (sceneNameCounts.get(n) || 0) + 1);
6104
+ }
6105
+ for (const cloned of clonedObjects) {
6106
+ const count = sceneNameCounts.get(cloned.name) || 0;
6107
+ if (count >= 2) {
6108
+ warnings.push(`Duplicate name "${cloned.name}" now appears ${count} times in scene. Use fileID ${cloned.file_id} to target this clone.`);
6109
+ }
6110
+ }
6111
+ if (!doc.validate()) {
6112
+ return { success: false, file_path, error: "Validation failed after duplicating GameObject" };
6113
+ }
6114
+ const saveResult = doc.save();
6115
+ if (!saveResult.success) {
6116
+ return { success: false, file_path, error: saveResult.error };
6117
+ }
6118
+ return {
6119
+ success: true,
6120
+ file_path,
6121
+ game_object_id: idMap.get(goFileId) ? parseInt(idMap.get(goFileId), 10) : undefined,
6122
+ transform_id: newTransformId ? parseInt(newTransformId, 10) : undefined,
6123
+ total_duplicated: allOldIds.size,
6124
+ cloned_objects: clonedObjects,
6125
+ warnings: warnings.length > 0 ? warnings : undefined
6126
+ };
6127
+ }
6128
+ function unpackPrefab(options) {
6129
+ const { file_path, prefab_instance, project_path } = options;
6130
+ if (!import_fs13.existsSync(file_path)) {
6131
+ return { success: false, file_path, error: `File not found: ${file_path}` };
6132
+ }
6133
+ let sceneDoc;
6134
+ try {
6135
+ sceneDoc = UnityDocument.from_file(file_path);
6136
+ } catch (err) {
6137
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
6138
+ }
6139
+ let prefabInstanceBlock = null;
6140
+ const asNumber = parseInt(prefab_instance, 10);
6141
+ if (!isNaN(asNumber)) {
6142
+ const block = sceneDoc.find_by_file_id(String(asNumber));
6143
+ if (block && block.class_id === 1001) {
6144
+ prefabInstanceBlock = block;
6145
+ }
6146
+ }
6147
+ if (!prefabInstanceBlock) {
6148
+ const piBlocks = sceneDoc.find_by_class_id(1001);
6149
+ const escapedName = prefab_instance.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6150
+ const nameModPattern = new RegExp(`propertyPath: m_Name\\s*\\n\\s*value: ${escapedName}\\s*$`, "m");
6151
+ for (const block of piBlocks) {
6152
+ if (nameModPattern.test(block.raw)) {
6153
+ prefabInstanceBlock = block;
6154
+ break;
6155
+ }
6156
+ }
6157
+ }
6158
+ if (!prefabInstanceBlock) {
6159
+ return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
6160
+ }
6161
+ const prefabInstanceId = prefabInstanceBlock.file_id;
6162
+ const sourcePrefabMatch = prefabInstanceBlock.raw.match(/m_SourcePrefab:[\s\S]*?guid:\s*([a-f0-9]+)/);
6163
+ if (!sourcePrefabMatch) {
6164
+ return { success: false, file_path, error: `Could not find m_SourcePrefab GUID in PrefabInstance block (fileID: ${prefabInstanceId})` };
6165
+ }
6166
+ const sourcePrefabGuid = sourcePrefabMatch[1];
6167
+ let sourcePrefabPath = null;
6168
+ if (project_path) {
6169
+ const guidCache = load_guid_cache_for_file(file_path, project_path);
6170
+ if (guidCache) {
6171
+ const resolvedPath = guidCache.resolve_absolute(sourcePrefabGuid);
6172
+ if (resolvedPath)
6173
+ sourcePrefabPath = resolvedPath;
6174
+ }
6175
+ }
6176
+ if (!sourcePrefabPath || !import_fs13.existsSync(sourcePrefabPath)) {
6177
+ const inferredProject = project_path || find_unity_project_root(path6.dirname(file_path));
6178
+ if (inferredProject) {
6179
+ const nativeBuild = getNativeBuildGuidCache();
6180
+ if (nativeBuild) {
6181
+ try {
6182
+ const freshCache = nativeBuild(inferredProject);
6183
+ if (freshCache[sourcePrefabGuid]) {
6184
+ sourcePrefabPath = path6.join(inferredProject, freshCache[sourcePrefabGuid]);
6185
+ }
6186
+ } catch {}
6187
+ }
6188
+ }
6189
+ }
6190
+ if (!sourcePrefabPath || !import_fs13.existsSync(sourcePrefabPath)) {
6191
+ const searchedPaths = [];
6192
+ if (project_path)
6193
+ searchedPaths.push(`GUID cache: ${path6.join(project_path, ".unity-agentic", "guid-cache.json")}`);
6194
+ const inferredProject = project_path || find_unity_project_root(path6.dirname(file_path));
6195
+ if (inferredProject)
6196
+ searchedPaths.push(`Native GUID rebuild from: ${inferredProject}`);
6197
+ const searchInfo = searchedPaths.length > 0 ? ` Searched: ${searchedPaths.join("; ")}` : "";
6198
+ return { success: false, file_path, error: `Could not resolve source prefab with GUID ${sourcePrefabGuid}.${searchInfo} Provide --project path or run 'unity-agentic-tools setup'.` };
6199
+ }
6200
+ let prefabDoc;
6201
+ try {
6202
+ prefabDoc = UnityDocument.from_file(sourcePrefabPath);
6203
+ } catch (err) {
6204
+ return { success: false, file_path, error: `Failed to read source prefab: ${err instanceof Error ? err.message : String(err)}` };
6205
+ }
6206
+ const prefabIds = prefabDoc.all_file_ids();
6207
+ const existing = sceneDoc.all_file_ids();
6208
+ const idMap = new Map;
6209
+ for (const oldId of prefabIds) {
6210
+ let newId;
6211
+ do {
6212
+ newId = String(Math.floor(Math.random() * 9000000000) + 1e9);
6213
+ } while (existing.has(newId) || newId === "0");
6214
+ existing.add(newId);
6215
+ idMap.set(oldId, newId);
6216
+ }
6217
+ const removedComponents = new Set;
6218
+ const removedSection = prefabInstanceBlock.raw.match(/m_RemovedComponents:\s*\n((?:\s*-\s*\{[^}]+\}\s*\n)*)/);
6219
+ if (removedSection) {
6220
+ const removedMatches = removedSection[1].matchAll(/fileID:\s*(\d+)/g);
6221
+ for (const rm of removedMatches) {
6222
+ removedComponents.add(rm[1]);
6223
+ }
6224
+ }
6225
+ const clonedBlocks = [];
6226
+ for (const block of prefabDoc.blocks) {
6227
+ if (removedComponents.has(block.file_id))
6228
+ continue;
6229
+ const cloned = block.clone();
6230
+ for (const [oldId, newId] of idMap) {
6231
+ cloned.remap_file_id(oldId, newId);
6232
+ }
6233
+ let raw = cloned.raw;
6234
+ raw = raw.replace(/\s*m_CorrespondingSourceObject:\s*\{[^}]+\}\n?/, `
6235
+ m_CorrespondingSourceObject: {fileID: 0}
6236
+ `);
6237
+ raw = raw.replace(/\s*m_PrefabInstance:\s*\{[^}]+\}\n?/, `
6238
+ m_PrefabInstance: {fileID: 0}
6239
+ `);
6240
+ raw = raw.replace(/\s*m_PrefabAsset:\s*\{[^}]+\}\n?/, `
6241
+ m_PrefabAsset: {fileID: 0}
6242
+ `);
6243
+ cloned.replace_raw(raw);
6244
+ clonedBlocks.push(cloned);
6245
+ }
6246
+ const modificationsSection = prefabInstanceBlock.raw.match(/m_Modifications:\s*\n((?:\s*-\s*target:[\s\S]*?(?=\s*m_RemovedComponents:|\s*m_RemovedGameObjects:|\s*m_AddedGameObjects:|\s*m_AddedComponents:|\s*m_SourcePrefab:))?)/);
6247
+ if (modificationsSection) {
6248
+ const modEntries = modificationsSection[1].split(/\n\s*-\s*target:/).filter((s) => s.trim());
6249
+ for (const entry of modEntries) {
6250
+ const targetIdMatch = entry.match(/\{fileID:\s*(\d+)/);
6251
+ const propPathMatch = entry.match(/propertyPath:\s*(.+)/);
6252
+ const valueMatch = entry.match(/value:\s*(.*)/);
6253
+ const objRefMatch = entry.match(/objectReference:\s*(\{[^}]*\})/);
6254
+ if (targetIdMatch && propPathMatch) {
6255
+ const targetOldId = targetIdMatch[1];
6256
+ const targetNewId = idMap.get(targetOldId);
6257
+ if (targetNewId === undefined)
6258
+ continue;
6259
+ const propertyPath = propPathMatch[1].trim();
6260
+ const value = valueMatch ? valueMatch[1].trim() : "";
6261
+ const objectReference = objRefMatch ? objRefMatch[1].trim() : "{fileID: 0}";
6262
+ let remappedObjRef = objectReference;
6263
+ if (objectReference !== "{fileID: 0}") {
6264
+ remappedObjRef = objectReference.replace(/(\{fileID:\s*)(\d+)/g, (match, prefix, oldId) => {
6265
+ if (oldId === "0")
6266
+ return match;
6267
+ return idMap.has(oldId) ? `${prefix}${idMap.get(oldId)}` : match;
6268
+ });
6269
+ }
6270
+ for (const clonedBlock of clonedBlocks) {
6271
+ if (clonedBlock.file_id === targetNewId) {
6272
+ clonedBlock.set_property(propertyPath, value, remappedObjRef);
6273
+ break;
6274
+ }
6275
+ }
6276
+ }
6277
+ }
6278
+ }
6279
+ const transformParentMatch = prefabInstanceBlock.raw.match(/m_TransformParent:\s*\{fileID:\s*(\d+)\}/);
6280
+ const transformParentId = transformParentMatch ? transformParentMatch[1] : "0";
6281
+ const rootInfo = prefabDoc.find_prefab_root();
6282
+ if (rootInfo && idMap.has(rootInfo.transform.file_id)) {
6283
+ const newRootTransformId = idMap.get(rootInfo.transform.file_id);
6284
+ for (const clonedBlock of clonedBlocks) {
6285
+ if (clonedBlock.file_id === newRootTransformId && clonedBlock.class_id === 4) {
6286
+ clonedBlock.set_property("m_Father", `{fileID: ${transformParentId}}`);
6287
+ break;
6288
+ }
6289
+ }
6290
+ }
6291
+ const strippedBlockIds = new Set;
6292
+ for (const block of sceneDoc.blocks) {
6293
+ if (block.is_stripped && block.raw.includes(`m_PrefabInstance: {fileID: ${prefabInstanceId}}`)) {
6294
+ strippedBlockIds.add(block.file_id);
6295
+ }
6296
+ }
6297
+ const blocksToRemove = new Set([prefabInstanceId, ...strippedBlockIds]);
6298
+ sceneDoc.remove_blocks(blocksToRemove);
6299
+ for (const clonedBlock of clonedBlocks) {
6300
+ sceneDoc.append_block(clonedBlock);
6301
+ }
6302
+ if (transformParentId !== "0" && rootInfo && idMap.has(rootInfo.transform.file_id)) {
6303
+ sceneDoc.add_child_to_parent(transformParentId, idMap.get(rootInfo.transform.file_id));
6304
+ }
6305
+ if (!sceneDoc.validate()) {
6306
+ return { success: false, file_path, error: "Validation failed after unpacking prefab" };
6307
+ }
6308
+ const saveResult = sceneDoc.save();
6309
+ if (!saveResult.success) {
6310
+ return { success: false, file_path, error: saveResult.error };
6311
+ }
6312
+ const newRootGoId = rootInfo ? idMap.get(rootInfo.game_object.file_id) : undefined;
6313
+ return {
6314
+ success: true,
6315
+ file_path,
6316
+ unpacked_count: clonedBlocks.length,
6317
+ root_game_object_id: newRootGoId ? parseInt(newRootGoId, 10) : undefined
6318
+ };
6319
+ }
2499
6320
  })