@ludra/cli 0.5.0 → 0.8.0
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/blocks/chasing-enemy/assets/slime.svg +21 -0
- package/blocks/chasing-enemy/block.json +64 -0
- package/blocks/chasing-enemy/demo.json +32 -0
- package/blocks/chasing-enemy/scripts/chaser.ts +19 -0
- package/blocks/coin-row/assets/coin.svg +22 -0
- package/blocks/coin-row/block.json +157 -0
- package/blocks/coin-row/demo.json +15 -0
- package/blocks/falling-platform/assets/falling-platform.svg +13 -0
- package/blocks/falling-platform/block.json +55 -0
- package/blocks/falling-platform/demo.json +24 -0
- package/blocks/falling-platform/scripts/falling-platform.ts +56 -0
- package/blocks/jump-pad/assets/jump-pad.svg +6 -0
- package/blocks/jump-pad/block.json +56 -0
- package/blocks/jump-pad/demo.json +17 -0
- package/blocks/jump-pad/scripts/jump-pad.ts +10 -0
- package/blocks/key-and-door/assets/door.svg +12 -0
- package/blocks/key-and-door/assets/key.svg +9 -0
- package/blocks/key-and-door/block.json +90 -0
- package/blocks/key-and-door/demo.json +31 -0
- package/blocks/key-and-door/scripts/key.ts +12 -0
- package/blocks/key-and-door/scripts/locked-door.ts +15 -0
- package/blocks/moving-platform/assets/lift.svg +9 -0
- package/blocks/moving-platform/block.json +38 -0
- package/blocks/moving-platform/demo.json +27 -0
- package/blocks/patrol-enemy/assets/slime.svg +21 -0
- package/blocks/patrol-enemy/block.json +60 -0
- package/blocks/patrol-enemy/demo.json +22 -0
- package/blocks/spikes/assets/spikes.svg +8 -0
- package/blocks/spikes/block.json +49 -0
- package/blocks/spikes/demo.json +16 -0
- package/blocks/teleporter/assets/teleporter.svg +14 -0
- package/blocks/teleporter/block.json +89 -0
- package/blocks/teleporter/demo.json +17 -0
- package/blocks/teleporter/scripts/teleporter.ts +17 -0
- package/blocks/time-limit/block.json +36 -0
- package/blocks/time-limit/demo.json +25 -0
- package/blocks/time-limit/scripts/time-limit.ts +16 -0
- package/blocks/turret/assets/bullet.svg +5 -0
- package/blocks/turret/assets/turret.svg +10 -0
- package/blocks/turret/block.json +59 -0
- package/blocks/turret/demo.json +35 -0
- package/blocks/turret/scripts/bullet.ts +11 -0
- package/blocks/turret/scripts/turret.ts +36 -0
- package/dist/blocks.d.ts +65 -0
- package/dist/blocks.js +95 -0
- package/dist/commands/blocks.d.ts +12 -0
- package/dist/commands/blocks.js +49 -0
- package/dist/commands/check.d.ts +1 -0
- package/dist/commands/check.js +57 -14
- package/dist/commands/dev.js +7 -2
- package/dist/commands/new.js +1 -1
- package/dist/commands/play.d.ts +1 -0
- package/dist/commands/play.js +12 -4
- package/dist/commands/review.d.ts +22 -0
- package/dist/commands/review.js +183 -0
- package/dist/commands/test.d.ts +1 -0
- package/dist/commands/test.js +20 -5
- package/dist/headless.d.ts +2 -4
- package/dist/headless.js +21 -3
- package/dist/index.js +25 -5
- package/dist/project.d.ts +18 -5
- package/dist/project.js +61 -21
- package/dist/server.js +22 -9
- package/package.json +4 -3
- package/templates/breakout/CLAUDE.md +43 -0
- package/templates/breakout/_gitignore +2 -0
- package/templates/breakout/ludra.json +5 -0
- package/templates/breakout/package.json +13 -0
- package/templates/breakout/scenes/main.scene.json +653 -0
- package/templates/breakout/scripts/backdrop.ts +101 -0
- package/templates/breakout/scripts/ball.ts +89 -0
- package/templates/breakout/scripts/brick.ts +51 -0
- package/templates/breakout/scripts/paddle.ts +53 -0
- package/templates/breakout/scripts/rules.ts +147 -0
- package/templates/breakout/scripts/wall.ts +21 -0
- package/templates/breakout/shaders/neon.frag +42 -0
- package/templates/breakout/tests/gameplay.test.json +31 -0
- package/templates/platformer/CLAUDE.md +13 -8
- package/templates/platformer/assets/lantern.svg +9 -0
- package/templates/platformer/package.json +1 -1
- package/templates/platformer/prefabs/coin.prefab.json +8 -0
- package/templates/platformer/prefabs/slime.prefab.json +9 -0
- package/templates/platformer/scenes/level-2.scene.json +413 -0
- package/templates/platformer/scenes/main.scene.json +356 -59
- package/templates/platformer/scripts/backdrop.ts +75 -0
- package/templates/platformer/scripts/hud.ts +111 -0
- package/templates/platformer/scripts/lantern.ts +35 -0
- package/templates/platformer/shaders/glow.frag +29 -0
- package/templates/platformer/tests/gameplay.test.json +45 -7
- package/templates/platformer/tests/level-2.test.json +49 -0
- package/templates/topdown/CLAUDE.md +39 -0
- package/templates/topdown/_gitignore +2 -0
- package/templates/topdown/assets/gem.svg +16 -0
- package/templates/topdown/assets/grass.svg +8 -0
- package/templates/topdown/assets/hedge.svg +10 -0
- package/templates/topdown/assets/hero.svg +23 -0
- package/templates/topdown/assets/portal.svg +15 -0
- package/templates/topdown/assets/slime.svg +21 -0
- package/templates/topdown/assets/thorns.svg +7 -0
- package/templates/topdown/ludra.json +5 -0
- package/templates/topdown/package.json +13 -0
- package/templates/topdown/scenes/main.scene.json +499 -0
- package/templates/topdown/scripts/hud.ts +94 -0
- package/templates/topdown/scripts/night.ts +67 -0
- package/templates/topdown/scripts/portal.ts +34 -0
- package/templates/topdown/shaders/dream.frag +31 -0
- package/templates/topdown/tests/gameplay.test.json +30 -0
package/dist/server.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
3
|
import { stripTypeScriptTypes } from "node:module";
|
|
4
4
|
import { dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { collectAssetPaths } from "@ludra/core";
|
|
7
|
-
import { isInside, readScene, scriptPaths } from "./project.js";
|
|
7
|
+
import { isInside, listScenes, readScene, scriptPaths } from "./project.js";
|
|
8
8
|
export const PROJECT_PREFIX = "/__ludra/project/";
|
|
9
9
|
export const SCRIPT_PREFIX = "/__ludra/script/";
|
|
10
10
|
/** An empty same-origin page that headless capture loads before importing the engine. */
|
|
@@ -19,6 +19,12 @@ const MEDIA_TYPES = {
|
|
|
19
19
|
".wav": "audio/wav",
|
|
20
20
|
".mp3": "audio/mpeg",
|
|
21
21
|
".ogg": "audio/ogg",
|
|
22
|
+
".woff2": "font/woff2",
|
|
23
|
+
".woff": "font/woff",
|
|
24
|
+
".ttf": "font/ttf",
|
|
25
|
+
".otf": "font/otf",
|
|
26
|
+
".frag": "text/plain; charset=utf-8",
|
|
27
|
+
".glsl": "text/plain; charset=utf-8",
|
|
22
28
|
};
|
|
23
29
|
/**
|
|
24
30
|
* Serves a project to a browser: the engine's browser runtime, the validated
|
|
@@ -42,7 +48,8 @@ export function createGameServer(project, indexHtml) {
|
|
|
42
48
|
res.end('<!doctype html><html><head><meta charset="utf-8"><title>Ludra</title></head><body style="margin:0"></body></html>');
|
|
43
49
|
}
|
|
44
50
|
else if (url.pathname === "/__ludra/scene") {
|
|
45
|
-
|
|
51
|
+
// ?path= picks another scene, for goals with "next" and world.goto.
|
|
52
|
+
const { scene, diagnostics } = readScene(project, url.searchParams.get("path") ?? undefined);
|
|
46
53
|
res.writeHead(200, { "content-type": "application/json" });
|
|
47
54
|
res.end(JSON.stringify({ ok: !!scene, scene, diagnostics }));
|
|
48
55
|
}
|
|
@@ -94,17 +101,23 @@ export function createGameServer(project, indexHtml) {
|
|
|
94
101
|
});
|
|
95
102
|
return { server, browserModule };
|
|
96
103
|
}
|
|
97
|
-
/** Changes whenever
|
|
104
|
+
/** Changes whenever a scene, prefab or any file they reference changes, so the page knows to reload. */
|
|
98
105
|
function version(project) {
|
|
99
106
|
if (!existsSync(project.entryPath))
|
|
100
107
|
return "missing";
|
|
101
|
-
const files = [
|
|
102
|
-
const
|
|
103
|
-
|
|
108
|
+
const files = [];
|
|
109
|
+
for (const path of listScenes(project)) {
|
|
110
|
+
files.push(resolve(project.dir, path));
|
|
111
|
+
const { scene } = readScene(project, path);
|
|
112
|
+
if (!scene)
|
|
113
|
+
continue;
|
|
104
114
|
files.push(...collectAssetPaths(scene).map((ref) => resolve(project.dir, ref.path)));
|
|
105
|
-
files.push(...scriptPaths(project, scene).map((
|
|
115
|
+
files.push(...scriptPaths(project, scene).map((p) => resolve(project.dir, p)));
|
|
106
116
|
}
|
|
107
|
-
|
|
117
|
+
const prefabs = join(project.dir, "prefabs");
|
|
118
|
+
if (existsSync(prefabs))
|
|
119
|
+
files.push(prefabs, ...readdirSync(prefabs).map((name) => join(prefabs, name)));
|
|
120
|
+
return [...new Set(files)].map((file) => (existsSync(file) ? statSync(file).mtimeMs : 0)).join(":");
|
|
108
121
|
}
|
|
109
122
|
function notFound(res) {
|
|
110
123
|
res.writeHead(404, { "content-type": "text/plain" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ludra/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "The game engine built for AI agents. The ludra command: create, check, play, test and run Ludra games.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -31,10 +31,11 @@
|
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"dist",
|
|
34
|
-
"templates"
|
|
34
|
+
"templates",
|
|
35
|
+
"blocks"
|
|
35
36
|
],
|
|
36
37
|
"dependencies": {
|
|
37
|
-
"@ludra/core": "0.
|
|
38
|
+
"@ludra/core": "0.8.0"
|
|
38
39
|
},
|
|
39
40
|
"keywords": [
|
|
40
41
|
"game-engine",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
A breakout game built with Ludra, a game engine designed for AI agents.
|
|
4
|
+
|
|
5
|
+
## Layout
|
|
6
|
+
|
|
7
|
+
- `ludra.json`: project config (name, entry scene, window size)
|
|
8
|
+
- `scenes/main.scene.json`: the level. Entities are made of components; `effects` adds particles, sound and shake to game events.
|
|
9
|
+
- `shaders/neon.frag`: the post-processing shader (bloom, chromatic aberration, scanlines, CRT curve).
|
|
10
|
+
- `scripts/`: the game rules in TypeScript. Run `npx @ludra/cli components script` for the API.
|
|
11
|
+
- `ball.ts` rests on the paddle, launches, keeps a constant speed and costs a life when it falls out.
|
|
12
|
+
- `paddle.ts` aims the ball by where it hits the paddle.
|
|
13
|
+
- `brick.ts` breaks on contact and adds `points` to the score.
|
|
14
|
+
- `rules.ts` wins the level when no entity whose id starts with `prefix` is left, and draws the brick-break feedback and the whole interface (`drawUI`).
|
|
15
|
+
- `backdrop.ts` and `wall.ts` only draw: the synthwave sky and the neon walls.
|
|
16
|
+
- `tests/*.test.json`: gameplay tests, run headlessly
|
|
17
|
+
|
|
18
|
+
## Loop
|
|
19
|
+
|
|
20
|
+
1. Edit `scenes/main.scene.json` or a script. Run `npx @ludra/cli components` for every component and field.
|
|
21
|
+
2. Run `npx @ludra/cli check` after every edit. Each problem names the JSON path and a fix.
|
|
22
|
+
3. Run `npx @ludra/cli play --watch ball,paddle --ascii` to see what actually happens. Add `--screenshot 1s,end` to save real rendered frames to `screenshots/` and look at them.
|
|
23
|
+
4. Write the behavior you want into `tests/*.test.json` and run `npx @ludra/cli test`.
|
|
24
|
+
5. Run `npx @ludra/cli review` and fix every issue it lists: it catches what makes a game feel unfinished (untested mechanics, silent events, the default HUD, flat looks).
|
|
25
|
+
6. Run `npx @ludra/cli dev` so a human can play it in the browser.
|
|
26
|
+
|
|
27
|
+
Never call a change done until `ludra check`, `ludra test` and `ludra review` are all clean.
|
|
28
|
+
|
|
29
|
+
## Rules of thumb
|
|
30
|
+
|
|
31
|
+
- Coordinates are entity centers in pixels; +y is down. The world has no gravity (`"gravity": 0`).
|
|
32
|
+
- The paddle is a `kinematic` body with `topdown` `"directions": "x"`: left/right only, and it stops at walls.
|
|
33
|
+
- The ball is a `dynamic` body with `"bounce": 1` and `"gravityScale": 0`. Bounce does the reflecting; `ball.ts` keeps the speed constant and never lets it fly almost flat.
|
|
34
|
+
- Bricks are `static` bodies with `brick.ts`. Give each brick an id starting with `brick-` so `rules.ts` counts it.
|
|
35
|
+
- The bottom of the world is open on purpose: that is where the ball is lost.
|
|
36
|
+
- A level is done when a test proves `won == true` with `deaths == 0`. Long routes are easiest to find by writing a small autopilot that steers the paddle under the ball, then pasting its input (frame counts like `"left:24f"`) into the test.
|
|
37
|
+
- Art and effects never change gameplay. Make it playable first, then dress it.
|
|
38
|
+
|
|
39
|
+
## Looks
|
|
40
|
+
|
|
41
|
+
Everything is drawn in code, no image files: each script has a `draw` hook next to its game logic. The scene loads the Orbitron font and runs `shaders/neon.frag` over every frame (tune it with `postfx.uniforms`).
|
|
42
|
+
|
|
43
|
+
There are no limits on looks. A script's `draw(ctx)` paints with the real Canvas 2D context (`ctx.g`), `drawUI(ctx)` replaces the HUD, `"fonts"` loads Google Fonts, and `"postfx"` runs a GLSL shader over every frame. Run `npx @ludra/cli components script` for the API. Draw hooks only paint: they can't change the game, so tests stay valid. After every visual change, look at real frames with `play --screenshot`.
|