castle-web-cli 0.4.72 → 0.4.73
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.js +22 -3
- package/dist/agent.js +731 -313
- package/dist/init.js +1 -1
- package/dist/shell/assets/index-Dfn29Bkt.js +108 -0
- package/dist/shell/assets/{index-CVEnWuGV.css → index-WNbOHPBj.css} +1 -1
- package/dist/shell/index.html +2 -2
- package/dist/vitePlugins.js +3 -2
- package/kits/basic-2d/CLAUDE.md +29 -8
- package/kits/basic-2d/behaviors/Collider.jsx +6 -4
- package/kits/basic-2d/behaviors/Layout.jsx +2 -2
- package/kits/basic-2d/behaviors/Sprite.jsx +210 -0
- package/kits/basic-2d/behaviors/tint.js +47 -0
- package/kits/basic-2d/docs/pxart-format.md +298 -0
- package/kits/basic-2d/drawings/pig.pxart +59 -0
- package/kits/basic-2d/editors/App.jsx +125 -76
- package/kits/basic-2d/editors/CodeEditor.jsx +9 -45
- package/kits/basic-2d/editors/FileBrowser.jsx +234 -47
- package/kits/basic-2d/editors/PlayOnly.jsx +9 -7
- package/kits/basic-2d/editors/PxArtEditor.jsx +662 -0
- package/kits/basic-2d/editors/SceneEditor.jsx +587 -221
- package/kits/basic-2d/editors/SelectionOverlay.jsx +808 -0
- package/kits/basic-2d/editors/SingleEditor.jsx +38 -20
- package/kits/basic-2d/editors/codeTheme.js +135 -0
- package/kits/basic-2d/editors/editorHistory.js +44 -17
- package/kits/basic-2d/editors/inspectorSheet.js +23 -0
- package/kits/basic-2d/editors/pixelCanvas.js +11 -0
- package/kits/basic-2d/editors/pixelEditorChrome.jsx +55 -0
- package/kits/basic-2d/editors/pixelGeometry.js +45 -0
- package/kits/basic-2d/editors/pixelInspector.jsx +416 -0
- package/kits/basic-2d/editors/pxArtEditorModel.js +718 -0
- package/kits/basic-2d/editors/pxArtPlayback.js +92 -0
- package/kits/basic-2d/editors/pxArtTimeline.jsx +752 -0
- package/kits/basic-2d/editors/pxArtTimeline.module.css +506 -0
- package/kits/basic-2d/editors/pxArtTools.js +124 -0
- package/kits/basic-2d/editors/useArtboardFit.js +102 -0
- package/kits/basic-2d/engine/ScenePlayer.jsx +10 -4
- package/kits/basic-2d/engine/SceneUI.jsx +3 -11
- package/kits/basic-2d/engine/assets.js +15 -0
- package/kits/basic-2d/engine/files.js +57 -2
- package/kits/basic-2d/engine/pxart.js +985 -0
- package/kits/basic-2d/engine/scene.js +222 -41
- package/kits/basic-2d/engine/ui.jsx +155 -26
- package/kits/basic-2d/engine/ui.module.css +1280 -344
- package/kits/basic-2d/eslint.config.js +21 -0
- package/kits/basic-2d/index.html +13 -0
- package/kits/basic-2d/package.json +1 -0
- package/kits/basic-2d/pnpm-lock.yaml +5 -5
- package/kits/basic-2d/scenes/main.scene +19 -26
- package/kits/basic-2d/scripts/draw.mjs +121 -0
- package/kits/basic-3d/editors/PlayOnly.jsx +9 -1
- package/kits/basic-3d/engine/ScenePlayer.jsx +7 -1
- package/package.json +1 -1
- package/dist/shell/assets/index-vmdKwUE1.js +0 -106
- package/kits/basic-2d/behaviors/Drawing.jsx +0 -142
- package/kits/basic-2d/drawings/block.drawing +0 -70
- package/kits/basic-2d/drawings/default.drawing +0 -70
- package/kits/basic-2d/editors/DrawingEditor.jsx +0 -224
|
@@ -6,6 +6,27 @@ export default [
|
|
|
6
6
|
ignores: ['node_modules/**', 'dist/**', '.castle/**', '*.config.js'],
|
|
7
7
|
},
|
|
8
8
|
js.configs.recommended,
|
|
9
|
+
{
|
|
10
|
+
// Node-side kit tooling (e.g. the `npm run draw` art generator). Runs under
|
|
11
|
+
// Node, not the browser, so it gets Node globals.
|
|
12
|
+
files: ['scripts/**/*.{js,mjs}'],
|
|
13
|
+
languageOptions: {
|
|
14
|
+
ecmaVersion: 'latest',
|
|
15
|
+
sourceType: 'module',
|
|
16
|
+
globals: {
|
|
17
|
+
process: 'readonly',
|
|
18
|
+
Buffer: 'readonly',
|
|
19
|
+
console: 'readonly',
|
|
20
|
+
URL: 'readonly',
|
|
21
|
+
setTimeout: 'readonly',
|
|
22
|
+
clearTimeout: 'readonly',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
rules: {
|
|
26
|
+
'no-undef': 'off',
|
|
27
|
+
'no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
9
30
|
{
|
|
10
31
|
files: ['engine/**/*.{js,jsx}', 'editors/**/*.{js,jsx}', 'behaviors/**/*.{js,jsx}', '*.js', '*.jsx'],
|
|
11
32
|
languageOptions: {
|
package/kits/basic-2d/index.html
CHANGED
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
4
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
|
5
|
+
<meta name="color-scheme" content="dark" />
|
|
5
6
|
<title>Castle Basic 2D JS Kit</title>
|
|
7
|
+
<!-- Declare the dark scheme + opaque dark background statically, before the
|
|
8
|
+
JS-imported CSS module loads. Otherwise this frame paints with the
|
|
9
|
+
default (light) scheme first and only switches to dark once the module
|
|
10
|
+
applies -- and Safari fails to repaint a composited iframe on a
|
|
11
|
+
color-scheme change (WebKit bug 309097), leaving static panels like the
|
|
12
|
+
file list blank until a forced repaint (e.g. opening devtools). -->
|
|
13
|
+
<style>
|
|
14
|
+
html {
|
|
15
|
+
color-scheme: dark;
|
|
16
|
+
background: #0a0a0a;
|
|
17
|
+
}
|
|
18
|
+
</style>
|
|
6
19
|
</head>
|
|
7
20
|
<body>
|
|
8
21
|
<div id="root"></div>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
|
+
"draw": "node scripts/draw.mjs",
|
|
6
7
|
"restart": "node ../../cli/dist/index.js restart .",
|
|
7
8
|
"screenshot": "node ../../cli/dist/index.js screenshot .",
|
|
8
9
|
"check": "eslint . && jscpd && node --input-type=module -e \"const { bundleProject } = await import('../../cli/dist/bundle.js'); await bundleProject('.');\""
|
|
@@ -30,8 +30,8 @@ importers:
|
|
|
30
30
|
specifier: ^1.2.3
|
|
31
31
|
version: 1.2.3
|
|
32
32
|
castle-web-sdk:
|
|
33
|
-
specifier:
|
|
34
|
-
version:
|
|
33
|
+
specifier: file:../../sdk
|
|
34
|
+
version: file:../../sdk
|
|
35
35
|
codemirror:
|
|
36
36
|
specifier: ^6.0.2
|
|
37
37
|
version: 6.0.2
|
|
@@ -291,8 +291,8 @@ packages:
|
|
|
291
291
|
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
|
292
292
|
engines: {node: '>=6'}
|
|
293
293
|
|
|
294
|
-
castle-web-sdk@
|
|
295
|
-
resolution: {
|
|
294
|
+
castle-web-sdk@file:../../sdk:
|
|
295
|
+
resolution: {directory: ../../sdk, type: directory}
|
|
296
296
|
|
|
297
297
|
chalk@4.1.2:
|
|
298
298
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
|
@@ -1149,7 +1149,7 @@ snapshots:
|
|
|
1149
1149
|
|
|
1150
1150
|
callsites@3.1.0: {}
|
|
1151
1151
|
|
|
1152
|
-
castle-web-sdk@
|
|
1152
|
+
castle-web-sdk@file:../../sdk: {}
|
|
1153
1153
|
|
|
1154
1154
|
chalk@4.1.2:
|
|
1155
1155
|
dependencies:
|
|
@@ -1,33 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"background": "#
|
|
2
|
+
"background": "#00396D",
|
|
3
3
|
"actors": [
|
|
4
4
|
{
|
|
5
|
-
"id": "
|
|
5
|
+
"id": "pig",
|
|
6
6
|
"components": {
|
|
7
|
-
"Layout": {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"Drawing": { "file": "drawings/block.drawing", "tint": "#06d6a0ff" }
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"id": "block_br",
|
|
27
|
-
"components": {
|
|
28
|
-
"Layout": { "x": 270, "y": 370, "width": 80, "height": 80 },
|
|
29
|
-
"Drawing": { "file": "drawings/block.drawing", "tint": "#4dabf7ff" }
|
|
7
|
+
"Layout": {
|
|
8
|
+
"x": 210,
|
|
9
|
+
"y": 310,
|
|
10
|
+
"width": 80,
|
|
11
|
+
"height": 80,
|
|
12
|
+
"z": 1
|
|
13
|
+
},
|
|
14
|
+
"Sprite": {
|
|
15
|
+
"file": "drawings/pig.pxart"
|
|
16
|
+
},
|
|
17
|
+
"Collider": {
|
|
18
|
+
"kind": "solid",
|
|
19
|
+
"width": 70,
|
|
20
|
+
"height": 70
|
|
21
|
+
}
|
|
30
22
|
}
|
|
31
23
|
}
|
|
32
|
-
]
|
|
24
|
+
],
|
|
25
|
+
"name": "Main"
|
|
33
26
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// `npm run draw -- <name>` — turn a terse svg-rect emission into a sprite.
|
|
2
|
+
//
|
|
3
|
+
// Reads an <svg> with one <rect> per pixel from stdin (or `--from <file>`),
|
|
4
|
+
// decodes + palette-quantizes it onto the agent 16-color subset, and writes a
|
|
5
|
+
// single-frame `drawings/<name>.pxart` in the deck (the script's cwd).
|
|
6
|
+
//
|
|
7
|
+
// This is a kit-layer concern: the `.pxart` format lives entirely in the kit
|
|
8
|
+
// (engine/pxart.js); the harness has no knowledge of it.
|
|
9
|
+
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import {
|
|
13
|
+
svgRectToPxArt,
|
|
14
|
+
serializeCompact,
|
|
15
|
+
AGENT_PALETTE_16,
|
|
16
|
+
DEFAULT_RESOLUTION,
|
|
17
|
+
} from '../engine/pxart.js';
|
|
18
|
+
|
|
19
|
+
// Read all of stdin as a UTF-8 string. Resolves '' if stdin is a TTY with no
|
|
20
|
+
// piped input so we can give a clear "no input" error instead of hanging.
|
|
21
|
+
function readStdin() {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
if (process.stdin.isTTY) {
|
|
24
|
+
resolve('');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const chunks = [];
|
|
28
|
+
process.stdin.on('data', (c) => chunks.push(c));
|
|
29
|
+
process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
30
|
+
process.stdin.on('error', reject);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Normalize a requested sprite name into a relative `drawings/<...>.pxart`
|
|
35
|
+
// path. Tolerates a leading `drawings/` and/or a trailing `.pxart`/`.px.json`
|
|
36
|
+
// so `draw ship`, `draw ship.pxart`, and `draw drawings/ship.pxart` all land in
|
|
37
|
+
// the same place.
|
|
38
|
+
function resolveSpritePath(dir, name) {
|
|
39
|
+
let rel = name.trim().replace(/\\/g, '/');
|
|
40
|
+
rel = rel.replace(/^\.?\/*/, '');
|
|
41
|
+
if (rel.startsWith('drawings/')) rel = rel.slice('drawings/'.length);
|
|
42
|
+
rel = rel.replace(/\.px\.json$/i, '').replace(/\.pxart$/i, '');
|
|
43
|
+
if (!rel) return null;
|
|
44
|
+
|
|
45
|
+
const drawingsDir = path.resolve(dir, 'drawings');
|
|
46
|
+
const filepath = path.resolve(drawingsDir, `${rel}.pxart`);
|
|
47
|
+
const within = path.relative(drawingsDir, filepath);
|
|
48
|
+
if (within.startsWith('..') || path.isAbsolute(within)) return null;
|
|
49
|
+
return filepath;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getFlagValue(flag) {
|
|
53
|
+
const idx = process.argv.indexOf(flag);
|
|
54
|
+
return idx >= 0 ? process.argv[idx + 1] : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// All positional (non-flag) args after the script, in order.
|
|
58
|
+
function positionals() {
|
|
59
|
+
const args = process.argv.slice(2);
|
|
60
|
+
const withValues = new Set(['--from']);
|
|
61
|
+
const out = [];
|
|
62
|
+
for (let i = 0; i < args.length; i++) {
|
|
63
|
+
if (args[i].startsWith('--')) {
|
|
64
|
+
if (withValues.has(args[i])) i++;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
out.push(args[i]);
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function main() {
|
|
73
|
+
const name = positionals()[0];
|
|
74
|
+
if (!name) {
|
|
75
|
+
console.error('Usage: npm run draw -- <name> [--from FILE] (svg-rect on stdin -> drawings/<name>.pxart)');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// The script's cwd is the deck; sprites land under <deck>/drawings/.
|
|
80
|
+
const dir = process.cwd();
|
|
81
|
+
const filepath = resolveSpritePath(dir, name);
|
|
82
|
+
if (!filepath) {
|
|
83
|
+
console.error(`Invalid sprite name: ${name}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const fromFile = getFlagValue('--from');
|
|
88
|
+
let svg;
|
|
89
|
+
if (fromFile) {
|
|
90
|
+
try {
|
|
91
|
+
svg = fs.readFileSync(path.resolve(fromFile), 'utf-8');
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.error(`Could not read --from file ${fromFile}: ${e instanceof Error ? e.message : String(e)}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
svg = await readStdin();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!svg.trim()) {
|
|
101
|
+
console.error('No svg-rect input. Pipe an <svg> with <rect> cells via stdin or pass --from <file>.');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const art = svgRectToPxArt(svg, { resolution: DEFAULT_RESOLUTION, palette: AGENT_PALETTE_16 });
|
|
106
|
+
if (!art) {
|
|
107
|
+
console.error('Could not decode any pixels from the svg-rect input (no usable <rect fill="...">). Emit one <rect> per filled pixel on a 16x16 viewBox.');
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fs.mkdirSync(path.dirname(filepath), { recursive: true });
|
|
112
|
+
fs.writeFileSync(filepath, serializeCompact(art), 'utf-8');
|
|
113
|
+
|
|
114
|
+
const rel = path.relative(path.resolve(dir), filepath);
|
|
115
|
+
console.log(`Wrote ${rel}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
main().catch((e) => {
|
|
119
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
120
|
+
process.exit(1);
|
|
121
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { Lifecycle } from 'castle-web-sdk';
|
|
2
3
|
import { initialFiles, parseJsonFile, parseModels } from '../engine/files';
|
|
3
4
|
import { ScenePlayer } from '../engine/ScenePlayer';
|
|
4
5
|
import { behaviorClasses } from './behaviorRegistry';
|
|
@@ -10,5 +11,12 @@ export function PlayOnly() {
|
|
|
10
11
|
const { value: sceneData } = parseJsonFile('scenes/main.scene', sceneText);
|
|
11
12
|
if (!sceneData) return null;
|
|
12
13
|
const models = parseModels(initialFiles);
|
|
13
|
-
return
|
|
14
|
+
return (
|
|
15
|
+
<ScenePlayer
|
|
16
|
+
sceneData={sceneData}
|
|
17
|
+
models={models}
|
|
18
|
+
behaviorClasses={behaviorClasses}
|
|
19
|
+
onFirstFrame={Lifecycle.ready}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
14
22
|
}
|
|
@@ -9,9 +9,10 @@ import { TouchControls } from './TouchControls';
|
|
|
9
9
|
// wire keyboard / pointer input, run the update+sync+render loop, and render
|
|
10
10
|
// the behavior-driven UI overlay. No game logic lives here -- behaviors and
|
|
11
11
|
// scenes are the place for that.
|
|
12
|
-
export function ScenePlayer({ sceneData, models, behaviorClasses }) {
|
|
12
|
+
export function ScenePlayer({ sceneData, models, behaviorClasses, onFirstFrame }) {
|
|
13
13
|
const runtimeRef = useRef(null);
|
|
14
14
|
const cameraRef = useRef(null);
|
|
15
|
+
const firstFrameRef = useRef(false);
|
|
15
16
|
const getKeys = useCallback(() => runtimeRef.current?.keys ?? null, []);
|
|
16
17
|
const getRuntime = useCallback(() => runtimeRef.current, []);
|
|
17
18
|
const pointerHandlers = useMemo(() => makePlayPointerHandlers(getRuntime), [getRuntime]);
|
|
@@ -39,6 +40,11 @@ export function ScenePlayer({ sceneData, models, behaviorClasses }) {
|
|
|
39
40
|
applyCameraSpec(camera, runtime.camera ?? defaultCameraSpec);
|
|
40
41
|
runtime.activeCamera = camera;
|
|
41
42
|
renderer.render(runtime.three, camera);
|
|
43
|
+
if (!firstFrameRef.current) {
|
|
44
|
+
firstFrameRef.current = true;
|
|
45
|
+
// Wait one frame so the draw composites before reveal, avoiding a blank flash.
|
|
46
|
+
requestAnimationFrame(() => onFirstFrame?.());
|
|
47
|
+
}
|
|
42
48
|
};
|
|
43
49
|
return (
|
|
44
50
|
<div style={{ position: 'fixed', inset: 0, background: '#000' }}>
|