castle-web-cli 0.4.101 → 0.4.103

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.
@@ -29,6 +29,9 @@ const linkBtn = {
29
29
  const deleteBtn = { ...linkBtn, color: '#ff7a7a' };
30
30
  const actionRow = { display: 'flex', justifyContent: 'space-between', alignItems: 'center', margin: '4px 0 12px' };
31
31
  const noteStyle = { fontSize: 12, color: 'var(--castle-inspector-muted, #888)', margin: '2px 0 10px' };
32
+ const resetRow = { display: 'flex', justifyContent: 'space-between', alignItems: 'center', margin: '2px 0 10px' };
33
+ const overrideNote = { fontSize: 12, color: '#b58dff' };
34
+ const resetLink = { ...linkBtn, color: '#b58dff' };
32
35
 
33
36
  // --- shape helpers ---------------------------------------------------------
34
37
  const FULL_BOX = { type: 'box', x: 0.5, y: 0.5, w: 1, h: 1 };
@@ -177,21 +180,11 @@ export class Collider {
177
180
  const offXPx = Math.round(((shape.x ?? 0.5) - 0.5) * bw);
178
181
  const offYPx = Math.round(((shape.y ?? 0.5) - 0.5) * bh);
179
182
 
180
- // Per-subfield override state vs the blueprint's version of THIS shape. The
181
- // shape fields don't go through AutoFields, so without this an instance that
182
- // changed e.g. box 3's offset X wouldn't show the tint / Default / Reset.
183
- // Null override (editing the blueprint template itself) shows nothing.
184
- const bpShapes = override?.baseline?.('shapes');
185
- const bShape = Array.isArray(bpShapes) ? bpShapes[i] : null;
186
- const shapeOv = (key, dflt, toPx) => {
187
- if (!bShape || bShape.type !== shape.type) return {};
188
- const bv = bShape[key] ?? dflt;
189
- return {
190
- overridden: Math.abs((shape[key] ?? dflt) - bv) > 1e-4,
191
- defaultValue: toPx(bv),
192
- onReset: () => patchSel({ [key]: bv }),
193
- };
194
- };
183
+ // The shapes list is one array prop, so it's overridden as a whole (not per
184
+ // field). The Panel title already tints when the instance diverges (via
185
+ // override.anyOverridden); this drives a single "reset the whole shape set
186
+ // back to the blueprint" affordance -- coarse on purpose, to stay simple.
187
+ const shapesOverridden = Boolean(override?.isOverridden?.('shapes'));
195
188
 
196
189
  const density = component.density > 0 ? component.density : 0.001;
197
190
 
@@ -217,6 +210,14 @@ export class Collider {
217
210
  +
218
211
  </button>
219
212
  </div>
213
+ {shapesOverridden ? (
214
+ <div style={resetRow}>
215
+ <span style={overrideNote}>Shapes overridden</span>
216
+ <button type="button" style={resetLink} onClick={() => override.reset('shapes')}>
217
+ Reset to blueprint
218
+ </button>
219
+ </div>
220
+ ) : null}
220
221
  {addOpen ? (
221
222
  <div style={addMenu}>
222
223
  {/* triangle/polygon are supported by the data model + runtime but not
@@ -238,50 +239,24 @@ export class Collider {
238
239
  <>
239
240
  <SelectField label="Shape" value={shape.type} onChange={setType} options={['box', 'circle']} />
240
241
  {isCircle ? (
241
- <NumberField
242
- label="Radius"
243
- value={radiusPx}
244
- onChange={(v) => patchSel({ r: v / Math.min(bw, bh) })}
245
- {...shapeOv('r', 0.5, (f) => Math.round(f * Math.min(bw, bh)))}
246
- />
242
+ <NumberField label="Radius" value={radiusPx} onChange={(v) => patchSel({ r: v / Math.min(bw, bh) })} />
247
243
  ) : (
248
244
  <>
249
- <NumberField
250
- label="Width"
251
- value={widthPx}
252
- onChange={(v) => patchSel({ w: v / bw })}
253
- {...shapeOv('w', 1, (f) => Math.round(f * bw))}
254
- />
255
- <NumberField
256
- label="Height"
257
- value={heightPx}
258
- onChange={(v) => patchSel({ h: v / bh })}
259
- {...shapeOv('h', 1, (f) => Math.round(f * bh))}
260
- />
245
+ <NumberField label="Width" value={widthPx} onChange={(v) => patchSel({ w: v / bw })} />
246
+ <NumberField label="Height" value={heightPx} onChange={(v) => patchSel({ h: v / bh })} />
261
247
  <NumberField
262
248
  label="Angle"
263
249
  value={Math.round(shape.angle ?? 0)}
264
250
  min={-180}
265
251
  max={180}
266
252
  onChange={(v) => patchSel({ angle: v })}
267
- {...shapeOv('angle', 0, (f) => Math.round(f))}
268
253
  />
269
254
  </>
270
255
  )}
271
256
  </>
272
257
  )}
273
- <NumberField
274
- label="Offset X"
275
- value={offXPx}
276
- onChange={(v) => patchSel({ x: 0.5 + v / bw })}
277
- {...shapeOv('x', 0.5, (f) => Math.round((f - 0.5) * bw))}
278
- />
279
- <NumberField
280
- label="Offset Y"
281
- value={offYPx}
282
- onChange={(v) => patchSel({ y: 0.5 + v / bh })}
283
- {...shapeOv('y', 0.5, (f) => Math.round((f - 0.5) * bh))}
284
- />
258
+ <NumberField label="Offset X" value={offXPx} onChange={(v) => patchSel({ x: 0.5 + v / bw })} />
259
+ <NumberField label="Offset Y" value={offYPx} onChange={(v) => patchSel({ y: 0.5 + v / bh })} />
285
260
  <div style={actionRow}>
286
261
  {autoFit && !fitted ? (
287
262
  <button type="button" onClick={() => writeShapes(shapes.map((s, k) => (k === i ? autoFit : s)))} style={linkBtn}>
@@ -23,10 +23,35 @@
23
23
  "scenes/**",
24
24
  "blueprints/**",
25
25
  "behaviors/**"
26
- ]
26
+ ],
27
+ "fileTypes": [
28
+ {
29
+ "ext": ".scene",
30
+ "label": "Scene",
31
+ "new": "New scene",
32
+ "icon": "globe",
33
+ "editor": "kit",
34
+ "data": true
35
+ },
36
+ {
37
+ "ext": ".pxart",
38
+ "label": "Pixel Art",
39
+ "new": "New drawing",
40
+ "icon": "layer-group",
41
+ "editor": "kit",
42
+ "data": true
43
+ },
44
+ {
45
+ "ext": ".jsx",
46
+ "label": "Behavior",
47
+ "new": "New behavior",
48
+ "icon": "code"
49
+ }
50
+ ],
51
+ "defaultPlayFile": "scenes/main.scene"
27
52
  },
28
53
  "deckId": "ckRZGFW4iPrx",
29
54
  "cardId": "_oBFbAW6DxsO",
30
55
  "title": "physics-2d",
31
- "publishedVersion": "2026-07-28T19:19:14.734Z"
56
+ "publishedVersion": "2026-07-28T22:34:22.244Z"
32
57
  }
@@ -300,6 +300,18 @@ function findBehavior(behaviors, name) {
300
300
  return behaviors.find((candidate) => candidate.behaviorName === name);
301
301
  }
302
302
 
303
+ // Structural equality for plain JSON prop values (scalars, arrays, plain
304
+ // objects) -- used to tell whether an instance override has become identical to
305
+ // its blueprint baseline, so it can be dropped back to inheriting.
306
+ function deepEqual(a, b) {
307
+ if (Object.is(a, b)) return true;
308
+ if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;
309
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
310
+ const keys = Object.keys(a);
311
+ if (keys.length !== Object.keys(b).length) return false;
312
+ return keys.every((k) => deepEqual(a[k], b[k]));
313
+ }
314
+
303
315
  // Apply an inspector-style partial patch (`{ propName: value, ... }`) to one
304
316
  // actor's component, IN PLACE. For a blueprint's own template actor (no
305
317
  // `blueprint` ref) this is a plain merge -- there's no baseline to diff
@@ -321,7 +333,10 @@ export function applyComponentPatch(files, behaviors, actor, behaviorName, nextP
321
333
  const overrides = { ...(actor.components[behaviorName] ?? {}) };
322
334
  for (const [prop, value] of Object.entries(nextProps)) {
323
335
  const baseline = prop in templateProps ? templateProps[prop] : defaultProps[prop];
324
- if (isInheritedProp(Behavior, prop) && Object.is(value, baseline)) delete overrides[prop];
336
+ // Deep-equal, not Object.is: object/array props (e.g. Collider `shapes`,
337
+ // Joints `list`) reset to the blueprint value are a fresh reference with
338
+ // identical content, so a reference check would never drop the override.
339
+ if (isInheritedProp(Behavior, prop) && deepEqual(value, baseline)) delete overrides[prop];
325
340
  else overrides[prop] = value;
326
341
  }
327
342
  if (Object.keys(overrides).length === 0) delete actor.components[behaviorName];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "castle-web-cli",
3
- "version": "0.4.101",
3
+ "version": "0.4.103",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "castle-web": "./dist/index.js"