castle-web-cli 0.4.55 → 0.4.56

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/bundle.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import * as path from 'path';
2
2
  import { build } from 'vite';
3
3
  import { viteSingleFile } from 'vite-plugin-singlefile';
4
+ import { sceneFilesPlugin } from './vitePlugins.js';
4
5
  export async function bundleProject(dir) {
5
6
  const result = await build({
6
7
  root: dir,
7
- plugins: [viteSingleFile()],
8
+ plugins: [viteSingleFile(), sceneFilesPlugin()],
8
9
  build: {
9
10
  write: false,
10
11
  rollupOptions: {
package/dist/serve.js CHANGED
@@ -6,6 +6,7 @@ import { createServer } from 'vite';
6
6
  import { WebSocketServer, WebSocket } from 'ws';
7
7
  import { createIdeServer, DECK_FOCUS_SCRIPT } from './ide.js';
8
8
  import { createAgentServer } from './agent.js';
9
+ import { sceneFilesPlugin } from './vitePlugins.js';
9
10
  import * as config from './config.js';
10
11
  function isPortFree(port) {
11
12
  return new Promise((resolve) => {
@@ -194,7 +195,7 @@ export async function serve(dir, options = {}) {
194
195
  process.on('exit', () => agentServer.shutdown());
195
196
  const vite = await createServer({
196
197
  root: projectDir,
197
- plugins: [castlePlugin(wsPort, ideServer, agentServer)],
198
+ plugins: [castlePlugin(wsPort, ideServer, agentServer), sceneFilesPlugin()],
198
199
  server: {
199
200
  port,
200
201
  strictPort: true,
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from 'vite';
2
+ export declare function sceneFilesPlugin(): Plugin;
@@ -0,0 +1,27 @@
1
+ import * as fs from 'fs';
2
+ // Safety net for `.scene` / `.drawing` files imported as JS modules.
3
+ //
4
+ // These files are DATA (JSON), not modules. The blessed way to read them is the
5
+ // engine's file APIs (`scene.readFromFile(name)` / the `?raw` glob in
6
+ // engine/files.js). But agents sometimes still write a plain
7
+ // `import scene from './scenes/main.scene'`, which Vite would otherwise serve as
8
+ // raw JSON-as-JS and the browser throws "Unexpected token ':'". This plugin
9
+ // makes that import resolve to the parsed object so it just works.
10
+ //
11
+ // `?raw` (and any other query suffix) is left untouched so the engine's
12
+ // `import.meta.glob(..., { query: '?raw' })` still gets raw text.
13
+ export function sceneFilesPlugin() {
14
+ return {
15
+ name: 'castle-scene-files',
16
+ load(id) {
17
+ const [filePath, query] = id.split('?');
18
+ if (query !== undefined)
19
+ return null;
20
+ if (filePath.endsWith('.scene') || filePath.endsWith('.drawing')) {
21
+ const text = fs.readFileSync(filePath, 'utf-8');
22
+ return `export default JSON.parse(${JSON.stringify(text)});\n`;
23
+ }
24
+ return null;
25
+ },
26
+ };
27
+ }
@@ -17,7 +17,7 @@ The deck is already serving when you start (`castle-web init` set that up; see `
17
17
  1. **Build incrementally.** Start with the smallest playable thing (one mechanic, one scene change), `npm run restart`, then add the next piece. Do NOT write the whole game in one shot.
18
18
  2. **After every edit:** `npm run restart` (no hot reload). The served page refreshes and the user sees the change.
19
19
  3. **Prefer real, editable assets.** For game objects, characters, and scenery, make actual drawings (`drawings/*.drawing`) and place them as real actors in `scenes/*.scene` — not shapes drawn in code. Real assets let the creator move and re-skin things in the editor and let other creators remix the deck. Data-driven UI (health bars, score/text, HUD gauges) and dynamic things (bullets, particles, effects) are correctly procedural/code — don't force those into drawings.
20
- 4. **Separate scenes per screen.** Use a separate `scenes/*.scene` file for each distinct screen — menu/title, each level, game-over, etc. — not one mega-scene. Each stays independently editable in the editor. Switch at runtime with `scene.load(targetSceneData)`; wire the transition deck-side (e.g. import the target `.scene` JSON and call `scene.load` on play / win / level change).
20
+ 4. **Separate scenes per screen.** Use a separate `scenes/*.scene` file for each distinct screen — menu/title, each level, game-over, etc. — not one mega-scene. Each stays independently editable in the editor. Switch at runtime with `scene.loadFromFile('gameover.scene')` (reads the file and transitions) on play / win / level change. NEVER `import` a `.scene` file as a module — scene files are data, not modules; use `scene.readFromFile` / `scene.loadFromFile`.
21
21
 
22
22
  Card size is **500 wide × 700 tall** (origin top-left, +y is down).
23
23
 
@@ -107,6 +107,9 @@ Rules: every actor needs a unique `id` (any string) and almost always a `Layout`
107
107
  - `scene.spawnActor({ components: { Layout: {...}, MyBehavior: {...} } })` — add a new actor at runtime. Returns the actor (with auto-minted `id` and `runtime = {}`). Use this; don't push to `scene.data.actors` by hand.
108
108
  - `scene.despawnActor(id)` — remove an actor at runtime. Use this; don't `splice` + `delete` by hand.
109
109
  - `scene.status` — string you can set/read for game-state ('playing', 'gameover', ...).
110
+ - `scene.load(sceneData)` — replace the running scene with the given scene data object.
111
+ - `scene.readFromFile(name)` — read and parse a scene file (`'gameover.scene'`, `'levels/2.scene'`), returning its scene data. Use this instead of importing a `.scene` file.
112
+ - `scene.loadFromFile(name)` — read a scene file and transition the running scene to it. The way to switch screens/levels and to restart, e.g. `scene.loadFromFile('main.scene')`.
110
113
  - `actor.runtime` — per-instance scratchpad for transient state across frames (e.g. velocity, trail history). Not serialized.
111
114
 
112
115
  ## Input shortcuts
@@ -1,8 +1,23 @@
1
+ import { initialFiles, parseJsonFile } from './files';
2
+
1
3
  const CARD_WIDTH = 500;
2
4
  const CARD_HEIGHT = 700;
3
5
 
4
6
  export const cardSize = { width: CARD_WIDTH, height: CARD_HEIGHT };
5
7
 
8
+ // Resolve a scene-file name to its key in the loaded file map. Accepts
9
+ // 'main.scene', 'scenes/main.scene', './scenes/main.scene', or 'main' -- the
10
+ // `.scene` extension and a leading `scenes/` are both optional.
11
+ function resolveSceneFileKey(name) {
12
+ const key = String(name).replace(/^\.?\//, '');
13
+ const candidates = [key];
14
+ if (!key.endsWith('.scene')) candidates.push(`${key}.scene`);
15
+ for (const candidate of [...candidates]) {
16
+ if (!candidate.startsWith('scenes/')) candidates.push(`scenes/${candidate}`);
17
+ }
18
+ return candidates.find((candidate) => initialFiles[candidate] !== undefined) ?? key;
19
+ }
20
+
6
21
  export class SceneRuntime {
7
22
  constructor(sceneData, behaviors, drawings) {
8
23
  this.behaviors = new Map(behaviors.map((Behavior) => [Behavior.behaviorName, Behavior]));
@@ -35,6 +50,26 @@ export class SceneRuntime {
35
50
  }
36
51
  }
37
52
 
53
+ // Read and parse a scene file by name, returning its scene data. Use this
54
+ // instead of `import x from './scenes/foo.scene'` -- scene files are data, not
55
+ // JS modules, so importing them is unsupported.
56
+ readFromFile(name) {
57
+ const key = resolveSceneFileKey(name);
58
+ const text = initialFiles[key];
59
+ if (text === undefined) {
60
+ throw new Error(`scene file not found: ${name}`);
61
+ }
62
+ const { value, error } = parseJsonFile(key, text);
63
+ if (error) throw new Error(error);
64
+ return value;
65
+ }
66
+
67
+ // Transition the running scene to the scene stored in the named file (read it
68
+ // fresh, then load it). The building block for "go to scene" / restart.
69
+ loadFromFile(name) {
70
+ this.load(this.readFromFile(name));
71
+ }
72
+
38
73
  clone() {
39
74
  return new SceneRuntime(this.serialize(), [...this.behaviors.values()], this.drawings);
40
75
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "castle-web-cli",
3
- "version": "0.4.55",
3
+ "version": "0.4.56",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "castle-web": "./dist/index.js"