castle-web-cli 0.4.81 → 0.4.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-prompts.d.ts +0 -3
- package/dist/agent-prompts.js +3 -8
- package/dist/agent.d.ts +0 -2
- package/dist/agent.js +4 -148
- package/dist/castle-host/host.js +28 -0
- package/dist/init.js +1 -1
- package/dist/native/loop.js +10 -28
- package/dist/native/tools.d.ts +0 -1
- package/dist/native/tools.js +3 -79
- package/dist/native/types.d.ts +0 -1
- package/dist/native/types.js +3 -3
- package/dist/shell/assets/{index-D3unT7do.js → index-ByhgiJoP.js} +1 -1
- package/dist/shell/assets/{index-RZrw5gQ2.css → index-D6hM_VlW.css} +1 -1
- package/dist/shell/index.html +2 -2
- package/kits/basic-2d/CLAUDE.md +2 -1
- package/kits/basic-2d/engine/ScenePlayer.jsx +5 -1
- package/kits/basic-2d/engine/blueprint.js +74 -3
- package/package.json +1 -1
- package/kits/basic-2d/pnpm-workspace.yaml +0 -3
package/kits/basic-2d/CLAUDE.md
CHANGED
|
@@ -100,13 +100,14 @@ This inline shape is fine to keep writing by hand — see `## Blueprints` for wh
|
|
|
100
100
|
Every actor placed through the editor is an **instance** of a **blueprint**: a `.scene` file under `blueprints/` whose single actor (`actors[0]`, no `id`) is the template. An instance actor looks like:
|
|
101
101
|
|
|
102
102
|
```json
|
|
103
|
-
{ "id": "abc123", "blueprint": "blueprints/
|
|
103
|
+
{ "id": "abc123", "blueprint": "blueprints/enemy.scene", "components": { "Layout": { "x": 100, "y": 200 } } }
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
`components` on an instance holds ONLY the props that differ from the blueprint (an override), merged on top of the blueprint's template per-property. Editing the blueprint file changes every instance that doesn't override that prop; editing an instance's own `Layout.x`/`y`/`rotation` never touches the blueprint (position/rotation are always instance-local — see `Layout.jsx`'s `propertyMeta`). This is entirely optional for you to manage directly:
|
|
107
107
|
|
|
108
108
|
- **Plain inline actors (no `blueprint` field) still just work.** Write them the normal way; the editor mints a blueprint for each one automatically the first time a human opens that scene in the browser, and rewrites the actor into an instance. You never have to do this migration yourself.
|
|
109
109
|
- **Reference a blueprint directly when it saves real duplication** — e.g. 10 identical enemy actors: write one `blueprints/enemy.scene` (`{ "name": "Enemy", "actors": [{ "components": { "Sprite": {...}, "Collider": {...}, "Enemy": {...} } }] }`) and 10 scene actors that each just set `"blueprint": "blueprints/enemy.scene"` plus their own `Layout.x`/`y`. Don't bother doing this for a one-off actor — plain inline is simpler and migrates fine later.
|
|
110
|
+
- **When an actor is a distinct kind of thing** (not a one-off), author its blueprint yourself — `blueprints/<meaningful>.scene` with a real `"name"` — instead of leaving it inline for auto-migration to name; migration derives a reasonable name/file from the actor's `id` when it can, but a blueprint you write intentionally will always be clearer than one it infers.
|
|
110
111
|
- Never invent your own `blueprint` path pointing at a file you didn't also create — a dangling reference resolves to "no template" (the instance's own sparse `components` render alone, missing whatever it expected to inherit).
|
|
111
112
|
|
|
112
113
|
## Built-in behaviors
|
|
@@ -54,7 +54,11 @@ export function ScenePlayer({ sceneData, sprites, files, behaviorClasses, onFirs
|
|
|
54
54
|
canvas.addEventListener('pointermove', onPointerMove);
|
|
55
55
|
canvas.addEventListener('pointerup', onPointerUp);
|
|
56
56
|
canvas.addEventListener('pointercancel', onPointerUp);
|
|
57
|
-
|
|
57
|
+
// Don't steal focus from the embedding shell (e.g. the chat composer) on
|
|
58
|
+
// reload/remount. A real standalone/top-level play, or an iframe that's
|
|
59
|
+
// already focused, still auto-focuses; a click into the game always
|
|
60
|
+
// focuses via onPointerDown above.
|
|
61
|
+
if (document.hasFocus() || window.parent === window) canvas.focus();
|
|
58
62
|
const stopLoop = startPlayerLoop(canvas, ctx, runtime, onFirstFrame);
|
|
59
63
|
return () => {
|
|
60
64
|
stopLoop();
|
|
@@ -126,6 +126,69 @@ function mintBlueprintPath(existingPaths) {
|
|
|
126
126
|
return `${BLUEPRINTS_DIR}/blueprint-${max + 1}.scene`;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// "auto-tetris" -> "Auto Tetris". Splits on non-alphanumeric runs (covers the
|
|
130
|
+
// hyphen/underscore ids actors typically use) and title-cases each word.
|
|
131
|
+
function humanizeActorId(id) {
|
|
132
|
+
return id
|
|
133
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
134
|
+
.filter(Boolean)
|
|
135
|
+
.map((word) => word[0].toUpperCase() + word.slice(1))
|
|
136
|
+
.join(' ');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// "Auto Tetris" -> "auto-tetris" for the filename slug. Kept independent of
|
|
140
|
+
// the actor id's own formatting (an id could already be any casing/shape).
|
|
141
|
+
function slugifyActorId(id) {
|
|
142
|
+
return id
|
|
143
|
+
.trim()
|
|
144
|
+
.toLowerCase()
|
|
145
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
146
|
+
.replace(/^-+|-+$/g, '');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Derive a migration blueprint's { name, path } from the orphan actor's own
|
|
150
|
+
// `id` -- the one meaningful signal available at migration time (vs. the
|
|
151
|
+
// anonymous "Blueprint N" counter), so e.g. an actor `id: "logo"` mints
|
|
152
|
+
// `blueprints/logo.scene` named "Logo" instead of "Blueprint 1". Returns null
|
|
153
|
+
// when the id doesn't yield a usable name/slug, so the caller falls back to
|
|
154
|
+
// the counter-based mint functions.
|
|
155
|
+
//
|
|
156
|
+
// Collision safety is load-bearing: `knownBlueprints` includes every
|
|
157
|
+
// already-migrated blueprint in this same run (see the caller's loop), so two
|
|
158
|
+
// orphan actors that humanize to the same name, or that collide with a
|
|
159
|
+
// pre-existing blueprint, must never mint the same path or duplicate name.
|
|
160
|
+
// Deterministically disambiguate with a numeric suffix instead.
|
|
161
|
+
function deriveBlueprintName(actorId, knownBlueprints) {
|
|
162
|
+
if (typeof actorId !== 'string') return null;
|
|
163
|
+
const baseSlug = slugifyActorId(actorId);
|
|
164
|
+
const baseName = humanizeActorId(actorId.trim());
|
|
165
|
+
if (!baseSlug || !baseName) return null;
|
|
166
|
+
const existingNames = new Set(knownBlueprints.map((blueprint) => blueprint.name));
|
|
167
|
+
const existingPaths = new Set(knownBlueprints.map((blueprint) => blueprint.path));
|
|
168
|
+
const MAX_ATTEMPTS = 1000;
|
|
169
|
+
for (let suffix = 1; suffix <= MAX_ATTEMPTS; suffix += 1) {
|
|
170
|
+
const name = suffix === 1 ? baseName : `${baseName} ${suffix}`;
|
|
171
|
+
const path = suffix === 1 ? `${BLUEPRINTS_DIR}/${baseSlug}.scene` : `${BLUEPRINTS_DIR}/${baseSlug}-${suffix}.scene`;
|
|
172
|
+
if (!existingNames.has(name) && !existingPaths.has(path)) return { name, path };
|
|
173
|
+
}
|
|
174
|
+
// Pathological: 1000 colliding suffixes. Let the caller fall back to the
|
|
175
|
+
// counter-based mint functions rather than looping forever.
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// { name, path } for a freshly minted migration blueprint: the actor id's
|
|
180
|
+
// derived name/slug when usable and collision-free, else the anonymous
|
|
181
|
+
// counter (see `deriveBlueprintName`). Factored out so the branching here
|
|
182
|
+
// doesn't add to `migrateOrphanActors`'s own complexity.
|
|
183
|
+
function mintMigrationBlueprintName(actorId, knownBlueprints) {
|
|
184
|
+
const derived = deriveBlueprintName(actorId, knownBlueprints);
|
|
185
|
+
if (derived) return derived;
|
|
186
|
+
return {
|
|
187
|
+
name: mintBlueprintName(knownBlueprints.map((blueprint) => blueprint.name)),
|
|
188
|
+
path: mintBlueprintPath(knownBlueprints.map((blueprint) => blueprint.path)),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
129
192
|
export function formatBlueprintFileText(name, components) {
|
|
130
193
|
return formatJson({ name, actors: [{ components }] });
|
|
131
194
|
}
|
|
@@ -408,11 +471,19 @@ export function migrateOrphanActors(files, behaviors, sceneData) {
|
|
|
408
471
|
if (!props) continue;
|
|
409
472
|
const Behavior = findBehavior(behaviors, behaviorName);
|
|
410
473
|
const { inherited, overridden } = splitByInherit(Behavior, props);
|
|
411
|
-
|
|
474
|
+
// A behavior attached with no props at all (e.g. `AutoTetris: {}`) has
|
|
475
|
+
// both `inherited` and `overridden` empty. It must still land somewhere,
|
|
476
|
+
// or the attachment itself -- not just some prop of it -- silently
|
|
477
|
+
// vanishes from the resolved actor. Fall back to the template side (as
|
|
478
|
+
// `{}`) so the instance keeps referencing the behavior exactly once.
|
|
479
|
+
if (Object.keys(inherited).length > 0 || Object.keys(overridden).length === 0) {
|
|
480
|
+
templateComponents[behaviorName] = inherited;
|
|
481
|
+
}
|
|
412
482
|
if (Object.keys(overridden).length > 0) instanceOverrides[behaviorName] = overridden;
|
|
413
483
|
}
|
|
414
|
-
|
|
415
|
-
|
|
484
|
+
// The actor's own id is the one meaningful signal available here (vs. an
|
|
485
|
+
// anonymous "Blueprint N") -- see `mintMigrationBlueprintName`.
|
|
486
|
+
const { name, path } = mintMigrationBlueprintName(actor.id, knownBlueprints);
|
|
416
487
|
const text = formatBlueprintFileText(name, templateComponents);
|
|
417
488
|
newBlueprintFiles.push({ path, text });
|
|
418
489
|
knownBlueprints = [...knownBlueprints, { path, name, components: templateComponents }];
|
package/package.json
CHANGED