@pix3/cli 1.6.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 (155) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +754 -0
  3. package/dist/index.js +494 -0
  4. package/dist/runtime-types.json +1 -0
  5. package/dist/smoke/prebuilt/build-7MIXIMSZ.mjs +948 -0
  6. package/dist/smoke/prebuilt/chunk-J2SJVMDO.mjs +2 -0
  7. package/dist/smoke/prebuilt/chunk-OJXQXIIQ.mjs +239 -0
  8. package/dist/smoke/prebuilt/chunk-P2CBRXPJ.mjs +4050 -0
  9. package/dist/smoke/prebuilt/runtime.mjs +2 -0
  10. package/dist/smoke/prebuilt/smoke-worker.mjs +3 -0
  11. package/dist/smoke/prebuilt/three.mjs +2 -0
  12. package/dist/smoke/prebuilt/tree-defaults.mjs +2 -0
  13. package/dist/validate/prebuilt/build-AUGKXER3.js +948 -0
  14. package/dist/validate/prebuilt/bundle-main.js +24 -0
  15. package/dist/validate/prebuilt/chunk-6ACT6FNV.js +4050 -0
  16. package/dist/validate/prebuilt/chunk-BS2OZM7U.js +239 -0
  17. package/dist/validate/prebuilt/runtime.js +2 -0
  18. package/dist/validate/prebuilt/three.js +2 -0
  19. package/kit/files/.claude/skills/pix3-nodes/SKILL.md +210 -0
  20. package/kit/files/.claude/skills/pix3-nodes/reference.md +1331 -0
  21. package/kit/files/.claude/skills/pix3-scene-format/SKILL.md +235 -0
  22. package/kit/files/.claude/skills/pix3-scene-format/reference.md +176 -0
  23. package/kit/files/.claude/skills/pix3-scripts/SKILL.md +281 -0
  24. package/kit/files/.claude/skills/pix3-scripts/reference.md +638 -0
  25. package/kit/files/.claude/skills/pix3-verify/SKILL.md +386 -0
  26. package/kit/files/.claude/skills/pix3-verify/reference.md +21 -0
  27. package/kit/files/AGENTS.md +203 -0
  28. package/kit/files/CLAUDE.md +1 -0
  29. package/kit/kit.json +24 -0
  30. package/package.json +54 -0
  31. package/templates/empty-2d/files/README.md +20 -0
  32. package/templates/empty-2d/files/scenes/main.pix3scene +68 -0
  33. package/templates/empty-2d/files/sprites/pix3-logo.png +0 -0
  34. package/templates/empty-2d/template.yaml +7 -0
  35. package/templates/empty-3d/files/README.md +20 -0
  36. package/templates/empty-3d/files/scenes/main.pix3scene +108 -0
  37. package/templates/empty-3d/files/sprites/pix3-logo.png +0 -0
  38. package/templates/empty-3d/template.yaml +7 -0
  39. package/templates/idea-blank/files/README.md +22 -0
  40. package/templates/idea-blank/files/scenes/main.pix3scene +37 -0
  41. package/templates/idea-blank/template.yaml +10 -0
  42. package/templates/minigame-2d/files/README.md +44 -0
  43. package/templates/minigame-2d/files/design/tests/reachability.json +1 -0
  44. package/templates/minigame-2d/files/design/tests/routines/mute-music.json +44 -0
  45. package/templates/minigame-2d/files/scenes/main.pix3scene +84 -0
  46. package/templates/minigame-2d/files/scenes/menu.pix3scene +118 -0
  47. package/templates/minigame-2d/files/scenes/ui/settings-window.pix3scene +138 -0
  48. package/templates/minigame-2d/files/scripts/GameFlow.ts +133 -0
  49. package/templates/minigame-2d/files/scripts/MenuFlow.ts +217 -0
  50. package/templates/minigame-2d/files/scripts/SettingsWindow.ts +198 -0
  51. package/templates/minigame-2d/template.yaml +10 -0
  52. package/templates/playable-2d/files/README.md +30 -0
  53. package/templates/playable-2d/files/design/recipe.md +83 -0
  54. package/templates/playable-2d/files/design/tests/reachability.json +4 -0
  55. package/templates/playable-2d/files/design/tests/routines/intro-to-cta.json +32 -0
  56. package/templates/playable-2d/files/scenes/main.pix3scene +111 -0
  57. package/templates/playable-2d/files/scenes/ui/end-screen.pix3scene +91 -0
  58. package/templates/playable-2d/files/scenes/ui/intro.pix3scene +65 -0
  59. package/templates/playable-2d/files/scripts/CtaButton.ts +55 -0
  60. package/templates/playable-2d/files/scripts/GameFlow.ts +256 -0
  61. package/templates/playable-2d/files/sprites/pix3-logo.png +0 -0
  62. package/templates/playable-2d/template.yaml +9 -0
  63. package/templates/playable-3d/files/README.md +32 -0
  64. package/templates/playable-3d/files/design/recipe.md +127 -0
  65. package/templates/playable-3d/files/design/tests/reachability.json +4 -0
  66. package/templates/playable-3d/files/design/tests/routines/intro-to-cta.json +32 -0
  67. package/templates/playable-3d/files/scenes/main.pix3scene +230 -0
  68. package/templates/playable-3d/files/scenes/ui/end-screen.pix3scene +91 -0
  69. package/templates/playable-3d/files/scenes/ui/intro.pix3scene +65 -0
  70. package/templates/playable-3d/files/scripts/CtaButton.ts +55 -0
  71. package/templates/playable-3d/files/scripts/GameFlow.ts +309 -0
  72. package/templates/playable-3d/files/scripts/GameRules.ts +290 -0
  73. package/templates/playable-3d/files/scripts/ScoreHud.ts +113 -0
  74. package/templates/playable-3d/template.yaml +9 -0
  75. package/templates/recipe-arena-2d/files/README.md +48 -0
  76. package/templates/recipe-arena-2d/files/design/recipe.md +127 -0
  77. package/templates/recipe-arena-2d/files/design/tests/reachability.json +4 -0
  78. package/templates/recipe-arena-2d/files/design/tests/routines/terminal-retry.json +23 -0
  79. package/templates/recipe-arena-2d/files/scenes/main.pix3scene +292 -0
  80. package/templates/recipe-arena-2d/files/scenes/menu.pix3scene +100 -0
  81. package/templates/recipe-arena-2d/files/scenes/prefabs/hazard.pix3scene +42 -0
  82. package/templates/recipe-arena-2d/files/scenes/prefabs/pickup.pix3scene +45 -0
  83. package/templates/recipe-arena-2d/files/scenes/ui/result.pix3scene +85 -0
  84. package/templates/recipe-arena-2d/files/scripts/GameRules.ts +368 -0
  85. package/templates/recipe-arena-2d/files/scripts/MenuFlow.ts +106 -0
  86. package/templates/recipe-arena-2d/files/scripts/PlayerController.ts +151 -0
  87. package/templates/recipe-arena-2d/files/scripts/ScoreHud.ts +94 -0
  88. package/templates/recipe-arena-2d/files/scripts/Spawner.ts +214 -0
  89. package/templates/recipe-arena-2d/files/scripts/TouchRules.ts +174 -0
  90. package/templates/recipe-arena-2d/files/sprites/ph-avatar.png +0 -0
  91. package/templates/recipe-arena-2d/files/sprites/ph-hazard.png +0 -0
  92. package/templates/recipe-arena-2d/files/sprites/ph-pickup.png +0 -0
  93. package/templates/recipe-arena-2d/template.yaml +10 -0
  94. package/templates/recipe-blank-2d/files/README.md +61 -0
  95. package/templates/recipe-blank-2d/files/design/recipe.md +148 -0
  96. package/templates/recipe-blank-2d/files/design/tests/reachability.json +4 -0
  97. package/templates/recipe-blank-2d/files/design/tests/routines/restart.json +27 -0
  98. package/templates/recipe-blank-2d/files/design/tests/routines/terminal-retry.json +23 -0
  99. package/templates/recipe-blank-2d/files/scenes/main.pix3scene +224 -0
  100. package/templates/recipe-blank-2d/files/scenes/ui/result.pix3scene +85 -0
  101. package/templates/recipe-blank-2d/files/scripts/GameRules.ts +350 -0
  102. package/templates/recipe-blank-2d/files/scripts/ScoreHud.ts +110 -0
  103. package/templates/recipe-blank-2d/files/sprites/ph-bg.png +0 -0
  104. package/templates/recipe-blank-2d/files/sprites/ph-capsule.png +0 -0
  105. package/templates/recipe-blank-2d/files/sprites/ph-circle.png +0 -0
  106. package/templates/recipe-blank-2d/files/sprites/ph-orb.png +0 -0
  107. package/templates/recipe-blank-2d/files/sprites/ph-ring.png +0 -0
  108. package/templates/recipe-blank-2d/files/sprites/ph-star.png +0 -0
  109. package/templates/recipe-blank-2d/template.yaml +10 -0
  110. package/templates/recipe-bouncer-2d/files/README.md +81 -0
  111. package/templates/recipe-bouncer-2d/files/design/recipe.md +146 -0
  112. package/templates/recipe-bouncer-2d/files/design/tests/reachability.json +4 -0
  113. package/templates/recipe-bouncer-2d/files/design/tests/routines/terminal-retry.json +23 -0
  114. package/templates/recipe-bouncer-2d/files/scenes/main.pix3scene +615 -0
  115. package/templates/recipe-bouncer-2d/files/scenes/menu.pix3scene +191 -0
  116. package/templates/recipe-bouncer-2d/files/scenes/ui/result.pix3scene +87 -0
  117. package/templates/recipe-bouncer-2d/files/scripts/BallBody.ts +298 -0
  118. package/templates/recipe-bouncer-2d/files/scripts/GameRules.ts +377 -0
  119. package/templates/recipe-bouncer-2d/files/scripts/MenuFlow.ts +109 -0
  120. package/templates/recipe-bouncer-2d/files/scripts/PaddleController.ts +112 -0
  121. package/templates/recipe-bouncer-2d/files/scripts/ScoreHud.ts +94 -0
  122. package/templates/recipe-bouncer-2d/files/scripts/TouchRules.ts +263 -0
  123. package/templates/recipe-bouncer-2d/files/scripts/ball-collision.ts +307 -0
  124. package/templates/recipe-bouncer-2d/files/sprites/ph-ball.png +0 -0
  125. package/templates/recipe-bouncer-2d/files/sprites/ph-bg.png +0 -0
  126. package/templates/recipe-bouncer-2d/files/sprites/ph-bumper.png +0 -0
  127. package/templates/recipe-bouncer-2d/files/sprites/ph-paddle.png +0 -0
  128. package/templates/recipe-bouncer-2d/template.yaml +10 -0
  129. package/templates/recipe-grid-3d/files/README.md +22 -0
  130. package/templates/recipe-grid-3d/files/design/recipe.md +110 -0
  131. package/templates/recipe-grid-3d/files/scenes/main.pix3scene +169 -0
  132. package/templates/recipe-grid-3d/files/scenes/menu.pix3scene +100 -0
  133. package/templates/recipe-grid-3d/files/scenes/ui/result.pix3scene +105 -0
  134. package/templates/recipe-grid-3d/files/scripts/GridBoard.ts +319 -0
  135. package/templates/recipe-grid-3d/files/scripts/GridRules.ts +278 -0
  136. package/templates/recipe-grid-3d/files/scripts/MenuFlow.ts +106 -0
  137. package/templates/recipe-grid-3d/files/scripts/ScoreHud.ts +94 -0
  138. package/templates/recipe-grid-3d/template.yaml +10 -0
  139. package/templates/recipe-tapper-2d/files/README.md +39 -0
  140. package/templates/recipe-tapper-2d/files/design/recipe.md +132 -0
  141. package/templates/recipe-tapper-2d/files/design/tests/reachability.json +4 -0
  142. package/templates/recipe-tapper-2d/files/design/tests/routines/terminal-retry.json +23 -0
  143. package/templates/recipe-tapper-2d/files/scenes/main.pix3scene +269 -0
  144. package/templates/recipe-tapper-2d/files/scenes/menu.pix3scene +100 -0
  145. package/templates/recipe-tapper-2d/files/scenes/prefabs/hazard.pix3scene +42 -0
  146. package/templates/recipe-tapper-2d/files/scenes/prefabs/target.pix3scene +45 -0
  147. package/templates/recipe-tapper-2d/files/scenes/ui/result.pix3scene +85 -0
  148. package/templates/recipe-tapper-2d/files/scripts/GameRules.ts +368 -0
  149. package/templates/recipe-tapper-2d/files/scripts/MenuFlow.ts +106 -0
  150. package/templates/recipe-tapper-2d/files/scripts/ScoreHud.ts +94 -0
  151. package/templates/recipe-tapper-2d/files/scripts/Spawner.ts +214 -0
  152. package/templates/recipe-tapper-2d/files/scripts/TouchRules.ts +192 -0
  153. package/templates/recipe-tapper-2d/files/sprites/ph-hazard.png +0 -0
  154. package/templates/recipe-tapper-2d/files/sprites/ph-target.png +0 -0
  155. package/templates/recipe-tapper-2d/template.yaml +10 -0
@@ -0,0 +1,146 @@
1
+ # Recipe: recipe-bouncer-2d
2
+
3
+ ## What this is
4
+
5
+ A ball under gravity bounces off walls, bumpers and a paddle you steer; hits score, the
6
+ bottom drain costs a life. Playable, neon-lit and audible before any edit.
7
+
8
+ Ball physics lives in the project: `BallBody` runs the swept solver (`ball-collision.ts`)
9
+ over colliders rebuilt each frame from the marker nodes' **live world transforms**, so
10
+ a rotated flipper works for free. No `core:Hitbox2D` on the ball, no rapier. (The
11
+ engine's own `scene.physics2d` / `core:PhysicsBody2D` solver exists for games that grow
12
+ many interacting bodies — do not put both on the same ball.)
13
+
14
+ The paddle is optional: for an idle pinball, hide it and make the drain relaunch
15
+ instead of costing a life — the bumpers and the `ball-hit` juice carry the game.
16
+
17
+ The look: `PostProcess` (bloom + vignette) over bright accents, a tinted gradient
18
+ sprite behind the board, `Label2D` glow on the HUD (drawn after post — it glows on
19
+ canvas, never blooms).
20
+
21
+ ## Node map
22
+
23
+ | id | what |
24
+ | --- | --- |
25
+ | `post-fx` | `PostProcess` (`affect2D`): bloom + vignette |
26
+ | `game-root` | root; hosts `GameRules` + `TouchRules`; emits the HUD signals |
27
+ | `game-background` | full-screen `ColorRect2D`, the darkest colour |
28
+ | `bg-glow` | full-screen `Sprite2D`, the tinted gradient over it |
29
+ | `board` | playfield `Group2D`; clamps the paddle |
30
+ | `board-floor` | the plate, translucent so the glow shows through |
31
+ | `board-trim` | `trim-left`/`-right`/`-top` + `trim-drain`: DECOR, not colliders |
32
+ | `walls` → `wall-left`/`-right`/`-top` | box colliders, kind `wall` |
33
+ | `bumpers` → `bumper-a`/`-b`/`-c` | circle colliders, kind `bumper`; each a `Group2D` of `*-halo` + `*-core` rings |
34
+ | `paddles` → `paddle` | box collider, kind `paddle`; `PaddleController` |
35
+ | `drain` | trigger box, kind `drain` — entering it loses a ball |
36
+ | `ball` | hosts `BallBody` |
37
+ | `hud` | `CanvasLayer2D` + `ScoreHud`: `score-label`, `time-label`, `lives-bar`, `menu-button` |
38
+ | `result-overlay` / `result-label` / `retry-button` | end screen in `scenes/ui/result.pix3scene`, instanced into main hidden (`visible: false` is editor-only) |
39
+
40
+ `menu.pix3scene`: `menu-post-fx`, `menu-root`, `menu-background`, `menu-bg-glow`,
41
+ `title-label` (title patched in), `title-rule`, `subtitle-label`, `play-button`.
42
+
43
+ Signals: `ball` emits `ball-hit` (kind, nodeId, speed, x, y — world) and `ball-drained`;
44
+ `TouchRules` re-emits `touch-scored` / `touch-damaged` on `game-root`, where `GameRules`
45
+ answers `score-changed`, `lives-changed`, `time-changed`, `game-won` / `game-lost`.
46
+
47
+ ## Placeholders
48
+
49
+ Near-white PNGs carrying a `core:tint`: set `effects[0].params.color`.
50
+
51
+ | role | file | node |
52
+ | --- | --- | --- |
53
+ | background | `sprites/ph-bg.png` | `bg-glow`, `menu-bg-glow` (radial gradient) |
54
+ | avatar | `sprites/ph-ball.png` | `ball` |
55
+ | player | `sprites/ph-paddle.png` | `paddle` |
56
+ | collectible | `sprites/ph-bumper.png` | `bumper-*-core` / `*-halo` (a ring) |
57
+
58
+ ## Tunables
59
+
60
+ ```yaml
61
+ tunables:
62
+ gravity: { node: ball, component: "user:BallBody", property: gravity }
63
+ maxSpeed: { node: ball, component: "user:BallBody", property: maxSpeed }
64
+ minSpeed: { node: ball, component: "user:BallBody", property: minSpeed }
65
+ ballRadius: { node: ball, component: "user:BallBody", property: radius }
66
+ launchSpeed: { node: ball, component: "user:BallBody", property: launchSpeed }
67
+ launchAngleDeg: { node: ball, component: "user:BallBody", property: launchAngleDeg }
68
+ wallBounce: { node: ball, component: "user:BallBody", property: wallRestitution }
69
+ paddleBounce: { node: ball, component: "user:BallBody", property: paddleRestitution }
70
+ bumperBounce: { node: ball, component: "user:BallBody", property: bumperRestitution }
71
+ paddleMode: { node: paddle, component: "user:PaddleController", property: mode, default: pointer }
72
+ paddleSpeed: { node: paddle, component: "user:PaddleController", property: speed }
73
+ paddleHalfWidth: { node: paddle, component: "user:PaddleController", property: halfWidth }
74
+ targetScore: { node: game-root, component: "user:GameRules", property: targetScore }
75
+ startingLives: { node: game-root, component: "user:GameRules", property: startingLives }
76
+ touchRules: { node: game-root, component: "user:TouchRules", property: rules, default: "bumper:score:100;paddle:score:10;drain:damage:1" }
77
+ punchAmount: { node: game-root, component: "user:TouchRules", property: punchAmount }
78
+ hitstopMs: { node: game-root, component: "user:TouchRules", property: hitstopMs }
79
+ burstCount: { node: game-root, component: "user:TouchRules", property: burstCount }
80
+ sfxEnabled: { node: game-root, component: "user:TouchRules", property: sfxEnabled }
81
+ bgColor: { node: game-background, property: color, default: "#05030f" }
82
+ boardColor: { node: board-floor, property: color, default: "#120a33" }
83
+ bloomIntensity: { node: post-fx, property: bloomIntensity, min: 0, max: 3, default: 0.9 }
84
+ menuBloomIntensity: { node: menu-post-fx, property: bloomIntensity, min: 0, max: 3, default: 0.7 }
85
+ ```
86
+
87
+ `component` present → `set_component_property`, absent → `set_property`. Current values
88
+ live in the scene; each script's schema clamps on write. Bloom lifts only what is
89
+ *already* bright — brighten the colour or lower `bloomThreshold` (0.58), don't raise
90
+ intensity. A pastel / light ground is the reverse: **raise** it (~0.9) or everything blooms.
91
+
92
+ ## Extension points
93
+
94
+ - **Pinball flippers.** Two boxes **directly** under `paddles` (`flipper-left`,
95
+ `flipper-right`); swap `PaddleController` for a flipper script. `BallBody` reads each
96
+ direct child as a box centred on the node (a pivot `Group2D` there would itself be
97
+ the collider), so pivot in code: fix each flipper's **outer** end near its wall, tip
98
+ toward the centre, and every frame put the centre at `pivot ± (width/2)(cos θ, sin θ)`.
99
+ Holding `Key_ArrowLeft`/`Key_ArrowRight` lerps `rotation.z` −25° → +35° in ~0.06 s
100
+ (the right flipper mirrored: +25° → −35°); release lerps back. Add inlane guides
101
+ (boxes under `walls` from each side wall to a flipper's outer end), or the ball
102
+ drains around the flippers.
103
+ - **Juice is one-liners — add it WITH the mechanic, never as a later pass.**
104
+ `scene.audio.sfx('score')`, `scene.juice.burst({x, y})`,
105
+ `floatText('+100', { at: node })`, `punchScale`/`shake`/`flash`,
106
+ `scene.time.hitstop(50)` (on contact START, not every frame),
107
+ `core:BurstOnSignal`/`core:SfxOnSignal`. `TouchRules` already calls them on
108
+ contact — copy the line next to your mechanic, in board space.
109
+ - **More bumpers.** Duplicate a `bumpers` child (radius = width/2 × world scale) —
110
+ punch, particles and score already ride `ball-hit`.
111
+ - **Brick field (arkanoid).** Bricks under `walls` with ids `brick-*`; in `TouchRules`
112
+ `queueFree` the hit node when `kind === 'wall'` and the id matches.
113
+
114
+ Replacing a script named above is expected — `fs_write` with `overwrite: true` + `reason`.
115
+
116
+ ## Do not touch
117
+
118
+ - **Ending a run is `GameRules`' job**: `finish(won)` shows `result-overlay`, plays the
119
+ jingle, enables the initially-disabled `retry-button` and freezes gameplay. Call it
120
+ for custom endings — showing the overlay yourself leaves RETRY unpressable.
121
+ - The node ids above (rename `name`, never `id`), and the signal names.
122
+ - The collider grouping: `walls`/`paddles` children are boxes, `bumpers` children
123
+ circles, `drain` a trigger. Moving a node between them changes its physics, and a
124
+ decoration dropped into one becomes a collider.
125
+ - `post-fx` / `menu-post-fx`: keep the ids and `affect2D: true` — without it a 2D-only
126
+ scene gets no effects. Tune the values, don't delete the nodes.
127
+ - No `core:Hitbox2D` on the ball or walls (axis-aligned overlap would disagree with the
128
+ swept solver), and no rapier.
129
+
130
+ ## Playtest contract
131
+
132
+ terminalVisual: static
133
+ terminalRestartRoutine: terminal-retry
134
+
135
+ ## Verify
136
+
137
+ 1. `play_start` on `scenes/main.pix3scene`.
138
+ 2. `game_observe` twice ~0.5 s apart: `ball` moves and stays inside the board
139
+ (|x| < 470, y > −800 board space).
140
+ 3. **The core promise: the defender keeps the ball in play.** Whatever guards the
141
+ drain (paddle, flippers, …) must cover or funnel every downward path, never let the
142
+ ball round its open sides. Prove a save with `game_input`: a driven run outlives an
143
+ unattended one, and an unattended one survives several seconds (the stage idles
144
+ between turns, so a board that loses itself instantly reads as broken).
145
+ 4. Let the ball drain: `lives-bar` drops one and it relaunches after `resetDelaySec`;
146
+ three drains → `result-overlay` with `GAME OVER`.
@@ -0,0 +1,4 @@
1
+ {
2
+ "version": 1,
3
+ "proven": []
4
+ }
@@ -0,0 +1,23 @@
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
+ }