Files
T

3054 lines
106 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// packages/cocos-ext/src/scene.ts
var scene_exports = {};
__export(scene_exports, {
methods: () => methods
});
module.exports = __toCommonJS(scene_exports);
var path = __toESM(require("path"));
var COCOS_MCP_WIRE_PROTOCOL_VERSION = 1;
var uuidToNid = /* @__PURE__ */ new Map();
var nidToUuid = /* @__PURE__ */ new Map();
var detachedNodesForUndo = /* @__PURE__ */ new Map();
var DETACHED_NODE_STASH_MAX = 2048;
var nextNid = 1;
var currentSceneUuid = "";
var _modulePaths = module.paths;
try {
const EditorApp = globalThis.Editor?.App;
if (EditorApp?.path) {
const nm = path.join(EditorApp.path, "node_modules");
if (!_modulePaths.includes(nm)) {
_modulePaths.push(nm);
}
}
} catch {
}
function getCC() {
return require("cc");
}
function shortComponentName(comp, cc) {
try {
const full = cc.js?.getClassName?.(comp);
if (full && typeof full === "string") {
const parts2 = full.split(".");
return parts2[parts2.length - 1] || full;
}
} catch {
}
const raw = comp.name || comp.constructor?.name || "Component";
const parts = raw.split(".");
return parts[parts.length - 1] || raw;
}
function getOrCreateNid(uuid) {
let nid = uuidToNid.get(uuid);
if (nid === void 0) {
nid = nextNid++;
uuidToNid.set(uuid, nid);
nidToUuid.set(nid, uuid);
}
return nid;
}
function componentList(node, cc) {
let list = [];
try {
list = node.getComponents(cc.Component);
} catch {
return "";
}
const names = list.map((c) => shortComponentName(c, cc)).filter(Boolean);
return names.join(",");
}
function getCcValueCtors() {
return require("cc");
}
function getCcAssetManager() {
const cc = require("cc");
if (!cc.assetManager) {
throw new Error("cc.assetManager is unavailable in this runtime.");
}
return cc.assetManager;
}
function runtimeDirectSnapshot(value) {
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
return value;
}
if (!value || typeof value !== "object" || Array.isArray(value)) {
return void 0;
}
const record = value;
const copyNumbers = (keys) => {
const output = {};
for (const key of keys) {
const item = record[key];
if (typeof item !== "number" || !Number.isFinite(item)) {
return void 0;
}
output[key] = item;
}
return output;
};
return copyNumbers(["r", "g", "b", "a"]) ?? copyNumbers(["width", "height"]) ?? copyNumbers(["x", "y"]);
}
function materializeRuntimeDirectValue(value) {
if (value === null || typeof value !== "object") {
return value;
}
const cc = getCcValueCtors();
if ("r" in value && "g" in value && "b" in value && "a" in value) {
return new cc.Color(value.r, value.g, value.b, value.a);
}
if ("width" in value && "height" in value) {
return new cc.Size(value.width, value.height);
}
if ("x" in value && "y" in value) {
return new cc.Vec2(value.x, value.y);
}
return value;
}
function assetClassName(asset, cc) {
if (!asset || typeof asset !== "object") {
return "";
}
try {
const full = cc.js?.getClassName?.(asset);
if (typeof full === "string" && full.length > 0) {
return full;
}
} catch {
}
const constructor = asset.constructor;
return typeof constructor?.name === "string" ? constructor.name : "";
}
function validateAssetKind(asset, componentType, property, cc) {
const className = assetClassName(asset, cc).toLowerCase();
const normalizedProperty = property.trim().replace(/^_/, "").toLowerCase();
if (normalizedProperty === "spriteframe" && !className.includes("spriteframe")) {
return `Loaded asset is ${className || "unknown"}, expected SpriteFrame for ${componentType}.${property}.`;
}
if (normalizedProperty === "font" && !className.includes("font")) {
return `Loaded asset is ${className || "unknown"}, expected Font for ${componentType}.${property}.`;
}
return null;
}
function readAssetReferenceUuid(value, label) {
if (value === null || value === void 0) {
return { ok: true, uuid: null };
}
if (typeof value === "object" && !Array.isArray(value)) {
const uuid = value.uuid;
if (typeof uuid === "string" && uuid.trim().length > 0) {
return { ok: true, uuid: uuid.trim() };
}
}
return {
ok: false,
message: `${label} contains a non-null asset reference whose UUID cannot be observed safely.`
};
}
async function loadCcAssetByUuid(assetManager, uuid) {
const cached = assetManager.assets?.get?.(uuid);
if (cached) {
return cached;
}
if (typeof assetManager.loadAny !== "function") {
throw new Error("assetManager.loadAny is unavailable in this runtime.");
}
return await new Promise((resolve, reject) => {
let settled = false;
const timer = setTimeout(() => {
if (!settled) {
settled = true;
reject(new Error(`Timed out loading asset UUID ${uuid}.`));
}
}, 5e3);
const finish = (error, loaded) => {
if (settled) {
return;
}
settled = true;
clearTimeout(timer);
if (error) {
reject(error);
return;
}
const candidate = Array.isArray(loaded) ? loaded.find((item) => {
if (!item || typeof item !== "object") return false;
return item.uuid === uuid;
}) ?? loaded[0] : loaded;
if (!candidate) {
reject(new Error(`Asset manager returned no asset for UUID ${uuid}.`));
return;
}
resolve(candidate);
};
try {
assetManager.loadAny?.call(assetManager, uuid, finish);
} catch (error) {
finish(error instanceof Error ? error : new Error(String(error)));
}
});
}
function coerceNonNegativeNumber(val, prop) {
const n = typeof val === "number" ? val : Number(val);
if (!Number.isFinite(n)) {
throw new Error(`${prop} \u5FC5\u987B\u662F\u6709\u9650\u6570\u5B57\u3002`);
}
if (n < 0) {
throw new Error(`${prop} \u5FC5\u987B\u662F\u975E\u8D1F\u6570\u3002`);
}
return n;
}
function coerceFiniteNumber(val, prop) {
const n = typeof val === "number" ? val : Number(val);
if (!Number.isFinite(n)) {
throw new Error(`${prop} \u5FC5\u987B\u662F\u6709\u9650\u6570\u5B57\u3002`);
}
return n;
}
function coerceBoolean(val, prop) {
if (typeof val !== "boolean") {
throw new Error(`${prop} \u5FC5\u987B\u662F\u5E03\u5C14\u503C\u3002`);
}
return val;
}
function coerceString(val, prop, maxLen = 8192) {
if (typeof val !== "string") {
throw new Error(`${prop} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u3002`);
}
if (val.length > maxLen) {
throw new Error(`${prop} \u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7 ${maxLen}\u3002`);
}
return val;
}
function coerceEnumString(val, prop, mapping) {
if (typeof val !== "string") {
throw new Error(`${prop} \u5FC5\u987B\u662F\u679A\u4E3E\u5B57\u7B26\u4E32\u3002`);
}
const key = val.trim().toUpperCase();
const hit = mapping[key];
if (hit === void 0) {
throw new Error(`${prop} \u975E\u6CD5\u679A\u4E3E\u503C\u3002\u53EF\u9009\uFF1A${Object.keys(mapping).join(", ")}`);
}
return hit;
}
function parseHexColorRGBA(input) {
const s = input.trim();
if (!/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(s)) {
throw new Error("color \u5FC5\u987B\u662F #RRGGBB \u6216 #RRGGBBAA \u7684 Hex \u5B57\u7B26\u4E32\u3002");
}
const hex = s.slice(1);
const r = Number.parseInt(hex.slice(0, 2), 16);
const g = Number.parseInt(hex.slice(2, 4), 16);
const b = Number.parseInt(hex.slice(4, 6), 16);
const a = hex.length === 8 ? Number.parseInt(hex.slice(6, 8), 16) : 255;
return { r, g, b, a };
}
function coerceHexColor(val) {
if (typeof val !== "string") {
throw new Error("color \u5165\u53C2\u5FC5\u987B\u662F Hex \u5B57\u7B26\u4E32\u3002");
}
const rgba = parseHexColorRGBA(val);
const cc = getCcValueCtors();
const c = new cc.Color(rgba.r, rgba.g, rgba.b, rgba.a);
return { r: c.r, g: c.g, b: c.b, a: c.a };
}
function coerceToSize(val) {
if (!val || typeof val !== "object" || Array.isArray(val)) {
throw new Error("size \u5165\u53C2\u5FC5\u987B\u662F { width, height } \u5BF9\u8C61\u3002");
}
const o = val;
const cc = getCcValueCtors();
const w = coerceNonNegativeNumber(o.width, "width");
const h = coerceNonNegativeNumber(o.height, "height");
const sz = new cc.Size(w, h);
return { width: sz.width, height: sz.height };
}
function coerceToVec2(val) {
if (!val || typeof val !== "object" || Array.isArray(val)) {
throw new Error("vec2 \u5165\u53C2\u5FC5\u987B\u662F { x, y } \u5BF9\u8C61\u3002");
}
const o = val;
const cc = getCcValueCtors();
const x = typeof o.x === "number" ? o.x : Number(o.x);
const y = typeof o.y === "number" ? o.y : Number(o.y);
if (!Number.isFinite(x) || !Number.isFinite(y)) {
throw new Error("vec2 \u5750\u6807\u5FC5\u987B\u662F\u6709\u9650\u6570\u5B57\u3002");
}
const v = new cc.Vec2(x, y);
return { x: v.x, y: v.y };
}
function coerceAssetUuid(val, prop) {
if (typeof val !== "string") {
throw new Error(`${prop} \u8D44\u6E90\u5165\u53C2\u5FC5\u987B\u662F UUID \u5B57\u7B26\u4E32\u3002`);
}
const uuid = val.trim();
if (!uuid) {
throw new Error(`${prop} \u8D44\u6E90 UUID \u4E0D\u80FD\u4E3A\u7A7A\u3002`);
}
return { __uuid__: uuid };
}
var COMPONENT_SCHEMAS = {
UITransform: {
width: {
component: "UITransform",
prop: "width",
type: "number",
coercer: (val) => coerceNonNegativeNumber(val, "width")
},
height: {
component: "UITransform",
prop: "height",
type: "number",
coercer: (val) => coerceNonNegativeNumber(val, "height")
},
anchorPoint: {
component: "UITransform",
prop: "anchorPoint",
type: "vec2",
coercer: coerceToVec2
}
},
Sprite: {
color: {
component: "Sprite",
prop: "color",
type: "hex-color",
coercer: coerceHexColor
},
spriteFrame: {
component: "Sprite",
prop: "spriteFrame",
dumpProp: "_spriteFrame",
type: "asset-uuid",
coercer: (val) => coerceAssetUuid(val, "spriteFrame")
},
type: {
component: "Sprite",
prop: "type",
type: "enum-string",
coercer: (val) => coerceEnumString(val, "type", { SIMPLE: 0, SLICED: 1, TILED: 2, FILLED: 3, MESH: 4 })
},
sizeMode: {
component: "Sprite",
prop: "sizeMode",
type: "enum-string",
coercer: (val) => coerceEnumString(val, "sizeMode", { CUSTOM: 0, TRIMMED: 1, RAW: 2 })
}
},
Label: {
color: {
component: "Label",
prop: "color",
type: "hex-color",
coercer: coerceHexColor
},
string: {
component: "Label",
prop: "string",
type: "string",
coercer: (val) => coerceString(val, "string")
},
fontSize: {
component: "Label",
prop: "fontSize",
type: "number",
coercer: (val) => coerceNonNegativeNumber(val, "fontSize")
},
lineHeight: {
component: "Label",
prop: "lineHeight",
type: "number",
coercer: (val) => coerceNonNegativeNumber(val, "lineHeight")
},
overflow: {
component: "Label",
prop: "overflow",
type: "enum-string",
coercer: (val) => coerceEnumString(val, "overflow", { NONE: 0, CLAMP: 1, SHRINK: 2, RESIZE_HEIGHT: 3 })
},
horizontalAlign: {
component: "Label",
prop: "horizontalAlign",
type: "enum-string",
coercer: (val) => coerceEnumString(val, "horizontalAlign", { LEFT: 0, CENTER: 1, RIGHT: 2 })
},
verticalAlign: {
component: "Label",
prop: "verticalAlign",
type: "enum-string",
coercer: (val) => coerceEnumString(val, "verticalAlign", { TOP: 0, CENTER: 1, BOTTOM: 2 })
},
font: {
component: "Label",
prop: "font",
dumpProp: "_font",
type: "asset-uuid",
coercer: (val) => coerceAssetUuid(val, "font")
}
},
Button: {
interactable: {
component: "Button",
prop: "interactable",
type: "boolean",
coercer: (val) => coerceBoolean(val, "interactable")
},
transition: {
component: "Button",
prop: "transition",
type: "enum-string",
coercer: (val) => coerceEnumString(val, "transition", { NONE: 0, COLOR: 1, SPRITE: 2, SCALE: 3 })
},
zoomScale: {
component: "Button",
prop: "zoomScale",
type: "number",
coercer: (val) => coerceNonNegativeNumber(val, "zoomScale")
}
},
Layout: {
type: {
component: "Layout",
prop: "type",
type: "enum-string",
coercer: (val) => coerceEnumString(val, "type", { NONE: 0, HORIZONTAL: 1, VERTICAL: 2, GRID: 3 })
},
resizeMode: {
component: "Layout",
prop: "resizeMode",
type: "enum-string",
coercer: (val) => coerceEnumString(val, "resizeMode", { NONE: 0, CONTAINER: 1, CHILDREN: 2 })
},
constraint: {
component: "Layout",
prop: "constraint",
type: "enum-string",
coercer: (val) => coerceEnumString(val, "constraint", { NONE: 0, FIXED_ROW: 1, FIXED_COL: 2 })
},
spacingX: {
component: "Layout",
prop: "spacingX",
type: "number",
coercer: (val) => coerceNonNegativeNumber(val, "spacingX")
},
spacingY: {
component: "Layout",
prop: "spacingY",
type: "number",
coercer: (val) => coerceNonNegativeNumber(val, "spacingY")
},
paddingLeft: {
component: "Layout",
prop: "paddingLeft",
type: "number",
coercer: (val) => coerceNonNegativeNumber(val, "paddingLeft")
},
paddingRight: {
component: "Layout",
prop: "paddingRight",
type: "number",
coercer: (val) => coerceNonNegativeNumber(val, "paddingRight")
},
paddingTop: {
component: "Layout",
prop: "paddingTop",
type: "number",
coercer: (val) => coerceNonNegativeNumber(val, "paddingTop")
},
paddingBottom: {
component: "Layout",
prop: "paddingBottom",
type: "number",
coercer: (val) => coerceNonNegativeNumber(val, "paddingBottom")
},
cellSize: {
component: "Layout",
prop: "cellSize",
type: "size",
coercer: coerceToSize
},
constraintNum: {
component: "Layout",
prop: "constraintNum",
type: "number",
coercer: (val) => coerceNonNegativeNumber(val, "constraintNum")
}
},
Widget: {
isAlignTop: {
component: "Widget",
prop: "isAlignTop",
type: "boolean",
coercer: (val) => coerceBoolean(val, "isAlignTop")
},
isAlignBottom: {
component: "Widget",
prop: "isAlignBottom",
type: "boolean",
coercer: (val) => coerceBoolean(val, "isAlignBottom")
},
isAlignLeft: {
component: "Widget",
prop: "isAlignLeft",
type: "boolean",
coercer: (val) => coerceBoolean(val, "isAlignLeft")
},
isAlignRight: {
component: "Widget",
prop: "isAlignRight",
type: "boolean",
coercer: (val) => coerceBoolean(val, "isAlignRight")
},
isAlignHorizontalCenter: {
component: "Widget",
prop: "isAlignHorizontalCenter",
type: "boolean",
coercer: (val) => coerceBoolean(val, "isAlignHorizontalCenter")
},
isAlignVerticalCenter: {
component: "Widget",
prop: "isAlignVerticalCenter",
type: "boolean",
coercer: (val) => coerceBoolean(val, "isAlignVerticalCenter")
},
top: {
component: "Widget",
prop: "top",
type: "number",
coercer: (val) => coerceFiniteNumber(val, "top")
},
bottom: {
component: "Widget",
prop: "bottom",
type: "number",
coercer: (val) => coerceFiniteNumber(val, "bottom")
},
left: {
component: "Widget",
prop: "left",
type: "number",
coercer: (val) => coerceFiniteNumber(val, "left")
},
right: {
component: "Widget",
prop: "right",
type: "number",
coercer: (val) => coerceFiniteNumber(val, "right")
},
horizontalCenter: {
component: "Widget",
prop: "horizontalCenter",
type: "number",
coercer: (val) => coerceFiniteNumber(val, "horizontalCenter")
},
verticalCenter: {
component: "Widget",
prop: "verticalCenter",
type: "number",
coercer: (val) => coerceFiniteNumber(val, "verticalCenter")
}
}
};
function shouldSkipNode(name, includeInternal) {
if (includeInternal) {
return false;
}
return name.startsWith("_");
}
var EDITOR_DECORATION_ROOT_NAMES = new Set(
[
"Editor Scene Foreground",
"Editor Scene Background",
"gizmoRoot",
"GAME_VIEW",
"Editor Debugger",
"Scene Gizmo Camera",
"Scene Gizmo Root",
"Internal Gizmo",
"Scene Editor Camera"
].map((n) => n.toLowerCase())
);
function shouldSkipEditorDecorationRoot(name, includeEditorDecorations) {
if (includeEditorDecorations) {
return false;
}
return EDITOR_DECORATION_ROOT_NAMES.has(name.trim().toLowerCase());
}
function parentInspectMeta(node, scene) {
const par = node.parent ?? null;
if (par == null || par.uuid === scene.uuid) {
return { parentNid: null, parentRelation: "scene" };
}
const pnid = uuidToNid.get(par.uuid);
if (pnid !== void 0) {
return { parentNid: pnid, parentRelation: "indexed" };
}
return { parentNid: null, parentRelation: "unknown" };
}
function findNodeByUuid(root, targetUuid) {
if (!root) {
return null;
}
if (root.uuid === targetUuid) {
return root;
}
for (const child of root.children) {
const found = findNodeByUuid(child, targetUuid);
if (found) {
return found;
}
}
return null;
}
function releaseDetachedNodeStash(uuid) {
const stashed = detachedNodesForUndo.get(uuid);
if (!stashed) {
return { released: false, destroyed: false, liveNodePreserved: false };
}
let reachable = null;
try {
reachable = findNodeByUuid(stashed.scene, uuid);
} catch {
reachable = null;
}
const liveNodePreserved = reachable === stashed.node || stashed.node.parent !== null;
detachedNodesForUndo.delete(uuid);
if (liveNodePreserved) {
return { released: true, destroyed: false, liveNodePreserved: true };
}
try {
stashed.node.destroy?.();
return { released: true, destroyed: true, liveNodePreserved: false };
} catch {
return { released: true, destroyed: false, liveNodePreserved: false };
}
}
function nearlyEqual(actual, expected, epsilon = 0.01) {
return typeof actual === "number" && Number.isFinite(actual) && Math.abs(actual - expected) <= epsilon;
}
function requestedUiSize(expected) {
const defaults = expected.kind === "label" ? { width: 220, height: 48 } : expected.kind === "button" ? { width: 180, height: 56 } : expected.kind === "container" ? { width: 320, height: 180 } : { width: 100, height: 100 };
return expected.size ?? defaults;
}
function settledUiSize(expected) {
const seed = requestedUiSize(expected);
const layout = expected.layout;
if (!layout || layout.resizeMode !== "container") {
return seed;
}
const implicitChildren = expected.kind === "button" ? [
{
kind: "label",
size: expected.size ?? { width: 180, height: 56 },
fontSize: expected.fontSize ?? 24
}
] : [];
const children = [...implicitChildren, ...expected.children ?? []].filter((child) => child.active !== false).map((child) => settledUiSize(child));
const count = children.length;
const paddingH = (layout.paddingLeft ?? 0) + (layout.paddingRight ?? 0);
const paddingV = (layout.paddingTop ?? 0) + (layout.paddingBottom ?? 0);
if (layout.type === "horizontal") {
return {
width: children.reduce((sum, child) => sum + child.width, 0) + (count - 1) * (layout.spacingX ?? 0) + paddingH,
height: seed.height
};
}
if (layout.type === "vertical") {
return {
width: seed.width,
height: children.reduce((sum, child) => sum + child.height, 0) + (count - 1) * (layout.spacingY ?? 0) + paddingV
};
}
let columns = null;
const constraintNum = layout.constraintNum ?? 2;
const constraint = layout.constraint ?? ((layout.constraintNum ?? 0) > 0 ? "fixed-column" : "none");
if (constraint === "fixed-column" && constraintNum > 0) {
columns = constraintNum;
} else if (constraint === "fixed-row" && constraintNum > 0) {
columns = count > 0 ? Math.ceil(count / constraintNum) : 1;
}
const rowHeights = [];
if (columns !== null) {
for (let index = 0; index < count; index += columns) {
rowHeights.push(Math.max(...children.slice(index, index + columns).map((child) => child.height), 0));
}
} else {
const availableWidth = Math.max(0, seed.width - paddingH);
let rowWidth = 0;
let rowHeight = 0;
for (const child of children) {
const nextWidth = rowWidth === 0 ? child.width : rowWidth + (layout.spacingX ?? 0) + child.width;
if (rowWidth > 0 && nextWidth > availableWidth) {
rowHeights.push(rowHeight);
rowWidth = child.width;
rowHeight = child.height;
} else {
rowWidth = nextWidth;
rowHeight = Math.max(rowHeight, child.height);
}
}
if (rowWidth > 0) {
rowHeights.push(rowHeight);
}
}
return {
width: seed.width,
height: rowHeights.reduce((sum, height) => sum + height, 0) + Math.max(0, rowHeights.length - 1) * (layout.spacingY ?? 0) + paddingV
};
}
function uiComponent(node, cc, name) {
try {
return node.getComponents(cc.Component).find((component) => shortComponentName(component, cc) === name) ?? null;
} catch {
return null;
}
}
function rgbaMatches(actual, expectedHex) {
if (!actual || typeof actual !== "object" || Array.isArray(actual)) {
return false;
}
const record = actual;
const expected = parseHexColorRGBA(expectedHex);
return record.r === expected.r && record.g === expected.g && record.b === expected.b && record.a === expected.a;
}
function verifyUiNodeRuntime(node, expected, cc) {
const mismatches = [];
const parentLayout = node.parent ? uiComponent(node.parent, cc, "Layout") : null;
const widgetOwnsX = expected.widget?.left !== void 0 || expected.widget?.right !== void 0 || expected.widget?.horizontalCenter !== void 0;
const widgetOwnsY = expected.widget?.top !== void 0 || expected.widget?.bottom !== void 0 || expected.widget?.verticalCenter !== void 0;
const parentLayoutOwnsX = parentLayout?.type === 1 || parentLayout?.type === 3;
const parentLayoutOwnsY = parentLayout?.type === 2 || parentLayout?.type === 3;
if (expected.name && node.name !== expected.name) {
mismatches.push(`name expected ${expected.name}, observed ${node.name}`);
}
if (expected.active !== void 0 && node.active !== expected.active) {
mismatches.push(`active expected ${String(expected.active)}, observed ${String(node.active)}`);
}
if (expected.position) {
for (const axis of ["x", "y", "z"]) {
const engineOwned = axis === "x" && (widgetOwnsX || parentLayoutOwnsX) || axis === "y" && (widgetOwnsY || parentLayoutOwnsY);
if (engineOwned) {
if (!Number.isFinite(node.position[axis])) {
mismatches.push(`engine-owned position.${axis} is invalid: ${String(node.position[axis])}`);
}
} else if (!nearlyEqual(node.position[axis], expected.position[axis])) {
mismatches.push(`position.${axis} expected ${expected.position[axis]}, observed ${node.position[axis]}`);
}
}
}
const settledExpectedSize = node.activeInHierarchy === false ? requestedUiSize(expected) : settledUiSize(expected);
let expectedWidth = settledExpectedSize.width;
let expectedHeight = settledExpectedSize.height;
const numeric = (value, fallback = 0) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
const scaleX = Math.abs(node.scale.x) || 1;
const scaleY = Math.abs(node.scale.y) || 1;
const parentTransform = node.parent ? uiComponent(node.parent, cc, "UITransform") : null;
const parentContentSize = parentTransform?.contentSize;
const parentWidth = numeric(parentTransform?.width, numeric(parentContentSize?.width));
const parentHeight = numeric(parentTransform?.height, numeric(parentContentSize?.height));
if (expected.widget?.left !== void 0 && expected.widget?.right !== void 0 && node.parent) {
expectedWidth = (parentWidth - expected.widget.left - expected.widget.right) / scaleX;
}
if (expected.widget?.top !== void 0 && expected.widget?.bottom !== void 0 && node.parent) {
expectedHeight = (parentHeight - expected.widget.top - expected.widget.bottom) / scaleY;
}
if (parentLayout?.resizeMode === 2 && node.parent) {
const usefulChildren = node.parent.children.filter(
(child) => child.activeInHierarchy !== false && uiComponent(child, cc, "UITransform") !== null
);
const childCount = usefulChildren.length;
const affectedByScale = parentLayout.affectedByScale === true;
const usedScaleX = affectedByScale ? scaleX : 1;
const usedScaleY = affectedByScale ? scaleY : 1;
if (parentLayout.type === 1 && childCount > 0) {
expectedWidth = (parentWidth - numeric(parentLayout.paddingLeft) - numeric(parentLayout.paddingRight) - (childCount - 1) * numeric(parentLayout.spacingX)) / childCount / usedScaleX;
} else if (parentLayout.type === 2 && childCount > 0) {
expectedHeight = (parentHeight - numeric(parentLayout.paddingTop) - numeric(parentLayout.paddingBottom) - (childCount - 1) * numeric(parentLayout.spacingY)) / childCount / usedScaleY;
} else if (parentLayout.type === 3) {
const cellSize = parentLayout.cellSize;
expectedWidth = numeric(cellSize?.width) / usedScaleX;
expectedHeight = numeric(cellSize?.height) / usedScaleY;
}
}
const transform = uiComponent(node, cc, "UITransform");
if (!transform) {
mismatches.push("UITransform component missing");
} else {
const contentSize = transform.contentSize;
const width = typeof transform.width === "number" ? transform.width : contentSize?.width;
const height = typeof transform.height === "number" ? transform.height : contentSize?.height;
if (!nearlyEqual(width, expectedWidth)) {
mismatches.push(`width expected ${expectedWidth}, observed ${String(width)}`);
}
if (!nearlyEqual(height, expectedHeight)) {
mismatches.push(`height expected ${expectedHeight}, observed ${String(height)}`);
}
}
if (expected.kind === "shape" || expected.kind === "sprite" || expected.kind === "button") {
const sprite = uiComponent(node, cc, "Sprite");
if (!sprite) {
mismatches.push("Sprite component missing");
} else {
if (!sprite.spriteFrame) {
mismatches.push("Sprite.spriteFrame is empty");
}
const expectedColor = expected.color ?? (expected.kind === "shape" ? "#FFFFFFFF" : expected.kind === "button" ? "#3478F6FF" : void 0);
if (expectedColor && !rgbaMatches(sprite.color, expectedColor)) {
mismatches.push(`Sprite.color does not match ${expectedColor}`);
}
if (expected.nineSlice === true && sprite.type !== 1) {
mismatches.push(`Sprite.type expected SLICED(1), observed ${String(sprite.type)}`);
}
}
}
if (expected.kind === "label") {
const label = uiComponent(node, cc, "Label");
if (!label) {
mismatches.push("Label component missing");
} else {
const expectedText = expected.text ?? expected.buttonLabel ?? "Label";
if (label.string !== expectedText) {
mismatches.push(`Label.string expected ${JSON.stringify(expectedText)}, observed ${JSON.stringify(label.string)}`);
}
if (!nearlyEqual(label.fontSize, expected.fontSize ?? 28)) {
mismatches.push(`Label.fontSize expected ${expected.fontSize ?? 28}, observed ${String(label.fontSize)}`);
}
if (!nearlyEqual(label.lineHeight, expected.lineHeight ?? expected.fontSize ?? 32)) {
mismatches.push(
`Label.lineHeight expected ${expected.lineHeight ?? expected.fontSize ?? 32}, observed ${String(label.lineHeight)}`
);
}
if (label.overflow !== 1) {
mismatches.push(`Label.overflow expected CLAMP(1), observed ${String(label.overflow)}`);
}
const horizontal = { left: 0, center: 1, right: 2 };
const vertical = { top: 0, center: 1, bottom: 2 };
if (label.horizontalAlign !== horizontal[expected.horizontalAlign ?? "center"]) {
mismatches.push("Label.horizontalAlign does not match");
}
if (label.verticalAlign !== vertical[expected.verticalAlign ?? "center"]) {
mismatches.push("Label.verticalAlign does not match");
}
if (expected.color && !rgbaMatches(label.color, expected.color)) {
mismatches.push(`Label.color does not match ${expected.color}`);
}
}
}
if (expected.kind === "button") {
const button = uiComponent(node, cc, "Button");
if (!button) {
mismatches.push("Button component missing");
} else if (button.interactable !== (expected.interactable !== false)) {
mismatches.push(`Button.interactable expected ${String(expected.interactable !== false)}`);
}
}
if (expected.layout) {
const layout = uiComponent(node, cc, "Layout");
if (!layout) {
mismatches.push("Layout component missing");
} else {
const typeMap = { horizontal: 1, vertical: 2, grid: 3 };
const resizeMap = { none: 0, container: 1, children: 2 };
const constraintMap = { none: 0, "fixed-row": 1, "fixed-column": 2 };
if (layout.type !== typeMap[expected.layout.type]) {
mismatches.push(`Layout.type does not match ${expected.layout.type}`);
}
if (layout.resizeMode !== resizeMap[expected.layout.resizeMode ?? "none"]) {
mismatches.push(`Layout.resizeMode does not match ${expected.layout.resizeMode ?? "none"}`);
}
if (expected.layout.constraint && layout.constraint !== constraintMap[expected.layout.constraint]) {
mismatches.push(`Layout.constraint does not match ${expected.layout.constraint}`);
}
for (const property of [
"spacingX",
"spacingY",
"paddingLeft",
"paddingRight",
"paddingTop",
"paddingBottom",
"constraintNum"
]) {
const expectedValue = expected.layout[property];
if (typeof expectedValue === "number" && !nearlyEqual(layout[property], expectedValue)) {
mismatches.push(`Layout.${property} expected ${expectedValue}, observed ${String(layout[property])}`);
}
}
if (expected.layout.cellSize) {
const cellSize = layout.cellSize;
if (!cellSize || !nearlyEqual(cellSize.width, expected.layout.cellSize.width) || !nearlyEqual(cellSize.height, expected.layout.cellSize.height)) {
mismatches.push("Layout.cellSize does not match");
}
}
}
}
if (expected.widget) {
const widget = uiComponent(node, cc, "Widget");
if (!widget) {
mismatches.push("Widget component missing");
} else {
const alignments = [
["top", "isAlignTop"],
["bottom", "isAlignBottom"],
["left", "isAlignLeft"],
["right", "isAlignRight"],
["horizontalCenter", "isAlignHorizontalCenter"],
["verticalCenter", "isAlignVerticalCenter"]
];
for (const [valueProperty, enabledProperty] of alignments) {
const expectedValue = expected.widget[valueProperty];
if (typeof expectedValue === "number") {
if (widget[enabledProperty] !== true) {
mismatches.push(`Widget.${enabledProperty} is not enabled`);
}
if (!nearlyEqual(widget[valueProperty], expectedValue)) {
mismatches.push(`Widget.${valueProperty} expected ${expectedValue}, observed ${String(widget[valueProperty])}`);
}
}
}
}
}
return mismatches;
}
function resolveComponentTypeInput(input) {
const raw = input.trim();
if (!raw) {
throw new Error("componentType must be non-empty.");
}
if (raw.includes(".")) {
return raw;
}
if (raw.startsWith("cc")) {
return raw;
}
return `cc.${raw}`;
}
function resolveCtorByPath(cc, fullName) {
const raw = fullName.trim();
if (!raw) {
return null;
}
const pathParts = raw.split(".").filter(Boolean);
if (pathParts.length === 0) {
return null;
}
const parts = pathParts.length === 1 ? ["cc", pathParts[0]] : pathParts;
let cur = cc;
for (const p of parts) {
if (!cur || typeof cur !== "object") {
return null;
}
cur = cur[p];
}
if (typeof cur === "function") {
return cur;
}
try {
const ccJs = cc.js ?? void 0;
const byName = ccJs?.getClassByName?.(raw);
if (typeof byName === "function") {
return byName;
}
if (raw.startsWith("cc.")) {
const short = raw.slice(3);
const byShort = ccJs?.getClassByName?.(short);
if (typeof byShort === "function") {
return byShort;
}
}
} catch {
}
return null;
}
function resolveComponentCtor(rawType) {
const trimmed = rawType.trim();
if (!trimmed) {
return null;
}
const fullName = trimmed.includes(".") ? trimmed : `cc.${trimmed}`;
const ccAny = getCC();
const byPath = resolveCtorByPath(ccAny, fullName);
if (byPath) {
return byPath;
}
const js = ccAny.js;
if (typeof js?.getClassByName === "function") {
try {
const c = js.getClassByName(fullName);
if (typeof c === "function") {
return c;
}
} catch {
}
}
return null;
}
function isNodeUnderAncestorUuid(node, ancestorUuid) {
let cur = node;
while (cur) {
if (cur.uuid === ancestorUuid) {
return true;
}
cur = cur.parent ?? null;
}
return false;
}
function buildInspectPlain(node, cc, scene) {
const p = node.position;
const e = node.eulerAngles;
const s = node.scale;
const wp = node.worldPosition ?? p;
const activeInHierarchy = typeof node.activeInHierarchy === "boolean" ? node.activeInHierarchy : node.active;
const layer = typeof node.layer === "number" && Number.isFinite(node.layer) ? node.layer : 0;
const childCount = Array.isArray(node.children) ? node.children.length : 0;
const { parentNid, parentRelation } = parentInspectMeta(node, scene);
let comps = [];
try {
comps = node.getComponents(cc.Component);
} catch {
comps = [];
}
const components = comps.map((c) => shortComponentName(c, cc));
return {
name: node.name,
active: node.active,
activeInHierarchy,
layer,
childCount,
position: { x: p.x, y: p.y, z: p.z },
worldPosition: { x: wp.x, y: wp.y, z: wp.z },
rotationEuler: { x: e.x, y: e.y, z: e.z },
scale: { x: s.x, y: s.y, z: s.z },
components,
parentNid,
parentRelation
};
}
var methods = {
ping() {
return { status: "ok", layer: "scene" };
},
/**
* Runtime scene name + asset uuid for Main (`asset-db` → MCP `assetPath`). UUID is never sent to MCP clients.
*/
getCurrentSceneMeta() {
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: true, hasScene: false };
}
return { ok: true, hasScene: true, sceneName: scene.name, sceneAssetUuid: scene.uuid };
},
getHierarchy(...args) {
const opts = args[0] || {};
const maxDepth = opts.maxDepth !== void 0 ? Math.max(0, Math.min(100, opts.maxDepth)) : 5;
const maxNodes = opts.maxNodes !== void 0 ? Math.max(1, Math.min(5e4, opts.maxNodes)) : 800;
const includeInternal = opts.includeInternal === true;
const includeEditorDecorations = opts.includeEditorDecorations === true;
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return "[Error] No active scene in director.";
}
if (scene.uuid !== currentSceneUuid) {
uuidToNid.clear();
nidToUuid.clear();
nextNid = 1;
currentSceneUuid = scene.uuid;
}
const lines = [];
lines.push(
`[Scene: ${scene.name} | proto:${COCOS_MCP_WIRE_PROTOCOL_VERSION} | Session Time: ${Date.now()} | Note: NIDs are session-only | editorDecor:${includeEditorDecorations ? "on" : "off"}]`
);
let nodeCount = 0;
let truncatedByDepth = false;
let truncatedByNodes = false;
const visit = (node, depth) => {
if (nodeCount >= maxNodes) {
truncatedByNodes = true;
return;
}
if (shouldSkipNode(node.name, includeInternal)) {
return;
}
if (shouldSkipEditorDecorationRoot(node.name, includeEditorDecorations)) {
return;
}
if (depth > maxDepth) {
truncatedByDepth = true;
return;
}
const nid = getOrCreateNid(node.uuid);
nodeCount += 1;
const indent = " ".repeat(depth);
const comps = componentList(node, cc);
const inactive = node.active ? "" : " [INACTIVE]";
lines.push(`${indent}[${nid}] ${node.name} (${comps})${inactive}`);
if (depth === maxDepth) {
if (node.children.length > 0) {
truncatedByDepth = true;
}
return;
}
for (const child of node.children) {
if (nodeCount >= maxNodes) {
truncatedByNodes = true;
return;
}
visit(child, depth + 1);
}
};
visit(scene, 0);
if (truncatedByDepth) {
lines.push("... (truncated by maxDepth limitation)");
}
if (truncatedByNodes) {
lines.push("... (truncated by maxNodes limitation)");
}
return lines.join("\n");
},
searchNodes(...args) {
const opts = args[0] || {};
const nameSub = (opts.nameSubstring ?? "").trim().toLowerCase();
const compF = (opts.componentFilter ?? "").trim().toLowerCase();
if (!nameSub && !compF) {
return "[Error] Provide non-empty nameSubstring and/or componentFilter to limit matches.";
}
const maxResults = opts.maxResults !== void 0 ? Math.max(1, Math.min(500, opts.maxResults)) : 80;
const includeInternal = opts.includeInternal === true;
const includeEditorDecorations = opts.includeEditorDecorations === true;
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return "[Error] No active scene in director.";
}
if (scene.uuid !== currentSceneUuid) {
uuidToNid.clear();
nidToUuid.clear();
nextNid = 1;
currentSceneUuid = scene.uuid;
}
const lines = [];
lines.push(
`[Search | Scene: ${scene.name} | proto:${COCOS_MCP_WIRE_PROTOCOL_VERSION} | maxResults: ${maxResults} | NIDs session-scoped]`
);
let count = 0;
let capped = false;
const visit = (node, pathSegs) => {
if (count >= maxResults) {
capped = true;
return;
}
if (shouldSkipNode(node.name, includeInternal)) {
return;
}
if (shouldSkipEditorDecorationRoot(node.name, includeEditorDecorations)) {
return;
}
const comps = componentList(node, cc);
const nm = node.name.toLowerCase();
const cl = comps.toLowerCase();
const nameOk = !nameSub || nm.includes(nameSub);
const compOk = !compF || cl.includes(compF);
if (nameOk && compOk) {
count += 1;
const nid = getOrCreateNid(node.uuid);
const pathStr = pathSegs.length > 0 ? `${pathSegs.join("/")}/${node.name}` : node.name;
lines.push(`[${nid}] ${pathStr} (${comps})`);
}
for (const child of node.children) {
if (count >= maxResults) {
capped = true;
return;
}
visit(child, [...pathSegs, node.name]);
}
};
for (const child of scene.children) {
if (count >= maxResults) {
capped = true;
break;
}
visit(child, [scene.name]);
}
if (count === 0) {
lines.push("(no matches)");
}
if (capped) {
lines.push("... (truncated by maxResults)");
}
return lines.join("\n");
},
inspectNode(...args) {
const raw = args[0];
const nid = raw?.nid;
if (typeof nid !== "number" || !Number.isFinite(nid) || !Number.isInteger(nid)) {
return { ok: false, error: "invalid_args", message: "nid must be an integer (use getHierarchy NIDs)." };
}
const uuid = nidToUuid.get(nid);
if (!uuid) {
return {
ok: false,
error: "nid_not_found",
message: "Unknown nid \u2014 scene may have changed; call getHierarchy again."
};
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, error: "no_scene", message: "No active scene." };
}
if (scene.uuid !== currentSceneUuid) {
return {
ok: false,
error: "stale_session",
message: "Scene changed since last getHierarchy; refresh NID map first."
};
}
const node = findNodeByUuid(scene, uuid);
if (!node) {
return {
ok: false,
error: "node_missing",
message: "Node uuid no longer in scene; call getHierarchy again."
};
}
return { ok: true, data: buildInspectPlain(node, cc, scene) };
},
/**
* Force deferred Label/Layout/Widget work to a stable runtime state before
* task-level verification. Creator otherwise flushes these components on a
* later render frame, which can make an immediate read a false positive.
*/
settleUiNodes(...args) {
const rawNids = args[0];
if (!Array.isArray(rawNids) || rawNids.length < 1 || rawNids.length > 64) {
return {
ok: false,
error: "ui_settle_failed",
message: "settleUiNodes requires an array of 1 to 64 nids."
};
}
const nids = [...new Set(rawNids)];
if (nids.some((nid) => typeof nid !== "number" || !Number.isInteger(nid))) {
return {
ok: false,
error: "ui_settle_failed",
message: "settleUiNodes requires integer nids."
};
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, error: "ui_settle_failed", message: "No active scene." };
}
const nodes = [];
for (const nid of nids) {
const uuid = nidToUuid.get(nid);
const node = uuid ? findNodeByUuid(scene, uuid) : null;
if (!node) {
return {
ok: false,
error: "ui_settle_failed",
message: `UI node ${nid} is missing from the active runtime scene.`
};
}
nodes.push(node);
}
const depthOf = (node) => {
let depth = 0;
let current = node.parent;
while (current && depth < 64) {
depth += 1;
current = current.parent;
}
return depth;
};
const shallowToDeep = nodes.map((node, index) => ({ node, index, depth: depthOf(node) })).sort((left, right) => left.depth - right.depth || left.index - right.index).map((entry) => entry.node);
const deepToShallow = [...shallowToDeep].reverse();
const stableNumber = (value) => typeof value === "number" && Number.isFinite(value) ? Math.round(value * 1e4) / 1e4 : null;
const geometrySnapshot = () => JSON.stringify(
shallowToDeep.map((node) => {
const transform = uiComponent(node, cc, "UITransform");
const contentSize = transform?.contentSize;
return [
node.uuid,
stableNumber(node.position.x),
stableNumber(node.position.y),
stableNumber(node.position.z),
stableNumber(typeof transform?.width === "number" ? transform.width : contentSize?.width),
stableNumber(typeof transform?.height === "number" ? transform.height : contentSize?.height)
];
})
);
let labelUpdates = 0;
let layoutUpdates = 0;
let widgetUpdates = 0;
let previousSnapshot = null;
let stablePasses = 0;
try {
const maxPasses = Math.min(16, Math.max(4, shallowToDeep.length + 2));
for (let pass = 1; pass <= maxPasses; pass += 1) {
for (const node of shallowToDeep) {
if (node.activeInHierarchy === false) continue;
const label = uiComponent(node, cc, "Label");
const updateRenderData = label?.updateRenderData;
if (typeof updateRenderData === "function") {
updateRenderData.call(label, true);
labelUpdates += 1;
}
}
for (const node of deepToShallow) {
if (node.activeInHierarchy === false) continue;
const layout = uiComponent(node, cc, "Layout");
const updateLayout = layout?.updateLayout;
if (typeof updateLayout === "function") {
updateLayout.call(layout, true);
layoutUpdates += 1;
}
}
for (const node of shallowToDeep) {
if (node.activeInHierarchy === false) continue;
const widget = uiComponent(node, cc, "Widget");
const updateAlignment = widget?.updateAlignment;
if (typeof updateAlignment === "function") {
updateAlignment.call(widget);
widgetUpdates += 1;
}
}
for (const node of deepToShallow) {
if (node.activeInHierarchy === false) continue;
const layout = uiComponent(node, cc, "Layout");
const updateLayout = layout?.updateLayout;
if (typeof updateLayout === "function") {
updateLayout.call(layout, true);
layoutUpdates += 1;
}
}
for (const node of shallowToDeep) {
if (node.activeInHierarchy === false) continue;
const label = uiComponent(node, cc, "Label");
const updateRenderData = label?.updateRenderData;
if (typeof updateRenderData === "function") {
updateRenderData.call(label, true);
labelUpdates += 1;
}
}
const snapshot = geometrySnapshot();
if (previousSnapshot === snapshot) {
stablePasses = pass;
break;
}
previousSnapshot = snapshot;
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return { ok: false, error: "ui_settle_failed", message: `UI settle failed: ${message}` };
}
if (stablePasses === 0) {
return {
ok: false,
error: "ui_settle_failed",
message: "UI geometry did not stabilize within the bounded settle passes."
};
}
return { ok: true, passes: stablePasses, labelUpdates, layoutUpdates, widgetUpdates };
},
verifyUiNode(...args) {
const raw = args[0];
if (!raw || typeof raw.nid !== "number" || !Number.isInteger(raw.nid)) {
return {
ok: false,
error: "ui_verification_failed",
message: "verifyUiNode requires an integer nid.",
mismatches: ["invalid nid"]
};
}
if (!raw.expected || typeof raw.expected !== "object" || Array.isArray(raw.expected)) {
return {
ok: false,
error: "ui_verification_failed",
message: "verifyUiNode requires an expected UI node specification.",
mismatches: ["invalid expected UI node specification"]
};
}
const uuid = nidToUuid.get(raw.nid);
const cc = getCC();
const scene = cc.director.getScene();
const node = uuid && scene ? findNodeByUuid(scene, uuid) : null;
if (!node) {
return {
ok: false,
error: "ui_verification_failed",
message: `UI node ${raw.nid} is missing from the active runtime scene.`,
mismatches: ["node missing"]
};
}
const mismatches = verifyUiNodeRuntime(node, raw.expected, cc);
if (mismatches.length > 0) {
return {
ok: false,
error: "ui_verification_failed",
message: `Runtime UI verification failed for ${node.name}: ${mismatches.join("; ")}`,
mismatches
};
}
return { ok: true };
},
/**
* Returns node uuid for Main to call `scene` `query-node` + `set-property` with `record: true` (undo).
* Does not mutate the scene.
*/
prepareSetNodeActive(...args) {
const raw = args[0];
const nid = raw?.nid;
const nextActive = raw?.active;
if (typeof nid !== "number" || !Number.isFinite(nid) || !Number.isInteger(nid)) {
return { ok: false, error: "invalid_args", message: "nid must be an integer (use getHierarchy NIDs)." };
}
if (typeof nextActive !== "boolean") {
return { ok: false, error: "invalid_args", message: "active must be a boolean." };
}
const uuid = nidToUuid.get(nid);
if (!uuid) {
return {
ok: false,
error: "nid_not_found",
message: "Unknown nid \u2014 scene may have changed; call getHierarchy again."
};
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, error: "no_scene", message: "No active scene." };
}
if (scene.uuid !== currentSceneUuid) {
return {
ok: false,
error: "stale_session",
message: "Scene changed since last getHierarchy; refresh NID map first."
};
}
const node = findNodeByUuid(scene, uuid);
if (!node) {
return {
ok: false,
error: "node_missing",
message: "Node uuid no longer in scene; call getHierarchy again."
};
}
return { ok: true, uuid: node.uuid };
},
/** Same nid/stale checks as `prepareSetNodeActive`; Main applies transform via query-node + set-property. */
prepareSetNodeTransform(...args) {
const raw = args[0];
const nid = raw?.nid;
if (typeof nid !== "number" || !Number.isFinite(nid) || !Number.isInteger(nid)) {
return { ok: false, error: "invalid_args", message: "nid must be an integer (use getHierarchy NIDs)." };
}
const uuid = nidToUuid.get(nid);
if (!uuid) {
return {
ok: false,
error: "nid_not_found",
message: "Unknown nid \u2014 scene may have changed; call getHierarchy again."
};
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, error: "no_scene", message: "No active scene." };
}
if (scene.uuid !== currentSceneUuid) {
return {
ok: false,
error: "stale_session",
message: "Scene changed since last getHierarchy; refresh NID map first."
};
}
const node = findNodeByUuid(scene, uuid);
if (!node) {
return {
ok: false,
error: "node_missing",
message: "Node uuid no longer in scene; call getHierarchy again."
};
}
return { ok: true, uuid: node.uuid };
},
/** Validates trimmed node `name` (1–256 chars, no ASCII control); Main sets path `name` via `set-property`. */
prepareSetNodeName(...args) {
const raw = args[0];
const nid = raw?.nid;
const rawName = raw?.name;
if (typeof nid !== "number" || !Number.isFinite(nid) || !Number.isInteger(nid)) {
return { ok: false, error: "invalid_args", message: "nid must be an integer (use getHierarchy NIDs)." };
}
if (typeof rawName !== "string") {
return { ok: false, error: "invalid_args", message: "name must be a string." };
}
const name = rawName.trim();
if (name.length === 0) {
return { ok: false, error: "invalid_args", message: "name must be non-empty after trim." };
}
if (name.length > 256) {
return { ok: false, error: "invalid_args", message: "name exceeds 256 characters." };
}
if (/[\u0000-\u001F\u007F]/.test(name)) {
return { ok: false, error: "invalid_args", message: "name must not contain control characters." };
}
const uuid = nidToUuid.get(nid);
if (!uuid) {
return {
ok: false,
error: "nid_not_found",
message: "Unknown nid \u2014 scene may have changed; call getHierarchy again."
};
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, error: "no_scene", message: "No active scene." };
}
if (scene.uuid !== currentSceneUuid) {
return {
ok: false,
error: "stale_session",
message: "Scene changed since last getHierarchy; refresh NID map first."
};
}
const node = findNodeByUuid(scene, uuid);
if (!node) {
return {
ok: false,
error: "node_missing",
message: "Node uuid no longer in scene; call getHierarchy again."
};
}
return { ok: true, uuid: node.uuid, name };
},
/** Validates parent `nid` + trimmed `name`; Main calls `scene` `create-node`. */
prepareCreateNode(...args) {
const raw = args[0];
const parentNid = raw?.parentNid;
const rawName = raw?.name;
if (typeof parentNid !== "number" || !Number.isFinite(parentNid) || !Number.isInteger(parentNid)) {
return { ok: false, error: "invalid_args", message: "parentNid must be an integer (use getHierarchy NIDs)." };
}
if (typeof rawName !== "string") {
return { ok: false, error: "invalid_args", message: "name must be a string." };
}
const name = rawName.trim();
if (name.length === 0) {
return { ok: false, error: "invalid_args", message: "name must be non-empty after trim." };
}
if (name.length > 256) {
return { ok: false, error: "invalid_args", message: "name exceeds 256 characters." };
}
if (/[\u0000-\u001F\u007F]/.test(name)) {
return { ok: false, error: "invalid_args", message: "name must not contain control characters." };
}
const parentUuid = nidToUuid.get(parentNid);
if (!parentUuid) {
return {
ok: false,
error: "nid_not_found",
message: "Unknown parentNid \u2014 call getHierarchy again."
};
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, error: "no_scene", message: "No active scene." };
}
if (scene.uuid !== currentSceneUuid) {
return {
ok: false,
error: "stale_session",
message: "Scene changed since last getHierarchy; refresh NID map first."
};
}
const parentNode = findNodeByUuid(scene, parentUuid);
if (!parentNode) {
return {
ok: false,
error: "node_missing",
message: "Parent node no longer in scene; call getHierarchy again."
};
}
return { ok: true, parentUuid: parentNode.uuid, name };
},
/** Parent `nid` for prefab instantiation (`create-node` + `assetUuid` in Main). */
prepareInstantiatePrefab(...args) {
const raw = args[0];
const parentNid = raw?.parentNid;
if (typeof parentNid !== "number" || !Number.isFinite(parentNid) || !Number.isInteger(parentNid)) {
return { ok: false, error: "invalid_args", message: "parentNid must be an integer (use getHierarchy NIDs)." };
}
const parentUuid = nidToUuid.get(parentNid);
if (!parentUuid) {
return {
ok: false,
error: "nid_not_found",
message: "Unknown parentNid \u2014 call getHierarchy again."
};
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, error: "no_scene", message: "No active scene." };
}
if (scene.uuid !== currentSceneUuid) {
return {
ok: false,
error: "stale_session",
message: "Scene changed since last getHierarchy; refresh NID map first."
};
}
const parentNode = findNodeByUuid(scene, parentUuid);
if (!parentNode) {
return {
ok: false,
error: "node_missing",
message: "Parent node no longer in scene; call getHierarchy again."
};
}
return { ok: true, parentUuid: parentNode.uuid };
},
/** Validates delete (scene root forbidden; leaf-only unless `cascade`). */
prepareDeleteNode(...args) {
const raw = args[0];
if (raw?.confirm !== true) {
return { ok: false, error: "invalid_args", message: "confirm must be true (explicit opt-in)." };
}
const nid = raw.nid;
const cascade = raw.cascade === true;
if (typeof nid !== "number" || !Number.isFinite(nid) || !Number.isInteger(nid)) {
return { ok: false, error: "invalid_args", message: "nid must be an integer (use getHierarchy NIDs)." };
}
const uuid = nidToUuid.get(nid);
if (!uuid) {
return {
ok: false,
error: "nid_not_found",
message: "Unknown nid \u2014 call getHierarchy again."
};
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, error: "no_scene", message: "No active scene." };
}
if (scene.uuid !== currentSceneUuid) {
return {
ok: false,
error: "stale_session",
message: "Scene changed since last getHierarchy; refresh NID map first."
};
}
if (uuid === scene.uuid) {
return {
ok: false,
error: "invalid_target",
message: "Cannot delete the scene root node."
};
}
const node = findNodeByUuid(scene, uuid);
if (!node) {
return {
ok: false,
error: "node_missing",
message: "Node no longer in scene; call getHierarchy again."
};
}
if (node.children.length > 0 && !cascade) {
return {
ok: false,
error: "non_leaf",
message: "Node has children: pass cascade: true to delete the whole subtree, or delete children first."
};
}
return { ok: true, uuid: node.uuid };
},
/** Validates reparent (`set-parent`): nids in map, no scene-root move, no cycles. */
prepareReparentNode(...args) {
const raw = args[0];
const childNid = raw?.childNid;
const parentNid = raw?.parentNid;
const keepWorldTransform = raw?.keepWorldTransform !== false;
if (typeof childNid !== "number" || !Number.isFinite(childNid) || !Number.isInteger(childNid)) {
return { ok: false, error: "invalid_args", message: "childNid must be an integer (use getHierarchy NIDs)." };
}
if (typeof parentNid !== "number" || !Number.isFinite(parentNid) || !Number.isInteger(parentNid)) {
return { ok: false, error: "invalid_args", message: "parentNid must be an integer (use getHierarchy NIDs)." };
}
if (childNid === parentNid) {
return { ok: false, error: "invalid_args", message: "childNid and parentNid must differ." };
}
const childUuid = nidToUuid.get(childNid);
const newParentUuid = nidToUuid.get(parentNid);
if (!childUuid) {
return {
ok: false,
error: "nid_not_found",
message: "Unknown childNid \u2014 call getHierarchy again."
};
}
if (!newParentUuid) {
return {
ok: false,
error: "nid_not_found",
message: "Unknown parentNid \u2014 call getHierarchy again."
};
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, error: "no_scene", message: "No active scene." };
}
if (scene.uuid !== currentSceneUuid) {
return {
ok: false,
error: "stale_session",
message: "Scene changed since last getHierarchy; refresh NID map first."
};
}
if (childUuid === scene.uuid) {
return {
ok: false,
error: "invalid_target",
message: "Cannot reparent the scene root node."
};
}
const childNode = findNodeByUuid(scene, childUuid);
const newParentNode = findNodeByUuid(scene, newParentUuid);
if (!childNode || !newParentNode) {
return {
ok: false,
error: "node_missing",
message: "Node no longer in scene; call getHierarchy again."
};
}
if (isNodeUnderAncestorUuid(newParentNode, childUuid)) {
return {
ok: false,
error: "invalid_target",
message: "Invalid reparent: new parent is the child itself or inside the child subtree (cycle)."
};
}
const par = childNode.parent ?? null;
const previousParentUuid = par == null || par.uuid === scene.uuid ? scene.uuid : par.uuid;
const noOp = previousParentUuid === newParentUuid;
return {
ok: true,
childUuid: childNode.uuid,
newParentUuid: newParentNode.uuid,
previousParentUuid,
keepWorldTransform,
noOp
};
},
/** After Main `create-node`, assign the next session `nid` for MCP tools. */
registerNidForNewNode(...args) {
const uuid = args[0];
if (typeof uuid !== "string" || uuid.trim().length === 0) {
return { ok: false, message: "registerNidForNewNode: invalid uuid" };
}
const existing = uuidToNid.get(uuid);
if (existing !== void 0) {
return { ok: true, nid: existing };
}
const nid = nextNid++;
uuidToNid.set(uuid, nid);
nidToUuid.set(nid, uuid);
return { ok: true, nid };
},
/** Drop session mapping (e.g. after MCP undo removed the node). */
forgetNodeNid(...args) {
const uuid = args[0];
if (typeof uuid !== "string") {
return { ok: true };
}
const nid = uuidToNid.get(uuid);
if (nid !== void 0) {
uuidToNid.delete(uuid);
nidToUuid.delete(nid);
}
return { ok: true };
},
/** Remove session nids for a node and all descendants (call before `remove-node` when cascade). */
forgetNidsInSubtree(...args) {
const rootUuid = args[0];
if (typeof rootUuid !== "string") {
return { ok: true };
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: true };
}
const root = findNodeByUuid(scene, rootUuid);
if (!root) {
return { ok: true };
}
const forget = (n) => {
const mapped = uuidToNid.get(n.uuid);
if (mapped !== void 0) {
uuidToNid.delete(n.uuid);
nidToUuid.delete(mapped);
}
for (const c of n.children) {
forget(c);
}
};
forget(root);
return { ok: true };
},
/** Snapshot currently mapped uuid↔nid pairs in a subtree (used for transactional delete). */
snapshotNidMappingsInSubtree(...args) {
const rootUuid = args[0];
if (typeof rootUuid !== "string") {
return { ok: true, entries: [] };
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: true, entries: [] };
}
const root = findNodeByUuid(scene, rootUuid);
if (!root) {
return { ok: true, entries: [] };
}
const entries = [];
const walk = (n) => {
const nid = uuidToNid.get(n.uuid);
if (nid !== void 0) {
entries.push({ uuid: n.uuid, nid });
}
for (const c of n.children) {
walk(c);
}
};
walk(root);
return { ok: true, entries };
},
/**
* Detach a subtree without destroying it so MCP local undo can restore the
* exact objects and UUIDs. This avoids Creator's scene-wide global undo,
* which can decode unrelated render assets while a frame is being drawn.
*/
detachNodeForMcpUndo(...args) {
const uuid = args[0];
if (typeof uuid !== "string" || uuid.length === 0) {
return { ok: false, message: "detachNodeForMcpUndo: uuid must be a non-empty string." };
}
if (detachedNodesForUndo.has(uuid)) {
return { ok: false, message: `detachNodeForMcpUndo: ${uuid} is already detached.` };
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene." };
}
const node = findNodeByUuid(scene, uuid);
if (!node || node === scene) {
return { ok: false, message: "Node is missing or is the scene root." };
}
const parent = node.parent;
if (!parent) {
return { ok: false, message: "Node has no parent." };
}
const siblingIndex = parent.children.findIndex((child) => child.uuid === uuid);
try {
if (typeof node.removeFromParent === "function") {
node.removeFromParent();
} else {
node.parent = null;
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return { ok: false, message: `detachNodeForMcpUndo failed: ${message}` };
}
if (node.parent || findNodeByUuid(scene, uuid)) {
return { ok: false, message: "detachNodeForMcpUndo did not remove the node from the scene tree." };
}
while (detachedNodesForUndo.size >= DETACHED_NODE_STASH_MAX) {
const oldest = detachedNodesForUndo.keys().next().value;
if (!oldest) {
break;
}
releaseDetachedNodeStash(oldest);
}
detachedNodesForUndo.set(uuid, {
node,
scene,
parentUuid: parent.uuid,
siblingIndex: Math.max(0, siblingIndex)
});
return { ok: true, uuid, parentUuid: parent.uuid, siblingIndex: Math.max(0, siblingIndex) };
},
/** Restore a subtree previously detached by `detachNodeForMcpUndo`. */
restoreDetachedNodeForMcpUndo(...args) {
const uuid = args[0];
if (typeof uuid !== "string" || uuid.length === 0) {
return { ok: false, message: "restoreDetachedNodeForMcpUndo: uuid must be a non-empty string." };
}
const stashed = detachedNodesForUndo.get(uuid);
if (!stashed) {
const current = getCC().director.getScene();
if (current && findNodeByUuid(current, uuid)) {
return { ok: true, uuid, alreadyRestored: true };
}
return {
ok: false,
terminal: true,
message: `No detached node stash exists for ${uuid}; the local undo entry is no longer recoverable.`
};
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene." };
}
if (scene !== stashed.scene) {
releaseDetachedNodeStash(uuid);
return {
ok: false,
terminal: true,
message: "The active scene changed after the node was detached; the stale undo entry was discarded."
};
}
const existing = findNodeByUuid(scene, uuid);
if (existing) {
if (existing !== stashed.node) {
releaseDetachedNodeStash(uuid);
} else {
detachedNodesForUndo.delete(uuid);
}
return { ok: true, uuid, alreadyRestored: true };
}
const parent = findNodeByUuid(scene, stashed.parentUuid);
if (!parent) {
return { ok: false, message: "Original parent is no longer in the active scene." };
}
try {
if (typeof parent.addChild === "function") {
parent.addChild(stashed.node);
} else {
stashed.node.parent = parent;
}
stashed.node.setSiblingIndex?.(stashed.siblingIndex);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return { ok: false, message: `restoreDetachedNodeForMcpUndo failed: ${message}` };
}
if (findNodeByUuid(scene, uuid) !== stashed.node) {
return { ok: false, message: "Restored node is not reachable from the active scene root." };
}
detachedNodesForUndo.delete(uuid);
return { ok: true, uuid };
},
/**
* Dispose detached subtrees when the MCP undo stack is cleared on unload.
* With no UUID list, release every stash (including entries evicted from the
* bounded Main undo stack). A node that another editor undo already restored
* into a live tree is never destroyed.
*/
discardDetachedNodesForMcpUndo(...args) {
const requested = Array.isArray(args[0]) ? args[0] : Array.from(detachedNodesForUndo.keys());
let discarded = 0;
let liveNodesPreserved = 0;
for (const value of requested) {
if (typeof value !== "string") {
continue;
}
const released = releaseDetachedNodeStash(value);
if (released.released) {
discarded += 1;
}
if (released.liveNodePreserved) {
liveNodesPreserved += 1;
}
}
return { ok: true, discarded, liveNodesPreserved };
},
/**
* Remove nodes created by one UI task in creation order. Removing an outer
* root first also removes its descendants; later UUIDs are therefore skipped
* without issuing noisy component-level undo operations.
*/
removeCreatedNodesForMcpUndo(...args) {
const requested = args[0];
const expectedSceneUuid = args[1];
if (!Array.isArray(requested) || requested.some((value) => typeof value !== "string")) {
return {
ok: false,
message: "removeCreatedNodesForMcpUndo requires an array of UUID strings.",
removed: [],
remaining: []
};
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene.", removed: [], remaining: [...requested] };
}
if (typeof expectedSceneUuid === "string" && expectedSceneUuid.length > 0 && scene.uuid !== expectedSceneUuid) {
return {
ok: false,
terminal: true,
message: "The active scene changed after the UI transaction was created.",
removed: [],
remaining: [...requested]
};
}
const removed = [];
const forget = (node) => {
const nid = uuidToNid.get(node.uuid);
if (nid !== void 0) {
uuidToNid.delete(node.uuid);
nidToUuid.delete(nid);
}
for (const child of node.children) {
forget(child);
}
};
let failure = null;
for (const uuid of requested) {
try {
const node = findNodeByUuid(scene, uuid);
if (!node || node === scene) {
continue;
}
forget(node);
if (typeof node.removeFromParent === "function") {
node.removeFromParent();
} else {
node.parent = null;
}
node.destroy?.();
removed.push(uuid);
} catch (error) {
failure = error instanceof Error ? error.message : String(error);
break;
}
}
const remaining = requested.filter((uuid) => findNodeByUuid(scene, uuid) !== null);
if (failure || remaining.length > 0) {
return {
ok: false,
message: `removeCreatedNodesForMcpUndo was incomplete${failure ? `: ${failure}` : ""} (remaining=${remaining.length}).`,
removed,
remaining
};
}
return { ok: true, removed, remaining, sceneUuid: scene.uuid };
},
/** Drop mappings by a pre-snapshotted list of uuids (safe after subtree was removed). */
forgetNidMappingsByUuids(...args) {
const list = args[0];
if (!Array.isArray(list)) {
return { ok: true };
}
for (const v of list) {
if (typeof v !== "string") {
continue;
}
const nid = uuidToNid.get(v);
if (nid !== void 0) {
uuidToNid.delete(v);
nidToUuid.delete(nid);
}
}
return { ok: true };
},
/** Restore `uuid` ↔ `nid` after editor undo brought a deleted node back (MCP `delete_node` undo). */
restoreNidMapping(...args) {
const uuid = args[0];
const nid = args[1];
if (typeof uuid !== "string" || typeof nid !== "number" || !Number.isInteger(nid)) {
return { ok: false, message: "restoreNidMapping: invalid uuid or nid" };
}
const prevUuid = nidToUuid.get(nid);
if (prevUuid !== void 0 && prevUuid !== uuid) {
uuidToNid.delete(prevUuid);
}
const prevNid = uuidToNid.get(uuid);
if (prevNid !== void 0 && prevNid !== nid) {
nidToUuid.delete(prevNid);
}
uuidToNid.set(uuid, nid);
nidToUuid.set(nid, uuid);
return { ok: true };
},
prepareAddComponent(...args) {
const raw = args[0];
const nid = raw?.nid;
const componentTypeIn = raw?.componentType;
if (typeof nid !== "number" || !Number.isInteger(nid)) {
return { ok: false, error: "invalid_args", message: "nid must be an integer." };
}
if (typeof componentTypeIn !== "string" || !componentTypeIn.trim()) {
return { ok: false, error: "invalid_args", message: "componentType must be a non-empty string." };
}
const uuid = nidToUuid.get(nid);
if (!uuid) {
return { ok: false, error: "nid_not_found", message: "Unknown nid \u2014 call getHierarchy again." };
}
const componentType = resolveComponentTypeInput(componentTypeIn);
return { ok: true, uuid, componentType };
},
prepareRemoveComponent(...args) {
const raw = args[0];
const nid = raw?.nid;
const componentIndex = raw?.componentIndex;
if (typeof nid !== "number" || !Number.isInteger(nid)) {
return { ok: false, error: "invalid_args", message: "nid must be an integer." };
}
if (typeof componentIndex !== "number" || !Number.isInteger(componentIndex) || componentIndex < 0) {
return { ok: false, error: "invalid_args", message: "componentIndex must be a non-negative integer." };
}
const uuid = nidToUuid.get(nid);
if (!uuid) {
return { ok: false, error: "nid_not_found", message: "Unknown nid \u2014 call getHierarchy again." };
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, error: "no_scene", message: "No active scene." };
}
const node = findNodeByUuid(scene, uuid);
if (!node) {
return { ok: false, error: "node_missing", message: "Node uuid no longer in scene; call getHierarchy again." };
}
let comps = [];
try {
comps = node.getComponents(cc.Component);
} catch {
comps = [];
}
if (componentIndex >= comps.length) {
return {
ok: false,
error: "invalid_args",
message: `componentIndex ${componentIndex} out of range (node has ${comps.length} component(s)).`
};
}
const component = comps[componentIndex];
const componentType = shortComponentName(component, cc);
const componentUuid = typeof component.uuid === "string" && component.uuid.trim().length > 0 ? component.uuid.trim() : void 0;
return { ok: true, uuid, componentUuid, componentIndex, componentType };
},
prepareDuplicateNode(...args) {
const raw = args[0];
const nid = raw?.nid;
if (typeof nid !== "number" || !Number.isInteger(nid)) {
return { ok: false, error: "invalid_args", message: "nid must be an integer." };
}
const uuid = nidToUuid.get(nid);
if (!uuid) {
return { ok: false, error: "nid_not_found", message: "Unknown nid \u2014 call getHierarchy again." };
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, error: "no_scene", message: "No active scene." };
}
const node = findNodeByUuid(scene, uuid);
if (!node) {
return { ok: false, error: "node_missing", message: "Node uuid no longer in scene; call getHierarchy again." };
}
const parent = node.parent;
if (!parent) {
return { ok: false, error: "invalid_args", message: "Cannot duplicate scene root." };
}
return { ok: true, uuid, parentUuid: parent.uuid };
},
/**
* Live scene-graph child UUIDs (order matches editor hierarchy). Used by Main after `duplicate-node`
* when the IPC return value is empty — `query-node` on the parent often omits children in the inode dump.
*/
listChildUuidsUnderParent(...args) {
const raw0 = args[0];
if (typeof raw0 !== "string" || raw0.length === 0) {
return { ok: false, message: "parentUuid must be a non-empty string." };
}
const parentUuid = raw0;
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene." };
}
const parent = findNodeByUuid(scene, parentUuid);
if (!parent) {
return { ok: false, message: "Parent node not found in scene graph." };
}
const uuids = [];
for (const child of parent.children) {
try {
if (child && typeof child.uuid === "string" && child.uuid.length > 0) {
uuids.push(child.uuid);
}
} catch {
}
}
return { ok: true, uuids };
},
/**
* Fallback duplication when editor IPC `scene.duplicate-node` is a no-op in some Creator builds.
* Clones the node in the live scene graph via `cc.instantiate` and appends it under the same parent.
*/
duplicateNodeFallback(...args) {
const raw0 = args[0];
if (typeof raw0 !== "string" || raw0.length === 0) {
return { ok: false, message: "uuid must be a non-empty string." };
}
const uuid = raw0;
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene." };
}
const node = findNodeByUuid(scene, uuid);
if (!node) {
return { ok: false, message: "Node uuid no longer in scene; call getHierarchy again." };
}
const parent = node.parent;
if (!parent) {
return { ok: false, message: "Cannot duplicate scene root." };
}
const inst = cc.instantiate;
if (typeof inst !== "function") {
return { ok: false, message: "cc.instantiate is not available in this runtime." };
}
let clone = null;
try {
clone = inst(node);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
return { ok: false, message: `cc.instantiate failed: ${msg}` };
}
if (!clone || typeof clone.uuid !== "string" || clone.uuid.length === 0) {
return { ok: false, message: "instantiate returned unexpected value." };
}
try {
const oldIdx = parent.children.findIndex((n) => n.uuid === node.uuid);
const addChild = parent.addChild;
if (typeof addChild === "function") {
addChild.call(parent, clone);
} else {
clone.parent = parent;
}
const setSiblingIndex = clone.setSiblingIndex;
if (typeof setSiblingIndex === "function" && oldIdx >= 0) {
setSiblingIndex.call(clone, oldIdx + 1);
}
} catch {
}
return { ok: true, uuid: clone.uuid };
},
/**
* Fallback remove path for Main `undo add_component` when scene IPC `remove-component` by index
* does not mutate node components in some Creator builds.
*/
removeLastComponentByType(...args) {
const rawUuid = args[0];
const rawType = args[1];
if (typeof rawUuid !== "string" || rawUuid.length === 0) {
return { ok: false, message: "uuid must be a non-empty string." };
}
if (typeof rawType !== "string" || rawType.length === 0) {
return { ok: false, message: "componentType must be a non-empty string." };
}
const normalized = rawType.trim().replace(/^cc\./, "").toLowerCase();
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene." };
}
const node = findNodeByUuid(scene, rawUuid);
if (!node) {
return { ok: false, message: "Node uuid no longer in scene; call getHierarchy again." };
}
let comps = [];
try {
comps = node.getComponents(cc.Component);
} catch {
comps = [];
}
const beforeCount = comps.length;
const candidates = comps.filter((comp) => shortComponentName(comp, cc).toLowerCase() === normalized);
if (candidates.length === 0) {
return { ok: true, removed: false, beforeCount, afterCount: beforeCount };
}
const target = candidates[candidates.length - 1];
try {
if (typeof node.removeComponent === "function") {
node.removeComponent(target);
} else if (typeof target.destroy === "function") {
target.destroy();
} else {
return { ok: false, message: "Neither node.removeComponent nor component.destroy is available." };
}
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
return { ok: false, message: `removeLastComponentByType failed: ${message}` };
}
let after = beforeCount;
try {
after = node.getComponents(cc.Component).length;
} catch {
}
return { ok: true, removed: after < beforeCount, beforeCount, afterCount: after };
},
/**
* Fallback add path for Main `undo remove_component` when scene IPC `create-component`
* does not mutate node components in some Creator builds.
*/
addLastComponentByType(...args) {
const rawUuid = args[0];
const rawType = args[1];
if (typeof rawUuid !== "string" || rawUuid.length === 0) {
return { ok: false, message: "uuid must be a non-empty string." };
}
if (typeof rawType !== "string" || rawType.length === 0) {
return { ok: false, message: "componentType must be a non-empty string." };
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene." };
}
const node = findNodeByUuid(scene, rawUuid);
if (!node) {
return { ok: false, message: "Node uuid no longer in scene; call getHierarchy again." };
}
const ctor = resolveComponentCtor(rawType);
if (!ctor) {
return { ok: false, message: `Unable to resolve component constructor: ${rawType}` };
}
let before = [];
try {
before = node.getComponents(cc.Component);
} catch {
before = [];
}
const beforeCount = before.length;
try {
const addComponent = node.addComponent;
if (typeof addComponent !== "function") {
return { ok: false, message: "node.addComponent is not available in this runtime." };
}
addComponent.call(node, ctor);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
return { ok: false, message: `addLastComponentByType failed: ${msg}` };
}
let after = [];
try {
after = node.getComponents(cc.Component);
} catch {
after = before;
}
const afterCount = after.length;
return { ok: true, added: afterCount > beforeCount, beforeCount, afterCount };
},
/**
* Returns live component totals for a node and a specific component short-name type.
* Used by Main to verify undo effects when `query-node` dumps are stale/noisy.
*/
getComponentStatsByType(...args) {
const rawUuid = args[0];
const rawType = args[1];
if (typeof rawUuid !== "string" || rawUuid.length === 0) {
return { ok: false, message: "uuid must be a non-empty string." };
}
if (typeof rawType !== "string" || rawType.length === 0) {
return { ok: false, message: "componentType must be a non-empty string." };
}
const normalized = rawType.trim().replace(/^cc\./, "").toLowerCase();
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene." };
}
const node = findNodeByUuid(scene, rawUuid);
if (!node) {
return { ok: false, message: "Node uuid no longer in scene; call getHierarchy again." };
}
let comps = [];
try {
comps = node.getComponents(cc.Component);
} catch {
comps = [];
}
const totalCount = comps.length;
const typeCount = comps.filter((comp) => shortComponentName(comp, cc).toLowerCase() === normalized).length;
return { ok: true, totalCount, typeCount };
},
/** Exact live truth for component UUID removal/undo verification. */
getComponentUuidState(...args) {
const rawNodeUuid = args[0];
const rawComponentUuid = args[1];
if (typeof rawNodeUuid !== "string" || rawNodeUuid.length === 0) {
return { ok: false, message: "nodeUuid must be a non-empty string." };
}
if (typeof rawComponentUuid !== "string" || rawComponentUuid.length === 0) {
return { ok: false, message: "componentUuid must be a non-empty string." };
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene." };
}
const node = findNodeByUuid(scene, rawNodeUuid);
if (!node) {
return { ok: false, message: "Node uuid no longer in scene; call getHierarchy again." };
}
let components = [];
try {
components = node.getComponents(cc.Component);
} catch (error) {
return {
ok: false,
message: error instanceof Error ? error.message : String(error)
};
}
const componentIndex = components.findIndex((component) => component.uuid === rawComponentUuid);
if (componentIndex < 0) {
return { ok: true, present: false };
}
return {
ok: true,
present: true,
componentIndex,
componentType: shortComponentName(components[componentIndex], cc)
};
},
/**
* Fallback reorder path when editor IPC `scene.move-node` is unavailable.
*/
setSiblingIndexFallback(...args) {
const rawUuid = args[0];
const rawIndex = args[1];
if (typeof rawUuid !== "string" || rawUuid.length === 0) {
return { ok: false, message: "uuid must be a non-empty string." };
}
if (typeof rawIndex !== "number" || !Number.isInteger(rawIndex) || rawIndex < 0) {
return { ok: false, message: "index must be a non-negative integer." };
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene." };
}
const node = findNodeByUuid(scene, rawUuid);
if (!node || !node.parent) {
return { ok: false, message: "Node or parent not found in scene graph." };
}
const siblings = node.parent.children;
const previousIndex = siblings.findIndex((n) => n.uuid === node.uuid);
if (previousIndex < 0) {
return { ok: false, message: "Node is not attached to parent children list." };
}
const maxIndex = Math.max(0, siblings.length - 1);
const index = Math.max(0, Math.min(maxIndex, rawIndex));
if (typeof node.setSiblingIndex !== "function") {
return { ok: false, message: "node.setSiblingIndex is not available in this runtime." };
}
try {
node.setSiblingIndex(index);
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
return { ok: false, message: `setSiblingIndexFallback failed: ${message}` };
}
return { ok: true, appliedIndex: index, previousIndex };
},
setComponentScalarPropertyDirect(...args) {
const rawUuid = args[0];
const rawIndex = args[1];
const rawProperty = args[2];
const rawValue = args[3];
if (typeof rawUuid !== "string" || rawUuid.length === 0) {
return { ok: false, message: "uuid must be a non-empty string." };
}
if (typeof rawIndex !== "number" || !Number.isInteger(rawIndex) || rawIndex < 0) {
return { ok: false, message: "componentIndex must be a non-negative integer." };
}
if (typeof rawProperty !== "string" || rawProperty.trim().length === 0) {
return { ok: false, message: "property must be a non-empty string." };
}
const requestedValue = runtimeDirectSnapshot(rawValue);
if (requestedValue === void 0) {
return {
ok: false,
message: "runtime-direct fallback supports scalars, Color, Vec2, and Size values only."
};
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene." };
}
const node = findNodeByUuid(scene, rawUuid);
if (!node) {
return { ok: false, message: "Node uuid no longer in scene; call getHierarchy again." };
}
let comps = [];
try {
comps = node.getComponents(cc.Component);
} catch {
comps = [];
}
if (rawIndex >= comps.length) {
return { ok: false, message: `componentIndex ${rawIndex} is out of range.` };
}
const component = comps[rawIndex];
const prop = rawProperty.trim();
const field = [prop, `_${prop}`].find((candidate) => candidate in component);
if (!field) {
return { ok: false, message: `component property is not available at runtime: ${prop}` };
}
const beforeValue = runtimeDirectSnapshot(component[field]);
if (beforeValue === void 0) {
return { ok: false, message: `component property is not supported by runtime-direct fallback: ${prop}` };
}
try {
component[field] = materializeRuntimeDirectValue(requestedValue);
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
return { ok: false, message: `runtime-direct property assignment failed: ${message}` };
}
const afterValue = runtimeDirectSnapshot(component[field]);
if (afterValue === void 0) {
return { ok: false, message: `component property became unsupported after assignment: ${prop}` };
}
return {
ok: true,
beforeValue,
afterValue,
changed: JSON.stringify(beforeValue) !== JSON.stringify(afterValue)
};
},
/**
* Read asset-ref UUID from live component instance for known asset properties.
* Used by Main to verify typed direct asset writes and their local undo.
*/
getComponentAssetRefUuid(...args) {
const rawUuid = args[0];
const rawComponentType = args[1];
const rawProperty = args[2];
if (typeof rawUuid !== "string" || rawUuid.length === 0) {
return { ok: false, message: "uuid must be a non-empty string." };
}
if (typeof rawComponentType !== "string" || rawComponentType.length === 0) {
return { ok: false, message: "componentType must be a non-empty string." };
}
if (typeof rawProperty !== "string" || rawProperty.length === 0) {
return { ok: false, message: "property must be a non-empty string." };
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene." };
}
const node = findNodeByUuid(scene, rawUuid);
if (!node) {
return { ok: false, message: "Node uuid no longer in scene; call getHierarchy again." };
}
let comps = [];
try {
comps = node.getComponents(cc.Component);
} catch {
comps = [];
}
const normType = rawComponentType.trim().replace(/^cc\./, "").toLowerCase();
const prop = rawProperty.trim();
const typed = comps.filter((c) => shortComponentName(c, cc).toLowerCase() === normType);
if (typed.length === 0) {
return { ok: false, message: `No component of type ${rawComponentType} on target node.` };
}
const target = typed[typed.length - 1];
const candidates = [prop, `_${prop}`];
for (const k of candidates) {
if (k in target) {
return readAssetReferenceUuid(target[k], `${rawComponentType}.${k}`);
}
}
return { ok: false, message: `Asset property ${rawComponentType}.${prop} is not available at runtime.` };
},
async setComponentAssetRefDirect(...args) {
const rawUuid = args[0];
const rawComponentType = args[1];
const rawProperty = args[2];
const rawAssetUuid = args[3];
if (typeof rawUuid !== "string" || rawUuid.length === 0) {
return { ok: false, message: "uuid must be a non-empty string." };
}
if (typeof rawComponentType !== "string" || rawComponentType.length === 0) {
return { ok: false, message: "componentType must be a non-empty string." };
}
if (typeof rawProperty !== "string" || rawProperty.length === 0) {
return { ok: false, message: "property must be a non-empty string." };
}
if (typeof rawAssetUuid !== "string" || rawAssetUuid.length === 0) {
return { ok: false, message: "assetUuid must be a non-empty string." };
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene." };
}
const node = findNodeByUuid(scene, rawUuid);
if (!node) {
return { ok: false, message: "Node uuid no longer in scene; call getHierarchy again." };
}
let comps = [];
try {
comps = node.getComponents(cc.Component);
} catch {
comps = [];
}
const normType = rawComponentType.trim().replace(/^cc\./, "").toLowerCase();
const typed = comps.filter((c) => shortComponentName(c, cc).toLowerCase() === normType);
if (typed.length === 0) {
return { ok: false, message: `No component of type ${rawComponentType} on target node.` };
}
const comp = typed[typed.length - 1];
const prop = rawProperty.trim();
const propCandidates = [prop, `_${prop}`];
const field = propCandidates.find((candidate) => candidate in comp);
if (!field) {
return { ok: false, message: `Asset property ${rawComponentType}.${prop} is not available at runtime.` };
}
const beforeValue = comp[field];
const before = readAssetReferenceUuid(beforeValue, `${rawComponentType}.${field}`);
if (!before.ok) {
return before;
}
const beforeUuid = before.uuid;
const am = getCcAssetManager();
let asset;
try {
asset = await loadCcAssetByUuid(am, rawAssetUuid);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return { ok: false, message: `Unable to load asset UUID ${rawAssetUuid}: ${message}` };
}
const kindError = validateAssetKind(asset, rawComponentType, prop, cc);
if (kindError) {
return { ok: false, message: kindError };
}
try {
comp[field] = asset;
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
return { ok: false, message: `direct asset assignment failed: ${message}` };
}
const after = readAssetReferenceUuid(comp[field], `${rawComponentType}.${field}`);
if (!after.ok) {
try {
comp[field] = beforeValue;
} catch {
return { ok: false, message: `${after.message} Rollback to the prior asset reference also failed.` };
}
return { ok: false, message: `${after.message} The prior asset reference was restored.` };
}
const afterUuid = after.uuid;
if (afterUuid !== rawAssetUuid) {
try {
comp[field] = beforeValue;
} catch {
return {
ok: false,
message: `Runtime asset assignment did not stick (expected ${rawAssetUuid}, observed ${afterUuid ?? "null"}) and rollback to the prior asset reference failed.`
};
}
return {
ok: false,
message: `Runtime asset assignment did not stick: expected ${rawAssetUuid}, observed ${afterUuid ?? "null"}. The prior asset reference was restored.`
};
}
return { ok: true, beforeUuid, afterUuid, changed: beforeUuid !== afterUuid };
},
setComponentAssetRefFromScene(...args) {
const rawUuid = args[0];
const rawComponentType = args[1];
const rawProperty = args[2];
const rawAssetUuid = args[3];
if (typeof rawUuid !== "string" || rawUuid.length === 0) {
return { ok: false, message: "uuid must be a non-empty string." };
}
if (typeof rawComponentType !== "string" || rawComponentType.length === 0) {
return { ok: false, message: "componentType must be a non-empty string." };
}
if (typeof rawProperty !== "string" || rawProperty.length === 0) {
return { ok: false, message: "property must be a non-empty string." };
}
if (typeof rawAssetUuid !== "string" || rawAssetUuid.length === 0) {
return { ok: false, message: "assetUuid must be a non-empty string." };
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene." };
}
const normType = rawComponentType.trim().replace(/^cc\./, "").toLowerCase();
const prop = rawProperty.trim();
const fieldCandidates = [prop, `_${prop}`];
const targetNode = findNodeByUuid(scene, rawUuid);
if (!targetNode) {
return { ok: false, message: "Node uuid no longer in scene; call getHierarchy again." };
}
let targetComps = [];
try {
targetComps = targetNode.getComponents(cc.Component);
} catch {
targetComps = [];
}
const targetTyped = targetComps.filter((c) => shortComponentName(c, cc).toLowerCase() === normType);
if (targetTyped.length === 0) {
return { ok: false, message: `No component of type ${rawComponentType} on target node.` };
}
const targetComp = targetTyped[targetTyped.length - 1];
let sourceAssetRef = null;
const walk = (node) => {
let comps = [];
try {
comps = node.getComponents(cc.Component);
} catch {
comps = [];
}
for (const comp of comps) {
if (shortComponentName(comp, cc).toLowerCase() !== normType) {
continue;
}
const c = comp;
for (const k of fieldCandidates) {
const v = c[k];
if (v && typeof v === "object" && !Array.isArray(v)) {
const u = v.uuid;
if (typeof u === "string" && u === rawAssetUuid) {
sourceAssetRef = v;
return true;
}
}
}
}
for (const child of node.children) {
if (walk(child)) {
return true;
}
}
return false;
};
walk(scene);
if (!sourceAssetRef) {
return {
ok: false,
message: `Asset UUID ${rawAssetUuid} is not currently loaded on any ${rawComponentType}.${prop} in scene.`
};
}
const targetField = fieldCandidates.find((candidate) => candidate in targetComp);
if (!targetField) {
return { ok: false, message: `Asset property ${rawComponentType}.${prop} is not available at runtime.` };
}
const beforeValue = targetComp[targetField];
const before = readAssetReferenceUuid(
beforeValue,
`${rawComponentType}.${targetField}`
);
if (!before.ok) {
return before;
}
const beforeUuid = before.uuid;
try {
targetComp[targetField] = sourceAssetRef;
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
return { ok: false, message: `setComponentAssetRefFromScene failed: ${message}` };
}
const after = readAssetReferenceUuid(
targetComp[targetField],
`${rawComponentType}.${targetField}`
);
if (!after.ok) {
try {
targetComp[targetField] = beforeValue;
} catch {
return { ok: false, message: `${after.message} Rollback to the prior asset reference also failed.` };
}
return { ok: false, message: `${after.message} The prior asset reference was restored.` };
}
const afterUuid = after.uuid;
if (afterUuid !== rawAssetUuid) {
try {
targetComp[targetField] = beforeValue;
} catch {
return {
ok: false,
message: `Scene asset assignment did not stick (expected ${rawAssetUuid}, observed ${afterUuid ?? "null"}) and rollback to the prior asset reference failed.`
};
}
return {
ok: false,
message: `Scene asset assignment did not stick: expected ${rawAssetUuid}, observed ${afterUuid ?? "null"}. The prior asset reference was restored.`
};
}
return { ok: true, beforeUuid, afterUuid, changed: beforeUuid !== afterUuid };
},
/**
* Clear (set to null) an asset-ref field on the last matching component instance.
* Used by Main to implement undo when the previous asset UUID is unknown/unobservable.
*/
clearComponentAssetRef(...args) {
const rawUuid = args[0];
const rawComponentType = args[1];
const rawProperty = args[2];
if (typeof rawUuid !== "string" || rawUuid.length === 0) {
return { ok: false, message: "uuid must be a non-empty string." };
}
if (typeof rawComponentType !== "string" || rawComponentType.length === 0) {
return { ok: false, message: "componentType must be a non-empty string." };
}
if (typeof rawProperty !== "string" || rawProperty.length === 0) {
return { ok: false, message: "property must be a non-empty string." };
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene." };
}
const node = findNodeByUuid(scene, rawUuid);
if (!node) {
return { ok: false, message: "Node uuid no longer in scene; call getHierarchy again." };
}
let comps = [];
try {
comps = node.getComponents(cc.Component);
} catch {
comps = [];
}
const normType = rawComponentType.trim().replace(/^cc\./, "").toLowerCase();
const typed = comps.filter((c) => shortComponentName(c, cc).toLowerCase() === normType);
if (typed.length === 0) {
return { ok: false, message: `No component of type ${rawComponentType} on target node.` };
}
const comp = typed[typed.length - 1];
const prop = rawProperty.trim();
const candidates = [prop, `_${prop}`];
const field = candidates.find((candidate) => candidate in comp);
if (!field) {
return { ok: false, message: `Asset property ${rawComponentType}.${prop} is not available at runtime.` };
}
const beforeValue = comp[field];
const before = readAssetReferenceUuid(beforeValue, `${rawComponentType}.${field}`);
if (!before.ok) {
return before;
}
const beforeUuid = before.uuid;
try {
comp[field] = null;
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
return { ok: false, message: `clearComponentAssetRef failed: ${message}` };
}
const after = readAssetReferenceUuid(comp[field], `${rawComponentType}.${field}`);
if (!after.ok) {
try {
comp[field] = beforeValue;
} catch {
return { ok: false, message: `${after.message} Rollback to the prior asset reference also failed.` };
}
return { ok: false, message: `${after.message} The prior asset reference was restored.` };
}
const afterUuid = after.uuid;
if (afterUuid !== null) {
try {
comp[field] = beforeValue;
} catch {
return {
ok: false,
message: `Asset clear did not stick (observed ${afterUuid}) and rollback to the prior reference failed.`
};
}
return {
ok: false,
message: `Asset clear did not stick (observed ${afterUuid}); the prior reference was restored.`
};
}
return { ok: true, beforeUuid, afterUuid, changed: beforeUuid !== afterUuid };
},
getNodeActiveState(...args) {
const rawUuid = args[0];
if (typeof rawUuid !== "string" || rawUuid.length === 0) {
return { ok: false, message: "uuid must be a non-empty string." };
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, message: "No active scene." };
}
const node = findNodeByUuid(scene, rawUuid);
if (!node) {
return { ok: false, message: "Node uuid no longer in scene; call getHierarchy again." };
}
return { ok: true, active: !!node.active };
},
prepareSetNodeSiblingIndex(...args) {
const raw = args[0];
const nid = raw?.nid;
const indexIn = raw?.index;
if (typeof nid !== "number" || !Number.isInteger(nid)) {
return { ok: false, error: "invalid_args", message: "nid must be an integer." };
}
if (typeof indexIn !== "number" || !Number.isInteger(indexIn) || indexIn < 0) {
return { ok: false, error: "invalid_args", message: "index must be a non-negative integer." };
}
const uuid = nidToUuid.get(nid);
if (!uuid) {
return { ok: false, error: "nid_not_found", message: "Unknown nid \u2014 call getHierarchy again." };
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, error: "no_scene", message: "No active scene." };
}
const node = findNodeByUuid(scene, uuid);
if (!node) {
return { ok: false, error: "node_missing", message: "Node uuid no longer in scene; call getHierarchy again." };
}
const parent = node.parent;
if (!parent) {
return { ok: false, error: "invalid_args", message: "Cannot reorder scene root." };
}
const siblings = parent.children;
const previousIndex = siblings.findIndex((n) => n.uuid === node.uuid);
if (previousIndex < 0) {
return { ok: false, error: "node_missing", message: "Node is not attached to parent children list." };
}
const maxIndex = Math.max(0, siblings.length - 1);
const index = Math.max(0, Math.min(maxIndex, indexIn));
return { ok: true, uuid, parentUuid: parent.uuid, index, previousIndex, noOp: previousIndex === index };
},
/**
* Validates whitelist + coerces `value`. Main reads property dump via `query-node` and calls `set-property`.
* `componentIndex` matches `inspectNode` / `getComponents` order.
*/
prepareSetComponentProperty(...args) {
const raw = args[0];
const nid = raw?.nid;
const componentIndex = raw?.componentIndex;
const property = raw?.property;
const valueIn = raw?.value;
if (typeof nid !== "number" || !Number.isFinite(nid) || !Number.isInteger(nid)) {
return { ok: false, error: "invalid_args", message: "nid must be an integer (use getHierarchy NIDs)." };
}
if (typeof componentIndex !== "number" || !Number.isInteger(componentIndex) || componentIndex < 0) {
return { ok: false, error: "invalid_args", message: "componentIndex must be a non-negative integer." };
}
if (typeof property !== "string" || !property.trim()) {
return { ok: false, error: "invalid_args", message: "property must be a non-empty string." };
}
const prop = property.trim();
const uuid = nidToUuid.get(nid);
if (!uuid) {
return {
ok: false,
error: "nid_not_found",
message: "Unknown nid \u2014 scene may have changed; call getHierarchy again."
};
}
const cc = getCC();
const scene = cc.director.getScene();
if (!scene) {
return { ok: false, error: "no_scene", message: "No active scene." };
}
if (scene.uuid !== currentSceneUuid) {
return {
ok: false,
error: "stale_session",
message: "Scene changed since last getHierarchy; refresh NID map first."
};
}
const node = findNodeByUuid(scene, uuid);
if (!node) {
return {
ok: false,
error: "node_missing",
message: "Node uuid no longer in scene; call getHierarchy again."
};
}
let comps = [];
try {
comps = node.getComponents(cc.Component);
} catch {
comps = [];
}
if (componentIndex >= comps.length) {
return {
ok: false,
error: "invalid_args",
message: `componentIndex ${componentIndex} out of range (node has ${comps.length} component(s)).`
};
}
const shortName = shortComponentName(comps[componentIndex], cc);
const schemasForType = COMPONENT_SCHEMAS[shortName] ?? {};
const schema = schemasForType[prop];
if (!schema) {
const allowedType = Object.keys(schemasForType);
return {
ok: false,
error: "not_whitelisted",
message: `\u5C5E\u6027\u672A\u5728\u5B89\u5168\u767D\u540D\u5355\u4E2D\u914D\u7F6E\u3002\u7EC4\u4EF6 ${shortName} \u5F53\u524D\u652F\u6301\u7684\u5C5E\u6027\u6709\uFF1A${allowedType.length > 0 ? allowedType.join(", ") : "(none)"}`
};
}
let coerced;
try {
coerced = schema.coercer(valueIn);
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
return { ok: false, error: "invalid_args", message };
}
const dumpProp = schema.dumpProp ?? prop;
const path2 = `__comps__.${componentIndex}.${dumpProp}`;
return { ok: true, uuid: node.uuid, path: path2, componentType: shortName, value: coerced };
}
};
var cocosCjsExports = typeof exports === "object" && exports ? exports : null;
if (cocosCjsExports) {
cocosCjsExports.methods = methods;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
methods
});