@combos-fun/plugin-development-tool 0.0.33 → 0.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/agent-skill.md CHANGED
@@ -138,8 +138,8 @@ triggers / spawn points later) but nothing else is registered yet.
138
138
  follows the object). The layer is **purely visual** — it does not wire up its own
139
139
  selection. Because the icon adds to the owner's rendered bounds, the owner's own
140
140
  `CombosDevelopmentToolTarget` pick gets a clickable hit area, so selecting and
141
- editing (e.g. `Sound.config.volume`) go through the normal
142
- selection → snapshot → `apply-property` flow and **persist to source** like any
141
+ editing (e.g. `Sound.volume`) go through the normal
142
+ selection → snapshot (`fieldDescriptors`) → `apply-property` flow and **persist to source** like any
143
143
  other scene edit. **The marked object must therefore carry a
144
144
  `CombosDevelopmentToolTarget`** added by game code. Objects with a `Sound`
145
145
  component must be given a Target when they should be editable even when they
@@ -147,6 +147,14 @@ triggers / spawn points later) but nothing else is registered yet.
147
147
  marker badges do not overlap.
148
148
  - On (re)build it posts `combos-development-tool:marker-overlay-success` with per-component counts.
149
149
 
150
+ ## Scene Edit fieldDescriptors
151
+
152
+ Selection snapshots no longer dump raw component `fields`. The host receives only
153
+ schema-backed `fieldDescriptors` derived from `@Field` metadata (`label`,
154
+ `description`, editor hints, min/max/step, etc.). Fields without a non-empty
155
+ `description` are omitted. Custom game Components that should appear in Creator
156
+ must declare `@Field({ label, description, ... })` in the user's language.
157
+
150
158
  ## Verification
151
159
 
152
160
  `pnpm --filter @combos-fun/plugin-development-tool run build`.
@@ -5,6 +5,7 @@ var engine = require('@combos-fun/engine');
5
5
  var pluginRenderer = require('@combos-fun/plugin-renderer');
6
6
  var pluginRendererGraphics = require('@combos-fun/plugin-renderer-graphics');
7
7
  var pluginRendererEvent = require('@combos-fun/plugin-renderer-event');
8
+ var inspectorDecorator = require('@combos-fun/inspector-decorator');
8
9
 
9
10
  /** Toggle development-tool selection / parent postMessage. Payload: `{ enabled: boolean }`. */
10
11
  const COMBOS_DEVELOPMENT_TOOL_SET = 'combos-development-tool:set';
@@ -201,30 +202,17 @@ function isSceneSourceAnchor(v) {
201
202
  typeof v.file === 'string');
202
203
  }
203
204
 
204
- const SKIP_KEYS = new Set([
205
- 'gameObject',
206
- 'name',
207
- 'started',
208
- '__componentDefaultParams',
209
- 'destroyed',
210
- 'inScene',
211
- 'worldTransform',
212
- 'children',
213
- '_parent',
214
- ]);
215
- const COMPONENT_SKIP_KEYS = {
216
- Graphics: new Set(['graphics']),
217
- Physics: new Set([
218
- 'body',
219
- 'Body',
220
- 'PhysicsEngine',
221
- 'World',
222
- 'Constraint',
223
- 'mouseConstraint',
224
- 'bodyParams',
225
- ]),
226
- Event: new Set(['hitArea']),
227
- };
205
+ function resolveValueType(value) {
206
+ if (typeof value === 'string')
207
+ return 'string';
208
+ if (typeof value === 'number')
209
+ return 'number';
210
+ if (typeof value === 'boolean')
211
+ return 'boolean';
212
+ if (value !== null && typeof value === 'object')
213
+ return 'object';
214
+ return 'unsupported';
215
+ }
228
216
  function cloneJsonSafe(value, depth = 0) {
229
217
  if (value === null || value === undefined)
230
218
  return value;
@@ -249,20 +237,171 @@ function cloneJsonSafe(value, depth = 0) {
249
237
  }
250
238
  return undefined;
251
239
  }
240
+ function readFieldReflectSchema(ctor) {
241
+ const properties = Reflect.getMetadata(inspectorDecorator.IDE_PROPERTY_METADATA, ctor) ||
242
+ {};
243
+ return Object.keys(properties).map(key => ({
244
+ key,
245
+ ...properties[key],
246
+ }));
247
+ }
248
+ /** Legacy `@type` / `@step` write `constructor.IDEProps` as an object keyed by property name. */
249
+ function readLegacyIdePropsSchema(ctor) {
250
+ const raw = ctor.IDEProps;
251
+ if (!raw || Array.isArray(raw) || typeof raw !== 'object') {
252
+ return [];
253
+ }
254
+ return Object.keys(raw).map(key => {
255
+ const entry = raw[key] || {};
256
+ return {
257
+ key,
258
+ type: typeof entry.type === 'string' ? entry.type : undefined,
259
+ step: typeof entry.step === 'number' ? entry.step : undefined,
260
+ min: typeof entry.min === 'number' ? entry.min : undefined,
261
+ max: typeof entry.max === 'number' ? entry.max : undefined,
262
+ label: typeof entry.label === 'string' ? entry.label : undefined,
263
+ description: typeof entry.description === 'string' ? entry.description : undefined,
264
+ group: typeof entry.group === 'string' ? entry.group : undefined,
265
+ editor: entry.editor,
266
+ enumOptions: Array.isArray(entry.enumOptions) ? entry.enumOptions : undefined,
267
+ unit: typeof entry.unit === 'string' ? entry.unit : undefined,
268
+ };
269
+ });
270
+ }
271
+ function collectSchemaProps(comp) {
272
+ const ctor = comp.constructor;
273
+ const fromField = readFieldReflectSchema(ctor);
274
+ if (fromField.length > 0) {
275
+ return fromField;
276
+ }
277
+ return readLegacyIdePropsSchema(ctor);
278
+ }
279
+ function mapEditorKind(options, valueType) {
280
+ if (options.editor) {
281
+ return options.editor;
282
+ }
283
+ if (valueType === 'boolean')
284
+ return 'toggle';
285
+ if (options.enumOptions?.length && valueType === 'string')
286
+ return 'enum';
287
+ if (valueType === 'number') {
288
+ if (options.min !== undefined && options.max !== undefined) {
289
+ return 'number-slider';
290
+ }
291
+ return 'number-stepper';
292
+ }
293
+ if (valueType === 'string')
294
+ return 'text';
295
+ return undefined;
296
+ }
297
+ function buildNumberMeta(options) {
298
+ if (options.min === undefined &&
299
+ options.max === undefined &&
300
+ options.step === undefined &&
301
+ options.unit === undefined) {
302
+ return undefined;
303
+ }
304
+ return {
305
+ min: options.min,
306
+ max: options.max,
307
+ step: options.step,
308
+ unit: options.unit,
309
+ };
310
+ }
311
+ function titleCaseLeaf(leaf) {
312
+ return leaf
313
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
314
+ .replace(/_/g, ' ')
315
+ .replace(/^./, char => char.toUpperCase());
316
+ }
317
+ function resolveLabel(options, path) {
318
+ if (options.label?.trim()) {
319
+ return options.label.trim();
320
+ }
321
+ const leaf = path[path.length - 1];
322
+ return leaf ? titleCaseLeaf(leaf) : undefined;
323
+ }
324
+ function leafDescriptorsFromValue(componentName, options, path, value) {
325
+ const description = options.description?.trim();
326
+ if (!description) {
327
+ // Scene Edit only exposes fields that include a human description.
328
+ return [];
329
+ }
330
+ const valueType = resolveValueType(value);
331
+ if (valueType === 'unsupported') {
332
+ return [];
333
+ }
334
+ const typeHint = options.type;
335
+ const isVector = typeHint === 'vector2' ||
336
+ (valueType === 'object' &&
337
+ value !== null &&
338
+ 'x' in value &&
339
+ 'y' in value);
340
+ const isSize = typeHint === 'size' ||
341
+ (valueType === 'object' &&
342
+ value !== null &&
343
+ 'width' in value &&
344
+ 'height' in value);
345
+ if (isVector || isSize) {
346
+ const record = value;
347
+ const axes = isSize ? ['width', 'height'] : ['x', 'y'];
348
+ const baseLabel = resolveLabel(options, path) ?? componentName;
349
+ const out = [];
350
+ for (const axis of axes) {
351
+ const axisValue = record[axis];
352
+ const axisType = resolveValueType(axisValue);
353
+ if (axisType === 'unsupported' || axisType === 'object')
354
+ continue;
355
+ const axisPath = [...path, axis];
356
+ out.push({
357
+ path: axisPath,
358
+ value: axisValue,
359
+ valueType: axisType,
360
+ label: `${baseLabel} ${axis.toUpperCase()}`,
361
+ description,
362
+ group: options.group ?? componentName,
363
+ editorKind: mapEditorKind(options, axisType),
364
+ numberMeta: buildNumberMeta(options),
365
+ persistTarget: componentName === 'Transform' ? 'gameObjectConstructor' : 'component',
366
+ });
367
+ }
368
+ return out;
369
+ }
370
+ if (valueType === 'object') {
371
+ return [];
372
+ }
373
+ const label = resolveLabel(options, path);
374
+ if (!label) {
375
+ return [];
376
+ }
377
+ return [
378
+ {
379
+ path,
380
+ value: cloneJsonSafe(value),
381
+ valueType,
382
+ label,
383
+ description,
384
+ group: options.group ?? componentName,
385
+ editorKind: mapEditorKind(options, valueType),
386
+ enumOptions: options.enumOptions,
387
+ numberMeta: buildNumberMeta(options),
388
+ persistTarget: componentName === 'Transform' ? 'gameObjectConstructor' : 'component',
389
+ },
390
+ ];
391
+ }
252
392
  function serializeComponent(comp) {
253
- const out = {};
254
- const componentSkips = COMPONENT_SKIP_KEYS[comp.name];
255
- for (const key of Object.keys(comp)) {
256
- if (SKIP_KEYS.has(key) || key.startsWith('_'))
257
- continue;
258
- if (componentSkips?.has(key))
259
- continue;
260
- const value = comp[key];
393
+ const componentName = comp.name;
394
+ const schema = collectSchemaProps(comp);
395
+ if (schema.length === 0) {
396
+ return [];
397
+ }
398
+ const out = [];
399
+ const record = comp;
400
+ for (const prop of schema) {
401
+ const value = record[prop.key];
261
402
  if (typeof value === 'function')
262
403
  continue;
263
- const cloned = cloneJsonSafe(value);
264
- if (cloned !== undefined)
265
- out[key] = cloned;
404
+ out.push(...leafDescriptorsFromValue(componentName, prop, [prop.key], value));
266
405
  }
267
406
  return out;
268
407
  }
@@ -281,8 +420,9 @@ function buildGameObjectSnapshot(go) {
281
420
  .filter(c => c.name !== 'CombosDevelopmentToolTarget')
282
421
  .map(c => ({
283
422
  componentName: c.name,
284
- fields: serializeComponent(c),
285
- }));
423
+ fieldDescriptors: serializeComponent(c),
424
+ }))
425
+ .filter(c => c.fieldDescriptors.length > 0);
286
426
  return {
287
427
  id: go.id,
288
428
  name: go.name,
@@ -1235,7 +1375,7 @@ var CombosDevelopmentToolSystem = CombosDevelopmentToolSystem$1;
1235
1375
  /** Auto-generated by scripts/build-package.mjs — do not edit. */
1236
1376
  Object.assign(CombosDevelopmentToolSystem, {
1237
1377
  packageName: "@combos-fun/plugin-development-tool",
1238
- packageVersion: "0.0.33",
1378
+ packageVersion: "0.0.35",
1239
1379
  });
1240
1380
 
1241
1381
  exports.COMBOS_DEVELOPMENT_TOOL_APPLY_PROPERTY = COMBOS_DEVELOPMENT_TOOL_APPLY_PROPERTY;