@pix3/cli 1.6.4 → 2.0.0-alpha.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 (168) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +722 -788
  3. package/dist/index.js +557 -421
  4. package/dist/runtime-types.json +1 -1
  5. package/dist/smoke/prebuilt/{build-7MIXIMSZ.mjs → build-7REMSA2S.mjs} +52 -52
  6. package/dist/smoke/prebuilt/chunk-4DGG2BKR.mjs +2 -0
  7. package/dist/smoke/prebuilt/chunk-YLBMUJ2H.mjs +238 -0
  8. package/dist/smoke/prebuilt/runtime.mjs +1 -1
  9. package/dist/smoke/prebuilt/smoke-worker.mjs +211 -2
  10. package/dist/smoke/prebuilt/tree-defaults.mjs +1 -1
  11. package/dist/validate/prebuilt/{build-AUGKXER3.js → build-ADF5EEE2.js} +52 -52
  12. package/dist/validate/prebuilt/bundle-main.js +223 -18
  13. package/dist/validate/prebuilt/chunk-VIHIF4G2.js +238 -0
  14. package/dist/validate/prebuilt/runtime.js +1 -1
  15. package/kit/files/.claude/skills/pix3-editor/SKILL.md +200 -0
  16. package/kit/files/.claude/skills/pix3-nodes/SKILL.md +13 -15
  17. package/kit/files/.claude/skills/pix3-nodes/reference.md +77 -71
  18. package/kit/files/.claude/skills/pix3-scene-format/SKILL.md +38 -41
  19. package/kit/files/.claude/skills/pix3-scene-format/pix3anim.md +185 -0
  20. package/kit/files/.claude/skills/pix3-scene-format/project-files.md +244 -0
  21. package/kit/files/.claude/skills/pix3-scene-format/reference.md +28 -16
  22. package/kit/files/.claude/skills/pix3-scripts/SKILL.md +34 -24
  23. package/kit/files/.claude/skills/pix3-scripts/reference.md +68 -141
  24. package/kit/files/.claude/skills/pix3-verify/SKILL.md +65 -326
  25. package/kit/files/.claude/skills/pix3-verify/reference.md +2 -14
  26. package/kit/files/AGENTS.md +76 -84
  27. package/kit/files/design/tests/bots/pix3-test-bot.d.ts +89 -0
  28. package/kit/kit.json +8 -5
  29. package/package.json +62 -54
  30. package/templates/2d/files/scenes/main.pix3scene +31 -0
  31. package/templates/2d/template.yaml +11 -0
  32. package/templates/3d/files/scenes/main.pix3scene +50 -0
  33. package/templates/3d/template.yaml +11 -0
  34. package/templates/base/files/README.md +39 -0
  35. package/templates/base/files/gitignore +11 -0
  36. package/templates/base/files/index.html +23 -0
  37. package/templates/base/files/package.json +28 -0
  38. package/templates/base/files/src/main.ts +3 -0
  39. package/templates/base/files/tsconfig.json +17 -0
  40. package/templates/base/files/vite.config.ts +10 -0
  41. package/dist/smoke/prebuilt/chunk-CBFYKDS7.mjs +0 -2
  42. package/dist/smoke/prebuilt/chunk-VJHJ5BCO.mjs +0 -239
  43. package/dist/validate/prebuilt/chunk-CFQFEI52.js +0 -239
  44. package/templates/empty-2d/files/README.md +0 -20
  45. package/templates/empty-2d/files/scenes/main.pix3scene +0 -68
  46. package/templates/empty-2d/files/sprites/pix3-logo.png +0 -0
  47. package/templates/empty-2d/template.yaml +0 -7
  48. package/templates/empty-3d/files/README.md +0 -20
  49. package/templates/empty-3d/files/scenes/main.pix3scene +0 -108
  50. package/templates/empty-3d/files/sprites/pix3-logo.png +0 -0
  51. package/templates/empty-3d/template.yaml +0 -7
  52. package/templates/idea-blank/files/README.md +0 -22
  53. package/templates/idea-blank/files/scenes/main.pix3scene +0 -37
  54. package/templates/idea-blank/template.yaml +0 -10
  55. package/templates/minigame-2d/files/README.md +0 -44
  56. package/templates/minigame-2d/files/design/tests/reachability.json +0 -1
  57. package/templates/minigame-2d/files/design/tests/routines/mute-music.json +0 -44
  58. package/templates/minigame-2d/files/scenes/main.pix3scene +0 -84
  59. package/templates/minigame-2d/files/scenes/menu.pix3scene +0 -118
  60. package/templates/minigame-2d/files/scenes/ui/settings-window.pix3scene +0 -138
  61. package/templates/minigame-2d/files/scripts/GameFlow.ts +0 -133
  62. package/templates/minigame-2d/files/scripts/MenuFlow.ts +0 -217
  63. package/templates/minigame-2d/files/scripts/SettingsWindow.ts +0 -198
  64. package/templates/minigame-2d/template.yaml +0 -10
  65. package/templates/playable-2d/files/README.md +0 -30
  66. package/templates/playable-2d/files/design/recipe.md +0 -83
  67. package/templates/playable-2d/files/design/tests/reachability.json +0 -4
  68. package/templates/playable-2d/files/design/tests/routines/intro-to-cta.json +0 -32
  69. package/templates/playable-2d/files/scenes/main.pix3scene +0 -111
  70. package/templates/playable-2d/files/scenes/ui/end-screen.pix3scene +0 -91
  71. package/templates/playable-2d/files/scenes/ui/intro.pix3scene +0 -65
  72. package/templates/playable-2d/files/scripts/CtaButton.ts +0 -55
  73. package/templates/playable-2d/files/scripts/GameFlow.ts +0 -256
  74. package/templates/playable-2d/files/sprites/pix3-logo.png +0 -0
  75. package/templates/playable-2d/template.yaml +0 -9
  76. package/templates/playable-3d/files/README.md +0 -32
  77. package/templates/playable-3d/files/design/recipe.md +0 -127
  78. package/templates/playable-3d/files/design/tests/reachability.json +0 -4
  79. package/templates/playable-3d/files/design/tests/routines/intro-to-cta.json +0 -32
  80. package/templates/playable-3d/files/scenes/main.pix3scene +0 -230
  81. package/templates/playable-3d/files/scenes/ui/end-screen.pix3scene +0 -91
  82. package/templates/playable-3d/files/scenes/ui/intro.pix3scene +0 -65
  83. package/templates/playable-3d/files/scripts/CtaButton.ts +0 -55
  84. package/templates/playable-3d/files/scripts/GameFlow.ts +0 -309
  85. package/templates/playable-3d/files/scripts/GameRules.ts +0 -290
  86. package/templates/playable-3d/files/scripts/ScoreHud.ts +0 -113
  87. package/templates/playable-3d/template.yaml +0 -9
  88. package/templates/recipe-arena-2d/files/README.md +0 -48
  89. package/templates/recipe-arena-2d/files/design/recipe.md +0 -127
  90. package/templates/recipe-arena-2d/files/design/tests/reachability.json +0 -4
  91. package/templates/recipe-arena-2d/files/design/tests/routines/terminal-retry.json +0 -23
  92. package/templates/recipe-arena-2d/files/scenes/main.pix3scene +0 -292
  93. package/templates/recipe-arena-2d/files/scenes/menu.pix3scene +0 -100
  94. package/templates/recipe-arena-2d/files/scenes/prefabs/hazard.pix3scene +0 -42
  95. package/templates/recipe-arena-2d/files/scenes/prefabs/pickup.pix3scene +0 -45
  96. package/templates/recipe-arena-2d/files/scenes/ui/result.pix3scene +0 -85
  97. package/templates/recipe-arena-2d/files/scripts/GameRules.ts +0 -368
  98. package/templates/recipe-arena-2d/files/scripts/MenuFlow.ts +0 -106
  99. package/templates/recipe-arena-2d/files/scripts/PlayerController.ts +0 -151
  100. package/templates/recipe-arena-2d/files/scripts/ScoreHud.ts +0 -94
  101. package/templates/recipe-arena-2d/files/scripts/Spawner.ts +0 -214
  102. package/templates/recipe-arena-2d/files/scripts/TouchRules.ts +0 -174
  103. package/templates/recipe-arena-2d/files/sprites/ph-avatar.png +0 -0
  104. package/templates/recipe-arena-2d/files/sprites/ph-hazard.png +0 -0
  105. package/templates/recipe-arena-2d/files/sprites/ph-pickup.png +0 -0
  106. package/templates/recipe-arena-2d/template.yaml +0 -10
  107. package/templates/recipe-blank-2d/files/README.md +0 -61
  108. package/templates/recipe-blank-2d/files/design/recipe.md +0 -148
  109. package/templates/recipe-blank-2d/files/design/tests/reachability.json +0 -4
  110. package/templates/recipe-blank-2d/files/design/tests/routines/restart.json +0 -27
  111. package/templates/recipe-blank-2d/files/design/tests/routines/terminal-retry.json +0 -23
  112. package/templates/recipe-blank-2d/files/scenes/main.pix3scene +0 -224
  113. package/templates/recipe-blank-2d/files/scenes/ui/result.pix3scene +0 -85
  114. package/templates/recipe-blank-2d/files/scripts/GameRules.ts +0 -350
  115. package/templates/recipe-blank-2d/files/scripts/ScoreHud.ts +0 -110
  116. package/templates/recipe-blank-2d/files/sprites/ph-bg.png +0 -0
  117. package/templates/recipe-blank-2d/files/sprites/ph-capsule.png +0 -0
  118. package/templates/recipe-blank-2d/files/sprites/ph-circle.png +0 -0
  119. package/templates/recipe-blank-2d/files/sprites/ph-orb.png +0 -0
  120. package/templates/recipe-blank-2d/files/sprites/ph-ring.png +0 -0
  121. package/templates/recipe-blank-2d/files/sprites/ph-star.png +0 -0
  122. package/templates/recipe-blank-2d/template.yaml +0 -10
  123. package/templates/recipe-bouncer-2d/files/README.md +0 -81
  124. package/templates/recipe-bouncer-2d/files/design/recipe.md +0 -146
  125. package/templates/recipe-bouncer-2d/files/design/tests/reachability.json +0 -4
  126. package/templates/recipe-bouncer-2d/files/design/tests/routines/terminal-retry.json +0 -23
  127. package/templates/recipe-bouncer-2d/files/scenes/main.pix3scene +0 -615
  128. package/templates/recipe-bouncer-2d/files/scenes/menu.pix3scene +0 -191
  129. package/templates/recipe-bouncer-2d/files/scenes/ui/result.pix3scene +0 -87
  130. package/templates/recipe-bouncer-2d/files/scripts/BallBody.ts +0 -298
  131. package/templates/recipe-bouncer-2d/files/scripts/GameRules.ts +0 -377
  132. package/templates/recipe-bouncer-2d/files/scripts/MenuFlow.ts +0 -109
  133. package/templates/recipe-bouncer-2d/files/scripts/PaddleController.ts +0 -112
  134. package/templates/recipe-bouncer-2d/files/scripts/ScoreHud.ts +0 -94
  135. package/templates/recipe-bouncer-2d/files/scripts/TouchRules.ts +0 -263
  136. package/templates/recipe-bouncer-2d/files/scripts/ball-collision.ts +0 -307
  137. package/templates/recipe-bouncer-2d/files/sprites/ph-ball.png +0 -0
  138. package/templates/recipe-bouncer-2d/files/sprites/ph-bg.png +0 -0
  139. package/templates/recipe-bouncer-2d/files/sprites/ph-bumper.png +0 -0
  140. package/templates/recipe-bouncer-2d/files/sprites/ph-paddle.png +0 -0
  141. package/templates/recipe-bouncer-2d/template.yaml +0 -10
  142. package/templates/recipe-grid-3d/files/README.md +0 -22
  143. package/templates/recipe-grid-3d/files/design/recipe.md +0 -110
  144. package/templates/recipe-grid-3d/files/scenes/main.pix3scene +0 -169
  145. package/templates/recipe-grid-3d/files/scenes/menu.pix3scene +0 -100
  146. package/templates/recipe-grid-3d/files/scenes/ui/result.pix3scene +0 -105
  147. package/templates/recipe-grid-3d/files/scripts/GridBoard.ts +0 -319
  148. package/templates/recipe-grid-3d/files/scripts/GridRules.ts +0 -278
  149. package/templates/recipe-grid-3d/files/scripts/MenuFlow.ts +0 -106
  150. package/templates/recipe-grid-3d/files/scripts/ScoreHud.ts +0 -94
  151. package/templates/recipe-grid-3d/template.yaml +0 -10
  152. package/templates/recipe-tapper-2d/files/README.md +0 -39
  153. package/templates/recipe-tapper-2d/files/design/recipe.md +0 -132
  154. package/templates/recipe-tapper-2d/files/design/tests/reachability.json +0 -4
  155. package/templates/recipe-tapper-2d/files/design/tests/routines/terminal-retry.json +0 -23
  156. package/templates/recipe-tapper-2d/files/scenes/main.pix3scene +0 -269
  157. package/templates/recipe-tapper-2d/files/scenes/menu.pix3scene +0 -100
  158. package/templates/recipe-tapper-2d/files/scenes/prefabs/hazard.pix3scene +0 -42
  159. package/templates/recipe-tapper-2d/files/scenes/prefabs/target.pix3scene +0 -45
  160. package/templates/recipe-tapper-2d/files/scenes/ui/result.pix3scene +0 -85
  161. package/templates/recipe-tapper-2d/files/scripts/GameRules.ts +0 -368
  162. package/templates/recipe-tapper-2d/files/scripts/MenuFlow.ts +0 -106
  163. package/templates/recipe-tapper-2d/files/scripts/ScoreHud.ts +0 -94
  164. package/templates/recipe-tapper-2d/files/scripts/Spawner.ts +0 -214
  165. package/templates/recipe-tapper-2d/files/scripts/TouchRules.ts +0 -192
  166. package/templates/recipe-tapper-2d/files/sprites/ph-hazard.png +0 -0
  167. package/templates/recipe-tapper-2d/files/sprites/ph-target.png +0 -0
  168. package/templates/recipe-tapper-2d/template.yaml +0 -10
@@ -1,113 +0,0 @@
1
- /**
2
- * ScoreHud — signal-driven HUD. Display only, no game logic.
3
- *
4
- * Listens on `sourceNode` (the node carrying `GameRules`) for:
5
- * `score-changed` (score)
6
- * `lives-changed` (lives, maxLives)
7
- * `time-changed` (seconds)
8
- *
9
- * If a HUD widget is missing from the scene it is quietly skipped, so you can
10
- * delete the timer or the lives bar without touching any script.
11
- *
12
- * With `GameRules.timeLimitSec` at 0 there is no clock, and `time-changed` carries
13
- * ELAPSED seconds instead — the label counts up. Set a limit and it counts down.
14
- *
15
- * Identical to the one the 2D blank recipe ships, down to the signal names: the HUD is
16
- * 2D in both, because a 3D game's score is still read on a flat overlay.
17
- */
18
- import { Bar2D, Label2D, Script, type PropertySchema } from '@pix3/runtime';
19
-
20
- export class ScoreHud extends Script {
21
- constructor(id: string, type: string) {
22
- super(id, type);
23
- this.config = {
24
- // Node that emits the HUD signals (the GameRules host).
25
- sourceNode: 'hud-root',
26
- scoreLabel: 'score-label',
27
- scoreFormat: 'SCORE {value}',
28
- timeLabel: 'time-label',
29
- timeFormat: '{value}',
30
- livesBar: 'lives-bar',
31
- };
32
- }
33
-
34
- static getPropertySchema(): PropertySchema {
35
- const str = (name: string, label: string, description?: string) => ({
36
- name,
37
- type: 'string' as const,
38
- ui: { label, group: 'HUD', description },
39
- getValue: (s: unknown) => (s as ScoreHud).config[name],
40
- setValue: (s: unknown, v: unknown) => {
41
- (s as ScoreHud).config[name] = typeof v === 'string' ? v : '';
42
- },
43
- });
44
-
45
- return {
46
- nodeType: 'ScoreHud',
47
- properties: [
48
- str(
49
- 'sourceNode',
50
- 'Source Node',
51
- 'Node that emits score-changed / lives-changed / time-changed'
52
- ),
53
- str('scoreLabel', 'Score Label'),
54
- str('scoreFormat', 'Score Format', '{value} is replaced by the score'),
55
- str('timeLabel', 'Time Label'),
56
- str('timeFormat', 'Time Format', '{value} is replaced by whole seconds'),
57
- str('livesBar', 'Lives Bar'),
58
- ],
59
- groups: { HUD: { label: 'HUD', expanded: true } },
60
- };
61
- }
62
-
63
- onStart(): void {
64
- const source = this.findNode(String(this.config.sourceNode ?? ''));
65
- if (!source) {
66
- console.warn(`[ScoreHud] Source node "${this.config.sourceNode}" not found.`);
67
- return;
68
- }
69
- source.connect('score-changed', this, (...args: unknown[]) =>
70
- this.setScore(Number(args[0]) || 0)
71
- );
72
- source.connect('time-changed', this, (...args: unknown[]) =>
73
- this.setTime(Number(args[0]) || 0)
74
- );
75
- source.connect('lives-changed', this, (...args: unknown[]) =>
76
- this.setLives(Number(args[0]) || 0, Number(args[1]) || 0)
77
- );
78
- // Ask for the opening values now that the listeners are live: GameRules
79
- // starts before this HUD (parent components tick before children).
80
- source.emit('hud-ready');
81
- }
82
-
83
- private setScore(score: number): void {
84
- const label = this.findNode(String(this.config.scoreLabel ?? ''));
85
- if (label instanceof Label2D) {
86
- label.setText(
87
- String(this.config.scoreFormat ?? '{value}').replace('{value}', String(Math.round(score)))
88
- );
89
- }
90
- }
91
-
92
- private setTime(seconds: number): void {
93
- const label = this.findNode(String(this.config.timeLabel ?? ''));
94
- if (label instanceof Label2D) {
95
- label.setText(
96
- String(this.config.timeFormat ?? '{value}').replace(
97
- '{value}',
98
- String(Math.max(0, Math.ceil(seconds)))
99
- )
100
- );
101
- }
102
- }
103
-
104
- private setLives(lives: number, maxLives: number): void {
105
- const bar = this.findNode(String(this.config.livesBar ?? ''));
106
- if (bar instanceof Bar2D) {
107
- if (maxLives > 0) {
108
- bar.maxValue = maxLives;
109
- }
110
- bar.setValue(lives);
111
- }
112
- }
113
- }
@@ -1,9 +0,0 @@
1
- id: playable-3d
2
- title: Playable 3D
3
- description: Portrait 3D playable-ad blank — tap-to-start intro (unlocks audio), gameplay placeholder, end screen with a store CTA button.
4
- # Flow recipe this template serves (see design/recipe.md).
5
- recipeId: recipe-scene-3d
6
- projectType: 3d
7
- targetPlatform: mobile
8
- viewport: { width: 1080, height: 1920 }
9
- order: 30
@@ -1,48 +0,0 @@
1
- # Arena 2D
2
-
3
- A playable skeleton: an avatar in a bounded field, two spawners dripping
4
- pickups and hazards, touch resolved into score or damage, lives + a timer, and a
5
- win/lose screen with RETRY / MENU. It plays as shipped — press play in
6
- `scenes/main.pix3scene` before changing anything.
7
-
8
- **`design/recipe.md` is the contract.** It lists the stable node ids, the
9
- tunables (with their ranges), the extension points and what must not be renamed.
10
- Read it first; tools and agents grep it.
11
-
12
- ## Layout
13
-
14
- ```
15
- scenes/menu.pix3scene entry / export scene — PLAY transitions to the game
16
- scenes/main.pix3scene the GAME (editor startup scene — iterate here)
17
- scenes/ui/result.pix3scene win/lose overlay — instanced into main hidden
18
- scenes/prefabs/*.pix3scene what the spawners instantiate
19
- scripts/PlayerController.ts how the avatar moves (swap this file whole)
20
- scripts/Spawner.ts timed instantiate + drift + despawn
21
- scripts/TouchRules.ts contact → semantic signals
22
- scripts/ScoreHud.ts signals → HUD widgets
23
- scripts/GameRules.ts score / lives / timer / win / lose / end flow
24
- scripts/MenuFlow.ts menu PLAY button
25
- sprites/ph-*.png near-white placeholders (tinted via core:tint)
26
- ```
27
-
28
- Full-screen UI lives in its own scene file, never inline in the game scene.
29
- `main.pix3scene` carries the end screen as a one-line `instance:` marked
30
- `visible: false` — that hides it in the **editor** only, so opening the project
31
- shows the game instead of a GAME OVER card. What play mode reads is
32
- `initiallyVisible`, authored in `scenes/ui/result.pix3scene`. Edit the overlay
33
- full-screen in its own tab (right-click → Open Prefab); tick the eye in
34
- `main.pix3scene` to preview it composited, and untick it before saving.
35
-
36
- ## The rule that keeps it workable
37
-
38
- One file, one responsibility. Each script is 70–140 lines on purpose: small
39
- files can be edited surgically; monoliths get rewritten wholesale and lose the
40
- changes that were already working. When you add a mechanic, add a script.
41
-
42
- Signals are the seam between them:
43
-
44
- ```
45
- TouchRules --touch-scored/touch-damaged--> GameRules --score-changed--> ScoreHud
46
- --lives-changed-->
47
- --time-changed-->
48
- ```
@@ -1,127 +0,0 @@
1
- # Recipe: recipe-arena-2d
2
-
3
- ## What this is
4
-
5
- An avatar in a bounded field; two spawners drip things in; touching a thing
6
- resolves through a group→outcome map into score or damage. Lives, timer,
7
- win/lose screen with RETRY / MENU. Playable before any edit — start from what
8
- runs. This is the **fallback recipe**: anything shaped like "steer something
9
- around a space while stuff comes at you" starts here.
10
-
11
- ## Node map
12
-
13
- | id | what |
14
- | --- | --- |
15
- | `game-root` | root; hosts `GameRules` + `TouchRules`; all HUD signals are emitted here |
16
- | `game-background` | full-screen `ColorRect2D` (palette background) |
17
- | `board` | the field (`Group2D`); its rect clamps the avatar |
18
- | `board-floor` | the field's visible plate |
19
- | `player` | avatar (`Sprite2D`), hosts `PlayerController` |
20
- | `spawner-pickups` / `spawner-hazards` | spawn bands, one `Spawner` each |
21
- | `hud` | `CanvasLayer2D` overlay, hosts `ScoreHud` |
22
- | `score-label` / `time-label` / `lives-bar` | HUD widgets, signal-driven only |
23
- | `menu-button` | back to the menu scene |
24
- | `result-overlay` / `result-label` / `retry-button` | end screen — authored in `scenes/ui/result.pix3scene`, instanced into `hud` with `visible: false` so the editor opens on the game; `initiallyVisible` and `GameRules` own it at run time |
25
-
26
- Signals: `TouchRules` emits `touch-scored` / `touch-damaged` on `game-root`;
27
- `GameRules` answers `score-changed`, `lives-changed`, `time-changed`,
28
- `game-won` / `game-lost`.
29
-
30
- ## Placeholders
31
-
32
- Near-white PNGs carrying a `core:tint` effect: set `effects[0].params.color` and
33
- the multiply gives you that palette colour.
34
-
35
- | role | file | node/prefab |
36
- | --- | --- | --- |
37
- | avatar | `sprites/ph-avatar.png` | `player` |
38
- | collectible | `sprites/ph-pickup.png` | `prefabs/pickup.pix3scene` |
39
- | threat | `sprites/ph-hazard.png` | `prefabs/hazard.pix3scene` |
40
-
41
- ## Tunables
42
-
43
- ```yaml
44
- tunables:
45
- playerMode: { node: player, component: "user:PlayerController", property: mode, default: pointer }
46
- playerSpeed: { node: player, component: "user:PlayerController", property: speed, min: 60, max: 2000, default: 620 }
47
- playerRadius: { node: player, component: "user:PlayerController", property: radius, min: 4, max: 400, default: 60 }
48
- pickupRate: { node: spawner-pickups, component: "user:Spawner", property: intervalSec, min: 0.05, max: 20, default: 1.0 }
49
- pickupMaxAlive: { node: spawner-pickups, component: "user:Spawner", property: maxAlive, min: 1, max: 200, default: 10 }
50
- pickupFallSpeed: { node: spawner-pickups, component: "user:Spawner", property: driftY, min: -3000, max: 3000, default: -300 }
51
- hazardRate: { node: spawner-hazards, component: "user:Spawner", property: intervalSec, min: 0.05, max: 20, default: 1.8 }
52
- hazardFallSpeed: { node: spawner-hazards, component: "user:Spawner", property: driftY, min: -3000, max: 3000, default: -380 }
53
- touchRadius: { node: game-root, component: "user:TouchRules", property: touchRadius, min: 4, max: 600, default: 62 }
54
- touchRules: { node: game-root, component: "user:TouchRules", property: rules, default: "pickup:score:1;hazard:damage:1" }
55
- winMode: { node: game-root, component: "user:GameRules", property: winMode, default: score }
56
- targetScore: { node: game-root, component: "user:GameRules", property: targetScore, min: 1, max: 99999, default: 12 }
57
- timeLimitSec: { node: game-root, component: "user:GameRules", property: timeLimitSec, min: 0, max: 600, default: 45 }
58
- startingLives: { node: game-root, component: "user:GameRules", property: startingLives, min: 1, max: 20, default: 3 }
59
- bgColor: { node: game-background, property: color, default: "#141a2e" }
60
- boardColor: { node: board-floor, property: color, default: "#1d2743" }
61
- ```
62
-
63
- `component` present → `set_component_property`; absent → `set_property`. The
64
- schema clamps out-of-range values rather than rejecting them.
65
-
66
- ## Extension points
67
-
68
- - **Different locomotion → replace `scripts/PlayerController.ts` wholesale.** It
69
- is the only file that reads input and writes `player.position`. A grid stepper,
70
- a jumper (velocity + gravity) or an auto-runner all drop in as long as the file
71
- still exports a `PlayerController` class and keeps the node inside `board`.
72
- Write it in ONE call — `fs_write` with `overwrite: true` and a `reason` (the
73
- size guard refuses a silent wholesale rewrite; this one is sanctioned).
74
- - **Trail / body behind the avatar.** Add a `segment` prefab (`Sprite2D` +
75
- `core:Hitbox2D` in a hazard group). In the locomotion script, on each completed
76
- step `scene.instantiate` a segment at the vacated position into a `segments`
77
- node and `queueFree` the tail once the trail exceeds its length. Self-collision
78
- then falls out of the existing rules — the segment's group is already in
79
- `touchRules`, so hitting your own trail costs a life with no new code. Grow the
80
- trail by skipping the tail free on a `touch-scored` frame.
81
- - **A third kind of thing.** Copy a prefab, give its `core:Hitbox2D` a new group,
82
- `create_node` + `add_component` a third `Spawner`, append `<group>:score:5` to
83
- `touchRules`. Chasers: a script on the prefab that steers toward `player`'s
84
- world position — contact is already punished. Spawn it under a spawner (the
85
- default `parentNode`) listed in `freezeNodes`: `finish()` freezes that whole
86
- subtree, so the chaser stops on game over. Parented elsewhere, it keeps
87
- crawling over the result card — list that parent in `freezeNodes` too.
88
- - **More juice.** `scene.juice.punchScale/shake/flash` + `scene.time.hitstop` are
89
- already called in `TouchRules`; raise the numbers there.
90
-
91
- ## Ending a run belongs to GameRules
92
-
93
- The result overlay and RETRY are **owned by `GameRules`**: it hides the overlay on
94
- start and keeps `retry-button` **disabled**, then `finish(won)` shows the overlay,
95
- writes the result text, enables RETRY and freezes the `freezeNodes` subtrees
96
- (every component on them and on what they spawned). For a custom
97
- win/lose condition call `finish(true|false)` on that component — do NOT show the
98
- overlay yourself. A hand-rolled ending leaves RETRY on screen with its handler bound
99
- and `enabled: false`, i.e. a button that can never be pressed (`game_observe` reports
100
- `control: { enabled: false }`; that is the tell).
101
-
102
- ## Do not touch
103
-
104
- - The node ids above (rename `name`, never `id`), and the signal names.
105
- - `visible: false` on the `result-overlay` instance in `main.pix3scene`. Tick the
106
- eye to preview the end screen over the field, but never save it on: it is what
107
- keeps every iteration from starting behind a GAME OVER card. Run-time
108
- visibility is `initiallyVisible` in `scenes/ui/result.pix3scene`, not this.
109
- - Component config on a **prefab instance** — the editor locks it. Prefabs carry
110
- fixed defaults only (their hitbox group); tunables live on plain nodes in
111
- `main.pix3scene`.
112
- - Do not import rapier: there is no rigidbody solver here and none is needed.
113
-
114
- ## Playtest contract
115
-
116
- terminalVisual: static
117
- terminalRestartRoutine: terminal-retry
118
-
119
- ## Verify
120
-
121
- 1. `play_start` on `scenes/main.pix3scene`.
122
- 2. `game_input` a pointer drag across the lower board — `player`'s position
123
- changes between two `game_observe` calls.
124
- 3. After ~3 s: children exist under `spawner-pickups`, and `score-label` climbs
125
- above `SCORE 0` once the avatar meets one.
126
- 4. Set `targetScore: 1`, replay, collect one → `result-overlay` visible with
127
- `YOU WIN!`; `retry-button` restarts the scene.
@@ -1,4 +0,0 @@
1
- {
2
- "version": 1,
3
- "proven": []
4
- }
@@ -1,23 +0,0 @@
1
- {
2
- "name": "terminal-retry",
3
- "description": "From the result screen, physically tap RETRY and check that play resumes.",
4
- "scope": "scenes/main.pix3scene",
5
- "params": {},
6
- "uses": ["result-overlay", "retry-button"],
7
- "steps": [
8
- {
9
- "type": "tap",
10
- "target": "retry-button",
11
- "holdMs": 200,
12
- "why": "A real pointer press proves the button is visible, enabled and reachable."
13
- },
14
- {
15
- "type": "wait",
16
- "frames": 30,
17
- "why": "Let the scene transition finish before reading the new game's phase."
18
- }
19
- ],
20
- "expect": [
21
- { "kind": "gameState", "path": "phase", "op": "eq", "value": "playing" }
22
- ]
23
- }
@@ -1,292 +0,0 @@
1
- # Pix3 Scene File (YAML)
2
- # Arena 2D — the GAME scene, and the editor's startup scene: this is the one to
3
- # iterate on and the one agents open and play directly. A full build boots the
4
- # MENU (see menu.pix3scene), whose PLAY button transitions here.
5
- #
6
- # Stable node ids (design/recipe.md is the contract — do not rename these):
7
- # game-root, game-background, board, player,
8
- # spawner-pickups, spawner-hazards,
9
- # hud, score-label, time-label, lives-bar, menu-button,
10
- # result-overlay, result-label, retry-button (scenes/ui/result.pix3scene)
11
- #
12
- # Full-screen UI lives in its own file under scenes/ui/ and is instanced here
13
- # hidden (`visible: false` = editor only), so opening this scene shows the game
14
- # rather than a menu or a GAME OVER card.
15
- #
16
- # 2D coordinates: origin at the canvas centre, X right, Y UP. Node order in the
17
- # tree is paint order (later/deeper draws on top).
18
- version: 1.0.0
19
- metadata:
20
- author: Pix3 Recipe
21
- description: Arena gameplay — spawners drop pickups and hazards into a bounded field
22
- root:
23
- - id: game-root
24
- type: Group2D
25
- name: Game Root
26
- properties:
27
- width: 1080
28
- height: 1920
29
- layout:
30
- enabled: true
31
- horizontalAlign: stretch
32
- verticalAlign: stretch
33
- components:
34
- - id: game-rules
35
- type: user:GameRules
36
- enabled: true
37
- config:
38
- winMode: score
39
- targetScore: 12
40
- timeLimitSec: 45
41
- startingLives: 3
42
- resultNode: result-overlay
43
- resultLabel: result-label
44
- winText: "YOU WIN!"
45
- loseText: "GAME OVER"
46
- retryButton: retry-button
47
- menuButton: menu-button
48
- menuScene: res://scenes/menu.pix3scene
49
- gameScene: res://scenes/main.pix3scene
50
- freezeNodes: spawner-pickups,spawner-hazards,player
51
- - id: touch-rules
52
- type: user:TouchRules
53
- enabled: true
54
- config:
55
- playerNode: player
56
- touchRadius: 62
57
- rules: "pickup:score:1;hazard:damage:1"
58
- consume: true
59
- scoreSound: ""
60
- damageSound: ""
61
- children:
62
- - id: game-background
63
- type: ColorRect2D
64
- name: Game Background
65
- properties:
66
- width: 1080
67
- height: 1920
68
- color: "#141a2e"
69
- transform:
70
- position: [0, 0]
71
- scale: [1, 1]
72
- rotation: 0
73
- layout:
74
- enabled: true
75
- horizontalAlign: stretch
76
- verticalAlign: stretch
77
- children: []
78
- - id: board
79
- type: Group2D
80
- name: Board
81
- properties:
82
- width: 980
83
- height: 1480
84
- transform:
85
- position: [0, -80]
86
- scale: [1, 1]
87
- rotation: 0
88
- children:
89
- - id: board-floor
90
- type: ColorRect2D
91
- name: Board Floor
92
- properties:
93
- width: 980
94
- height: 1480
95
- color: "#1d2743"
96
- transform:
97
- position: [0, 0]
98
- scale: [1, 1]
99
- rotation: 0
100
- children: []
101
- - id: spawner-pickups
102
- type: Group2D
103
- name: Spawner Pickups
104
- properties:
105
- width: 900
106
- height: 60
107
- transform:
108
- position: [0, 690]
109
- scale: [1, 1]
110
- rotation: 0
111
- components:
112
- - id: pickup-spawner
113
- type: user:Spawner
114
- enabled: true
115
- config:
116
- prefab: res://scenes/prefabs/pickup.pix3scene
117
- parentNode: ""
118
- intervalSec: 1.0
119
- intervalJitter: 0.35
120
- maxAlive: 10
121
- spawnWidth: 880
122
- spawnHeight: 60
123
- gridSnap: 0
124
- driftX: 0
125
- driftY: -300
126
- lifetimeSec: 7
127
- despawnRadius: 1450
128
- startDelaySec: 0.3
129
- children: []
130
- - id: spawner-hazards
131
- type: Group2D
132
- name: Spawner Hazards
133
- properties:
134
- width: 900
135
- height: 60
136
- transform:
137
- position: [0, 690]
138
- scale: [1, 1]
139
- rotation: 0
140
- components:
141
- - id: hazard-spawner
142
- type: user:Spawner
143
- enabled: true
144
- config:
145
- prefab: res://scenes/prefabs/hazard.pix3scene
146
- parentNode: ""
147
- intervalSec: 1.8
148
- intervalJitter: 0.6
149
- maxAlive: 8
150
- spawnWidth: 880
151
- spawnHeight: 60
152
- gridSnap: 0
153
- driftX: 0
154
- driftY: -380
155
- lifetimeSec: 6
156
- despawnRadius: 1450
157
- startDelaySec: 1.2
158
- children: []
159
- - id: player
160
- type: Sprite2D
161
- name: Player
162
- properties:
163
- texture: { type: 'texture', url: 'res://sprites/ph-avatar.png' }
164
- width: 120
165
- height: 120
166
- effects:
167
- - type: core:tint
168
- params: { color: "#ffffff", amount: 1 }
169
- transform:
170
- position: [0, -520]
171
- scale: [1, 1]
172
- rotation: 0
173
- components:
174
- - id: player-controller
175
- type: user:PlayerController
176
- enabled: true
177
- config:
178
- mode: pointer
179
- speed: 620
180
- radius: 60
181
- boundsNode: board
182
- deadZone: 4
183
- children: []
184
- - id: hud
185
- type: CanvasLayer2D
186
- name: HUD
187
- properties:
188
- width: 1080
189
- height: 1920
190
- layout:
191
- enabled: true
192
- horizontalAlign: stretch
193
- verticalAlign: stretch
194
- components:
195
- - id: score-hud
196
- type: user:ScoreHud
197
- enabled: true
198
- config:
199
- sourceNode: game-root
200
- scoreLabel: score-label
201
- scoreFormat: "SCORE {value}"
202
- timeLabel: time-label
203
- timeFormat: "{value}"
204
- livesBar: lives-bar
205
- children:
206
- - id: score-label
207
- type: Label2D
208
- name: Score Label
209
- properties:
210
- label: "SCORE 0"
211
- labelFontSize: 44
212
- labelColor: "#ffffff"
213
- labelAlign: left
214
- transform:
215
- position: [-340, 850]
216
- scale: [1, 1]
217
- rotation: 0
218
- layout:
219
- enabled: true
220
- horizontalAlign: left
221
- verticalAlign: top
222
- children: []
223
- - id: time-label
224
- type: Label2D
225
- name: Time Label
226
- properties:
227
- label: "45"
228
- labelFontSize: 44
229
- labelColor: "#f5ae39"
230
- labelAlign: center
231
- transform:
232
- position: [0, 850]
233
- scale: [1, 1]
234
- rotation: 0
235
- layout:
236
- enabled: true
237
- horizontalAlign: center
238
- verticalAlign: top
239
- children: []
240
- - id: lives-bar
241
- type: Bar2D
242
- name: Lives Bar
243
- properties:
244
- width: 240
245
- height: 28
246
- minValue: 0
247
- maxValue: 3
248
- value: 3
249
- barColor: "#ff4d6d"
250
- backBackgroundColor: "#2a3352"
251
- showBorder: false
252
- transform:
253
- position: [340, 855]
254
- scale: [1, 1]
255
- rotation: 0
256
- layout:
257
- enabled: true
258
- horizontalAlign: right
259
- verticalAlign: top
260
- children: []
261
- - id: menu-button
262
- type: Button2D
263
- name: Menu Button
264
- properties:
265
- width: 200
266
- height: 72
267
- label: "MENU"
268
- labelFontSize: 30
269
- labelColor: "#ffffff"
270
- backgroundColor: "#2a3352"
271
- hoverColor: "#3a446b"
272
- pressedColor: "#1d2743"
273
- buttonAction: back-to-menu
274
- transform:
275
- position: [-400, -880]
276
- scale: [1, 1]
277
- rotation: 0
278
- layout:
279
- enabled: true
280
- horizontalAlign: left
281
- verticalAlign: bottom
282
- children: []
283
- # Win/lose screen — authored in scenes/ui/result.pix3scene so this
284
- # scene opens on the GAME, not on a GAME OVER card. `visible: false`
285
- # hides it in the EDITOR only; `initiallyVisible` (in the overlay
286
- # file) is what play mode reads, and GameRules reveals it on the
287
- # win/lose transition. Tick the eye to preview it over the field.
288
- - id: result-overlay
289
- name: Result Overlay
290
- instance: res://scenes/ui/result.pix3scene
291
- properties:
292
- visible: false