@ludra/cli 0.0.0-stage → 0.4.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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -2
  3. package/dist/commands/check.d.ts +3 -0
  4. package/dist/commands/check.js +21 -0
  5. package/dist/commands/components.d.ts +3 -0
  6. package/dist/commands/components.js +55 -0
  7. package/dist/commands/dev.d.ts +4 -0
  8. package/dist/commands/dev.js +67 -0
  9. package/dist/commands/new.d.ts +3 -0
  10. package/dist/commands/new.js +46 -0
  11. package/dist/commands/play.d.ts +12 -0
  12. package/dist/commands/play.js +126 -0
  13. package/dist/commands/test.d.ts +3 -0
  14. package/dist/commands/test.js +55 -0
  15. package/dist/format.d.ts +11 -0
  16. package/dist/format.js +24 -0
  17. package/dist/headless.d.ts +16 -0
  18. package/dist/headless.js +202 -0
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +80 -0
  21. package/dist/project.d.ts +29 -0
  22. package/dist/project.js +94 -0
  23. package/dist/server.d.ts +17 -0
  24. package/dist/server.js +93 -0
  25. package/package.json +49 -4
  26. package/templates/platformer/CLAUDE.md +30 -0
  27. package/templates/platformer/_gitignore +2 -0
  28. package/templates/platformer/assets/coin.svg +22 -0
  29. package/templates/platformer/assets/flag.svg +15 -0
  30. package/templates/platformer/assets/ground.svg +12 -0
  31. package/templates/platformer/assets/lift.svg +9 -0
  32. package/templates/platformer/assets/platform.svg +9 -0
  33. package/templates/platformer/assets/player.svg +65 -0
  34. package/templates/platformer/assets/slime.svg +21 -0
  35. package/templates/platformer/assets/spikes.svg +8 -0
  36. package/templates/platformer/ludra.json +5 -0
  37. package/templates/platformer/package.json +13 -0
  38. package/templates/platformer/scenes/main.scene.json +154 -0
  39. package/templates/platformer/tests/gameplay.test.json +36 -0
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
2
+ <!-- Tile: riveted metal with hazard stripes for moving platforms. -->
3
+ <rect width="20" height="20" fill="#8fa3d9"/>
4
+ <rect width="20" height="3" fill="#c3d0f2"/>
5
+ <rect y="14" width="20" height="6" fill="#f2c14e"/>
6
+ <path d="M0 20 L 6 14 L 10 14 L 4 20 Z M10 20 L 16 14 L 20 14 L 14 20 Z" fill="#2b3150"/>
7
+ <circle cx="5" cy="8" r="1.4" fill="#5d6f9e"/>
8
+ <circle cx="15" cy="8" r="1.4" fill="#5d6f9e"/>
9
+ </svg>
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
2
+ <!-- Tile: stone brick for ledges and steps. -->
3
+ <rect width="20" height="20" fill="#56658f"/>
4
+ <rect width="20" height="3" fill="#7a8ab8"/>
5
+ <rect y="17" width="20" height="3" fill="#434f72"/>
6
+ <rect x="9" y="3" width="2" height="14" fill="#434f72"/>
7
+ <rect x="1" y="5" width="3" height="2" fill="#6b7aa6"/>
8
+ <rect x="13" y="9" width="4" height="2" fill="#6b7aa6"/>
9
+ </svg>
@@ -0,0 +1,65 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="140" height="36" viewBox="0 0 140 36">
2
+ <!-- Spritesheet: 5 frames of 28x36, facing right. 0 idle, 1-2 run, 3 jump, 4 fall. -->
3
+ <defs>
4
+ <g id="eyes">
5
+ <ellipse cx="15" cy="15" rx="3.2" ry="3.8" fill="#ffffff"/>
6
+ <ellipse cx="21.5" cy="15" rx="3.2" ry="3.8" fill="#ffffff"/>
7
+ <circle cx="16.3" cy="15.6" r="1.7" fill="#1d2333"/>
8
+ <circle cx="22.8" cy="15.6" r="1.7" fill="#1d2333"/>
9
+ </g>
10
+ <path id="leaf" d="M14 7 q 2 -6 8 -5 q -2 5 -8 5 z" fill="#4cc9a0"/>
11
+ </defs>
12
+
13
+ <!-- 0: idle -->
14
+ <g>
15
+ <rect x="7" y="29" width="6" height="6" rx="2" fill="#3f9e5c"/>
16
+ <rect x="16" y="29" width="6" height="6" rx="2" fill="#3f9e5c"/>
17
+ <rect x="3" y="7" width="22" height="24" rx="8" fill="#7cf29c"/>
18
+ <rect x="3" y="21" width="22" height="10" rx="5" fill="#62d886"/>
19
+ <use href="#leaf"/>
20
+ <use href="#eyes"/>
21
+ </g>
22
+
23
+ <!-- 1: run, legs apart -->
24
+ <g transform="translate(28 0)">
25
+ <rect x="3" y="28" width="6" height="6" rx="2" fill="#3f9e5c"/>
26
+ <rect x="19" y="29" width="6" height="6" rx="2" fill="#3f9e5c"/>
27
+ <rect x="3" y="6" width="22" height="24" rx="8" fill="#7cf29c"/>
28
+ <rect x="3" y="20" width="22" height="10" rx="5" fill="#62d886"/>
29
+ <use href="#leaf" transform="translate(0 -1)"/>
30
+ <use href="#eyes" transform="translate(0 -1)"/>
31
+ </g>
32
+
33
+ <!-- 2: run, legs together -->
34
+ <g transform="translate(56 0)">
35
+ <rect x="10" y="29" width="6" height="6" rx="2" fill="#3f9e5c"/>
36
+ <rect x="14" y="28" width="6" height="6" rx="2" fill="#3f9e5c"/>
37
+ <rect x="3" y="7" width="22" height="24" rx="8" fill="#7cf29c"/>
38
+ <rect x="3" y="21" width="22" height="10" rx="5" fill="#62d886"/>
39
+ <use href="#leaf"/>
40
+ <use href="#eyes"/>
41
+ </g>
42
+
43
+ <!-- 3: jump, stretched -->
44
+ <g transform="translate(84 0)">
45
+ <rect x="8" y="28" width="5" height="7" rx="2" fill="#3f9e5c"/>
46
+ <rect x="15" y="28" width="5" height="7" rx="2" fill="#3f9e5c"/>
47
+ <rect x="4" y="3" width="20" height="27" rx="8" fill="#7cf29c"/>
48
+ <rect x="4" y="19" width="20" height="11" rx="5" fill="#62d886"/>
49
+ <use href="#leaf" transform="translate(0 -4)"/>
50
+ <use href="#eyes" transform="translate(-0.5 -3)"/>
51
+ </g>
52
+
53
+ <!-- 4: fall, squashed, looking down -->
54
+ <g transform="translate(112 0)">
55
+ <rect x="4" y="29" width="7" height="6" rx="2" fill="#3f9e5c"/>
56
+ <rect x="17" y="29" width="7" height="6" rx="2" fill="#3f9e5c"/>
57
+ <rect x="1" y="10" width="26" height="21" rx="8" fill="#7cf29c"/>
58
+ <rect x="1" y="22" width="26" height="9" rx="4.5" fill="#62d886"/>
59
+ <use href="#leaf" transform="translate(0 3)"/>
60
+ <ellipse cx="15" cy="18" rx="3.2" ry="3.8" fill="#ffffff"/>
61
+ <ellipse cx="21.5" cy="18" rx="3.2" ry="3.8" fill="#ffffff"/>
62
+ <circle cx="16" cy="20" r="1.7" fill="#1d2333"/>
63
+ <circle cx="22.5" cy="20" r="1.7" fill="#1d2333"/>
64
+ </g>
65
+ </svg>
@@ -0,0 +1,21 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="64" height="26" viewBox="0 0 64 26">
2
+ <!-- Spritesheet: 2 frames of 32x26, a bouncing slime facing right. -->
3
+ <g>
4
+ <path d="M2 25 C 2 10, 8 3, 16 3 C 24 3, 30 10, 30 25 Z" fill="#c77dff"/>
5
+ <path d="M5 25 C 5 18, 9 14, 13 14 C 9 17, 8 21, 8 25 Z" fill="#e0b0ff" opacity="0.7"/>
6
+ <path d="M14 11 l 6 2" stroke="#2a1440" stroke-width="2" stroke-linecap="round"/>
7
+ <path d="M27 11 l -4 2" stroke="#2a1440" stroke-width="2" stroke-linecap="round"/>
8
+ <circle cx="18" cy="15" r="2" fill="#2a1440"/>
9
+ <circle cx="24" cy="15" r="2" fill="#2a1440"/>
10
+ <path d="M17 20 q 4 2 8 0" stroke="#2a1440" stroke-width="1.5" fill="none" stroke-linecap="round"/>
11
+ </g>
12
+ <g transform="translate(32 0)">
13
+ <path d="M0 25 C 0 14, 7 8, 16 8 C 25 8, 32 14, 32 25 Z" fill="#c77dff"/>
14
+ <path d="M3 25 C 3 20, 7 17, 11 17 C 7 19, 6 22, 6 25 Z" fill="#e0b0ff" opacity="0.7"/>
15
+ <path d="M14 15 l 6 2" stroke="#2a1440" stroke-width="2" stroke-linecap="round"/>
16
+ <path d="M28 15 l -4 2" stroke="#2a1440" stroke-width="2" stroke-linecap="round"/>
17
+ <circle cx="18" cy="18.5" r="2" fill="#2a1440"/>
18
+ <circle cx="24.5" cy="18.5" r="2" fill="#2a1440"/>
19
+ <path d="M17 22.5 q 4 1.5 8 0" stroke="#2a1440" stroke-width="1.5" fill="none" stroke-linecap="round"/>
20
+ </g>
21
+ </svg>
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" viewBox="0 0 20 16">
2
+ <!-- Tile: repeats horizontally across the hazard. -->
3
+ <path d="M0 16 L 5 1 L 10 16 Z" fill="#e4e8f2"/>
4
+ <path d="M5 1 L 10 16 L 6.5 16 Z" fill="#a9b1c7"/>
5
+ <path d="M10 16 L 15 1 L 20 16 Z" fill="#e4e8f2"/>
6
+ <path d="M15 1 L 20 16 L 16.5 16 Z" fill="#a9b1c7"/>
7
+ <rect x="0" y="13" width="20" height="3" fill="#ff5d73"/>
8
+ </svg>
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "entry": "scenes/main.scene.json",
4
+ "window": { "width": 960, "height": 540 }
5
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "ludra dev",
7
+ "check": "ludra check",
8
+ "test": "ludra test"
9
+ },
10
+ "devDependencies": {
11
+ "@ludra/cli": "^0.4.0"
12
+ }
13
+ }
@@ -0,0 +1,154 @@
1
+ {
2
+ "name": "Level 1",
3
+ "world": { "width": 1920, "height": 540, "gravity": 1800, "background": "#1b2140" },
4
+ "effects": {
5
+ "jump": { "sound": "jump", "particles": { "count": 6, "color": "#cfd8ff", "speed": 60, "direction": 90, "spread": 150, "life": 0.25, "size": 3, "gravity": 0 } },
6
+ "land": { "sound": "thud", "particles": { "count": 5, "color": "#cfd8ff", "speed": 50, "direction": -90, "spread": 160, "life": 0.2, "size": 2, "gravity": 300 } },
7
+ "collect": { "sound": "coin", "particles": { "count": 14, "color": "#ffd166", "speed": 140, "life": 0.45, "size": 3, "gravity": 200 } },
8
+ "stomp": { "sound": "hit", "shake": 5, "particles": { "count": 18, "color": "#c77dff", "speed": 180, "life": 0.5, "size": 4 } },
9
+ "die": { "sound": "explosion", "shake": 10, "particles": { "count": 30, "color": "#ff5d73", "speed": 220, "life": 0.7, "size": 4 } },
10
+ "win": { "sound": "powerup", "shake": 3, "particles": { "count": 60, "color": "#4cc9f0", "speed": 260, "life": 1.2, "size": 4, "gravity": 150 } }
11
+ },
12
+ "entities": [
13
+ {
14
+ "id": "player",
15
+ "components": {
16
+ "transform": { "x": 80, "y": 482 },
17
+ "body": { "type": "dynamic", "width": 28, "height": 36 },
18
+ "sprite": {
19
+ "color": "#7cf29c",
20
+ "image": "assets/player.svg",
21
+ "frameWidth": 28,
22
+ "frameHeight": 36,
23
+ "idle": "0",
24
+ "run": "1-2",
25
+ "jump": "3",
26
+ "fall": "4",
27
+ "fps": 10,
28
+ "layer": 3
29
+ },
30
+ "platformer": { "speed": 240, "jumpSpeed": 680 },
31
+ "camera": {}
32
+ }
33
+ },
34
+ {
35
+ "id": "ground-start",
36
+ "components": {
37
+ "transform": { "x": 380, "y": 520 },
38
+ "body": { "type": "static", "width": 760, "height": 40 },
39
+ "sprite": { "color": "#3b4766", "image": "assets/ground.svg", "tile": true }
40
+ }
41
+ },
42
+ {
43
+ "id": "ground-end",
44
+ "components": {
45
+ "transform": { "x": 1450, "y": 520 },
46
+ "body": { "type": "static", "width": 940, "height": 40 },
47
+ "sprite": { "color": "#3b4766", "image": "assets/ground.svg", "tile": true }
48
+ }
49
+ },
50
+ {
51
+ "id": "wall-left",
52
+ "components": {
53
+ "transform": { "x": -10, "y": 270 },
54
+ "body": { "type": "static", "width": 20, "height": 540 }
55
+ }
56
+ },
57
+ {
58
+ "id": "wall-right",
59
+ "components": {
60
+ "transform": { "x": 1930, "y": 270 },
61
+ "body": { "type": "static", "width": 20, "height": 540 }
62
+ }
63
+ },
64
+ {
65
+ "id": "ledge",
66
+ "components": {
67
+ "transform": { "x": 300, "y": 410 },
68
+ "body": { "type": "static", "width": 160, "height": 20 },
69
+ "sprite": { "color": "#56658f", "image": "assets/platform.svg", "tile": true }
70
+ }
71
+ },
72
+ {
73
+ "id": "coin-1",
74
+ "components": {
75
+ "transform": { "x": 300, "y": 370 },
76
+ "body": { "type": "static", "width": 20, "height": 20, "sensor": true },
77
+ "sprite": { "shape": "circle", "color": "#ffd166", "image": "assets/coin.svg", "frameWidth": 20, "idle": "0-3", "layer": 1 },
78
+ "collectible": { "score": 1 }
79
+ }
80
+ },
81
+ {
82
+ "id": "spikes",
83
+ "components": {
84
+ "transform": { "x": 560, "y": 492 },
85
+ "body": { "type": "static", "width": 80, "height": 16, "sensor": true },
86
+ "sprite": { "color": "#ff5d73", "image": "assets/spikes.svg", "tile": true, "layer": 1 },
87
+ "hazard": {}
88
+ }
89
+ },
90
+ {
91
+ "id": "lift",
92
+ "components": {
93
+ "transform": { "x": 800, "y": 440 },
94
+ "body": { "type": "kinematic", "width": 120, "height": 20 },
95
+ "sprite": { "color": "#8fa3d9", "image": "assets/lift.svg", "tile": true },
96
+ "patrol": { "axis": "x", "distance": 140, "speed": 90 }
97
+ }
98
+ },
99
+ {
100
+ "id": "coin-2",
101
+ "components": {
102
+ "transform": { "x": 870, "y": 390 },
103
+ "body": { "type": "static", "width": 20, "height": 20, "sensor": true },
104
+ "sprite": { "shape": "circle", "color": "#ffd166", "image": "assets/coin.svg", "frameWidth": 20, "idle": "0-3", "layer": 1 },
105
+ "collectible": { "score": 1 }
106
+ }
107
+ },
108
+ {
109
+ "id": "slime",
110
+ "components": {
111
+ "transform": { "x": 1150, "y": 487 },
112
+ "body": { "type": "kinematic", "width": 32, "height": 26, "sensor": true },
113
+ "sprite": { "color": "#c77dff", "image": "assets/slime.svg", "frameWidth": 32, "idle": "0", "run": "0-1", "fps": 4, "layer": 2 },
114
+ "patrol": { "axis": "x", "distance": 200, "speed": 70 },
115
+ "hazard": { "stompable": true, "score": 1 }
116
+ }
117
+ },
118
+ {
119
+ "id": "step-1",
120
+ "components": {
121
+ "transform": { "x": 1500, "y": 410 },
122
+ "body": { "type": "static", "width": 140, "height": 20 },
123
+ "sprite": { "color": "#56658f", "image": "assets/platform.svg", "tile": true }
124
+ }
125
+ },
126
+ {
127
+ "id": "step-2",
128
+ "components": {
129
+ "transform": { "x": 1680, "y": 330 },
130
+ "body": { "type": "static", "width": 140, "height": 20 },
131
+ "sprite": { "color": "#56658f", "image": "assets/platform.svg", "tile": true }
132
+ }
133
+ },
134
+ {
135
+ "id": "coin-3",
136
+ "components": {
137
+ "transform": { "x": 1680, "y": 290 },
138
+ "body": { "type": "static", "width": 20, "height": 20, "sensor": true },
139
+ "sprite": { "shape": "circle", "color": "#ffd166", "image": "assets/coin.svg", "frameWidth": 20, "idle": "0-3", "layer": 1 },
140
+ "collectible": { "score": 1 }
141
+ }
142
+ },
143
+ {
144
+ "id": "flag",
145
+ "components": {
146
+ "transform": { "x": 1860, "y": 460 },
147
+ "body": { "type": "static", "width": 24, "height": 80, "sensor": true },
148
+ "sprite": { "color": "#4cc9f0", "image": "assets/flag.svg", "frameWidth": 24, "idle": "0-1", "fps": 3, "flip": false, "layer": 1 },
149
+ "emitter": { "rate": 6, "color": "#fff3b0", "speed": 30, "spread": 70, "life": 1.2, "size": 3, "gravity": -20 },
150
+ "goal": { "requireScore": 4 }
151
+ }
152
+ }
153
+ ]
154
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "tests": [
3
+ {
4
+ "name": "player rests on the ground",
5
+ "duration": "1s",
6
+ "assert": ["player.grounded == true", "player.y == 482", "deaths == 0"]
7
+ },
8
+ {
9
+ "name": "player runs right",
10
+ "inputs": "right:1s",
11
+ "assert": ["player.x > 300 at 1s", "player.grounded == true at 1s"]
12
+ },
13
+ {
14
+ "name": "jumping leaves the ground and lands again",
15
+ "inputs": "wait:0.2s, jump:0.4s",
16
+ "assert": ["events.jump == 1", "player.y < 400 at 0.5s", "player.grounded == true at end"]
17
+ },
18
+ {
19
+ "name": "spikes kill the player, who respawns at the start",
20
+ "inputs": "right:1.85s",
21
+ "duration": "2s",
22
+ "assert": ["deaths == 1", "events.die == 1", "player.x < 200"]
23
+ },
24
+ {
25
+ "name": "the lift carries the player over the pit",
26
+ "inputs": "right:0.3s, right+jump:0.45s, right:0.85s, right+jump:0.45s, right:0.35s, right+jump:0.45s, right:0.18s",
27
+ "duration": "4s",
28
+ "assert": ["coin-2.exists == false", "player.grounded == true", "player.x > 860", "deaths == 0"]
29
+ },
30
+ {
31
+ "name": "the level can be completed without dying",
32
+ "inputs": "right:0.3s, right+jump:0.45s, right:0.85s, right+jump:0.45s, right:0.35s, right+jump:0.45s, right:0.18s, wait:1.0s, right+jump:0.45s, right:0.3s, wait:0.6s, right+jump:0.45s, right:0.1s, wait:0.6s, right:0.7s, right+jump:0.45s, right:0.2s, right+jump:0.45s, right:1.5s",
33
+ "assert": ["won == true", "deaths == 0", "score == 4", "events.stomp == 1", "slime.exists == false"]
34
+ }
35
+ ]
36
+ }