@pix3/cli 1.6.0 → 1.6.2
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/LICENSE +201 -201
- package/README.md +788 -754
- package/dist/index.js +208 -203
- package/dist/runtime-types.json +1 -1
- package/dist/smoke/prebuilt/{chunk-J2SJVMDO.mjs → chunk-7MZGPAUK.mjs} +1 -1
- package/dist/smoke/prebuilt/chunk-7WOFGLW3.mjs +239 -0
- package/dist/smoke/prebuilt/runtime.mjs +1 -1
- package/dist/smoke/prebuilt/smoke-worker.mjs +1 -1
- package/dist/smoke/prebuilt/tree-defaults.mjs +1 -1
- package/dist/validate/prebuilt/bundle-main.js +13 -13
- package/dist/validate/prebuilt/chunk-HKG6YI6Y.js +239 -0
- package/dist/validate/prebuilt/runtime.js +1 -1
- package/kit/files/.claude/skills/pix3-nodes/SKILL.md +210 -210
- package/kit/files/.claude/skills/pix3-nodes/reference.md +32 -24
- package/kit/files/.claude/skills/pix3-scene-format/SKILL.md +238 -235
- package/kit/files/.claude/skills/pix3-scene-format/reference.md +28 -28
- package/kit/files/.claude/skills/pix3-scripts/SKILL.md +285 -281
- package/kit/files/.claude/skills/pix3-scripts/reference.md +69 -52
- package/kit/files/.claude/skills/pix3-verify/SKILL.md +298 -256
- package/kit/files/.claude/skills/pix3-verify/reference.md +21 -21
- package/kit/files/AGENTS.md +203 -203
- package/kit/files/CLAUDE.md +1 -1
- package/kit/kit.json +2 -2
- package/package.json +54 -54
- package/templates/empty-2d/files/README.md +20 -20
- package/templates/empty-2d/files/scenes/main.pix3scene +68 -68
- package/templates/empty-2d/template.yaml +7 -7
- package/templates/empty-3d/files/README.md +20 -20
- package/templates/empty-3d/files/scenes/main.pix3scene +108 -108
- package/templates/empty-3d/template.yaml +7 -7
- package/templates/idea-blank/files/README.md +22 -22
- package/templates/idea-blank/files/scenes/main.pix3scene +37 -37
- package/templates/idea-blank/template.yaml +10 -10
- package/templates/minigame-2d/files/README.md +44 -44
- package/templates/minigame-2d/files/design/tests/reachability.json +1 -1
- package/templates/minigame-2d/files/design/tests/routines/mute-music.json +44 -44
- package/templates/minigame-2d/files/scenes/main.pix3scene +84 -84
- package/templates/minigame-2d/files/scenes/menu.pix3scene +118 -118
- package/templates/minigame-2d/files/scenes/ui/settings-window.pix3scene +138 -138
- package/templates/minigame-2d/files/scripts/GameFlow.ts +133 -133
- package/templates/minigame-2d/files/scripts/MenuFlow.ts +217 -217
- package/templates/minigame-2d/files/scripts/SettingsWindow.ts +198 -198
- package/templates/minigame-2d/template.yaml +10 -10
- package/templates/playable-2d/files/README.md +30 -30
- package/templates/playable-2d/files/design/recipe.md +83 -83
- package/templates/playable-2d/files/design/tests/reachability.json +4 -4
- package/templates/playable-2d/files/design/tests/routines/intro-to-cta.json +32 -32
- package/templates/playable-2d/files/scenes/main.pix3scene +111 -111
- package/templates/playable-2d/files/scenes/ui/end-screen.pix3scene +91 -91
- package/templates/playable-2d/files/scenes/ui/intro.pix3scene +65 -65
- package/templates/playable-2d/files/scripts/GameFlow.ts +256 -256
- package/templates/playable-2d/template.yaml +9 -9
- package/templates/playable-3d/files/README.md +32 -32
- package/templates/playable-3d/files/design/recipe.md +127 -127
- package/templates/playable-3d/files/design/tests/reachability.json +4 -4
- package/templates/playable-3d/files/design/tests/routines/intro-to-cta.json +32 -32
- package/templates/playable-3d/files/scenes/main.pix3scene +230 -230
- package/templates/playable-3d/files/scenes/ui/end-screen.pix3scene +91 -91
- package/templates/playable-3d/files/scenes/ui/intro.pix3scene +65 -65
- package/templates/playable-3d/files/scripts/GameFlow.ts +309 -309
- package/templates/playable-3d/files/scripts/GameRules.ts +290 -290
- package/templates/playable-3d/files/scripts/ScoreHud.ts +113 -113
- package/templates/playable-3d/template.yaml +9 -9
- package/templates/recipe-arena-2d/files/README.md +48 -48
- package/templates/recipe-arena-2d/files/design/recipe.md +127 -127
- package/templates/recipe-arena-2d/files/scenes/main.pix3scene +292 -292
- package/templates/recipe-arena-2d/files/scenes/menu.pix3scene +100 -100
- package/templates/recipe-arena-2d/files/scenes/prefabs/hazard.pix3scene +42 -42
- package/templates/recipe-arena-2d/files/scenes/prefabs/pickup.pix3scene +45 -45
- package/templates/recipe-arena-2d/files/scenes/ui/result.pix3scene +85 -85
- package/templates/recipe-arena-2d/files/scripts/GameRules.ts +368 -368
- package/templates/recipe-arena-2d/files/scripts/MenuFlow.ts +106 -106
- package/templates/recipe-arena-2d/files/scripts/PlayerController.ts +151 -151
- package/templates/recipe-arena-2d/files/scripts/ScoreHud.ts +94 -94
- package/templates/recipe-arena-2d/files/scripts/Spawner.ts +214 -214
- package/templates/recipe-arena-2d/files/scripts/TouchRules.ts +174 -174
- package/templates/recipe-arena-2d/template.yaml +10 -10
- package/templates/recipe-blank-2d/files/README.md +61 -61
- package/templates/recipe-blank-2d/files/design/recipe.md +148 -148
- package/templates/recipe-blank-2d/files/design/tests/reachability.json +4 -4
- package/templates/recipe-blank-2d/files/design/tests/routines/restart.json +27 -27
- package/templates/recipe-blank-2d/files/scenes/main.pix3scene +224 -224
- package/templates/recipe-blank-2d/files/scenes/ui/result.pix3scene +85 -85
- package/templates/recipe-blank-2d/files/scripts/GameRules.ts +350 -350
- package/templates/recipe-blank-2d/files/scripts/ScoreHud.ts +110 -110
- package/templates/recipe-blank-2d/template.yaml +10 -10
- package/templates/recipe-bouncer-2d/files/README.md +81 -81
- package/templates/recipe-bouncer-2d/files/design/recipe.md +146 -146
- package/templates/recipe-bouncer-2d/files/scenes/main.pix3scene +615 -615
- package/templates/recipe-bouncer-2d/files/scenes/menu.pix3scene +191 -191
- package/templates/recipe-bouncer-2d/files/scenes/ui/result.pix3scene +87 -87
- package/templates/recipe-bouncer-2d/files/scripts/BallBody.ts +298 -298
- package/templates/recipe-bouncer-2d/files/scripts/GameRules.ts +377 -377
- package/templates/recipe-bouncer-2d/files/scripts/MenuFlow.ts +109 -109
- package/templates/recipe-bouncer-2d/files/scripts/PaddleController.ts +112 -112
- package/templates/recipe-bouncer-2d/files/scripts/ScoreHud.ts +94 -94
- package/templates/recipe-bouncer-2d/files/scripts/TouchRules.ts +263 -263
- package/templates/recipe-bouncer-2d/files/scripts/ball-collision.ts +307 -307
- package/templates/recipe-bouncer-2d/template.yaml +10 -10
- package/templates/recipe-grid-3d/files/README.md +22 -22
- package/templates/recipe-grid-3d/files/design/recipe.md +110 -110
- package/templates/recipe-grid-3d/files/scenes/main.pix3scene +169 -169
- package/templates/recipe-grid-3d/files/scenes/menu.pix3scene +100 -100
- package/templates/recipe-grid-3d/files/scenes/ui/result.pix3scene +105 -105
- package/templates/recipe-grid-3d/files/scripts/GridBoard.ts +319 -319
- package/templates/recipe-grid-3d/files/scripts/GridRules.ts +278 -278
- package/templates/recipe-grid-3d/files/scripts/MenuFlow.ts +106 -106
- package/templates/recipe-grid-3d/files/scripts/ScoreHud.ts +94 -94
- package/templates/recipe-grid-3d/template.yaml +10 -10
- package/templates/recipe-tapper-2d/files/README.md +39 -39
- package/templates/recipe-tapper-2d/files/design/recipe.md +132 -132
- package/templates/recipe-tapper-2d/files/design/tests/reachability.json +4 -4
- package/templates/recipe-tapper-2d/files/design/tests/routines/terminal-retry.json +23 -23
- package/templates/recipe-tapper-2d/files/scenes/main.pix3scene +269 -269
- package/templates/recipe-tapper-2d/files/scenes/menu.pix3scene +100 -100
- package/templates/recipe-tapper-2d/files/scenes/prefabs/hazard.pix3scene +42 -42
- package/templates/recipe-tapper-2d/files/scenes/prefabs/target.pix3scene +45 -45
- package/templates/recipe-tapper-2d/files/scenes/ui/result.pix3scene +85 -85
- package/templates/recipe-tapper-2d/files/scripts/GameRules.ts +368 -368
- package/templates/recipe-tapper-2d/files/scripts/MenuFlow.ts +106 -106
- package/templates/recipe-tapper-2d/files/scripts/ScoreHud.ts +94 -94
- package/templates/recipe-tapper-2d/files/scripts/Spawner.ts +214 -214
- package/templates/recipe-tapper-2d/files/scripts/TouchRules.ts +192 -192
- package/templates/recipe-tapper-2d/template.yaml +10 -10
- package/dist/smoke/prebuilt/chunk-OJXQXIIQ.mjs +0 -239
- package/dist/validate/prebuilt/chunk-BS2OZM7U.js +0 -239
|
@@ -1,132 +1,132 @@
|
|
|
1
|
-
# Recipe: recipe-tapper-2d
|
|
2
|
-
|
|
3
|
-
## What this is
|
|
4
|
-
|
|
5
|
-
Objects fall into a field; tapping them is the whole game. One "bad" type
|
|
6
|
-
punishes instead of scoring. A countdown ends the run and a target score decides
|
|
7
|
-
win or lose. Playable before any edit.
|
|
8
|
-
|
|
9
|
-
Same script names and roles as the other recipes — `Spawner`, `TouchRules`,
|
|
10
|
-
`ScoreHud`, `GameRules` — so what you learn here transfers.
|
|
11
|
-
|
|
12
|
-
## Node map
|
|
13
|
-
|
|
14
|
-
| id | what |
|
|
15
|
-
| --- | --- |
|
|
16
|
-
| `game-root` | root; hosts `GameRules` + `TouchRules`; all HUD signals emitted here |
|
|
17
|
-
| `game-background` | full-screen `ColorRect2D` (palette background) |
|
|
18
|
-
| `board` | the field (`Group2D`) |
|
|
19
|
-
| `board-floor` | the field's visible plate |
|
|
20
|
-
| `spawner-targets` | spawn band for tappable things; hosts `Spawner` |
|
|
21
|
-
| `spawner-hazards` | spawn band for the bad type; hosts `Spawner` |
|
|
22
|
-
| `hud` | `CanvasLayer2D` overlay, hosts `ScoreHud` |
|
|
23
|
-
| `score-label` / `time-label` / `lives-bar` / `menu-button` | HUD |
|
|
24
|
-
| `result-overlay` / `result-label` / `retry-button` | end screen in `scenes/ui/result.pix3scene`, instanced into main hidden (`visible: false` is editor-only) |
|
|
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
|
-
| collectible | `sprites/ph-target.png` | `prefabs/target.pix3scene` |
|
|
38
|
-
| threat | `sprites/ph-hazard.png` | `prefabs/hazard.pix3scene` |
|
|
39
|
-
|
|
40
|
-
## Tunables
|
|
41
|
-
|
|
42
|
-
```yaml
|
|
43
|
-
tunables:
|
|
44
|
-
targetRate: { node: spawner-targets, component: "user:Spawner", property: intervalSec, min: 0.05, max: 20, default: 0.85 }
|
|
45
|
-
targetMaxAlive: { node: spawner-targets, component: "user:Spawner", property: maxAlive, min: 1, max: 200, default: 8 }
|
|
46
|
-
targetFallSpeed: { node: spawner-targets, component: "user:Spawner", property: driftY, min: -3000, max: 3000, default: -230 }
|
|
47
|
-
targetLifetime: { node: spawner-targets, component: "user:Spawner", property: lifetimeSec, min: 0, max: 120, default: 8 }
|
|
48
|
-
hazardRate: { node: spawner-hazards, component: "user:Spawner", property: intervalSec, min: 0.05, max: 20, default: 2.2 }
|
|
49
|
-
hazardFallSpeed: { node: spawner-hazards, component: "user:Spawner", property: driftY, min: -3000, max: 3000, default: -260 }
|
|
50
|
-
tapRadius: { node: game-root, component: "user:TouchRules", property: tapRadius, min: 0, max: 400, default: 40 }
|
|
51
|
-
missPenalty: { node: game-root, component: "user:TouchRules", property: missPenalty, min: 0, max: 10, default: 0 }
|
|
52
|
-
touchRules: { node: game-root, component: "user:TouchRules", property: rules, default: "target:score:1;hazard:damage:1" }
|
|
53
|
-
winMode: { node: game-root, component: "user:GameRules", property: winMode, default: time }
|
|
54
|
-
targetScore: { node: game-root, component: "user:GameRules", property: targetScore, min: 1, max: 99999, default: 18 }
|
|
55
|
-
timeLimitSec: { node: game-root, component: "user:GameRules", property: timeLimitSec, min: 0, max: 600, default: 30 }
|
|
56
|
-
startingLives: { node: game-root, component: "user:GameRules", property: startingLives, min: 1, max: 20, default: 3 }
|
|
57
|
-
bgColor: { node: game-background, property: color, default: "#141a2e" }
|
|
58
|
-
boardColor: { node: board-floor, property: color, default: "#1d2743" }
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
`component` present → `set_component_property`; absent → `set_property`. The
|
|
62
|
-
schema clamps out-of-range values rather than rejecting them.
|
|
63
|
-
|
|
64
|
-
## Extension points
|
|
65
|
-
|
|
66
|
-
- **Popping instead of falling (whack-a-mole).** Set `targetFallSpeed: 0` and
|
|
67
|
-
`targetLifetime` to ~1.2 s: things appear in place and vanish if not tapped.
|
|
68
|
-
Set `gridSnap` on the spawner (e.g. 220) and raise `spawnHeight` to get a mole
|
|
69
|
-
grid instead of a band.
|
|
70
|
-
- **Combos / multipliers.** A new script on `hud` that listens for
|
|
71
|
-
`touch-scored` on `game-root`, counts hits inside a time window, and adds
|
|
72
|
-
only the **extra** points: `game-root.emit('touch-scored', amount * (m - 1), x, y)`
|
|
73
|
-
under a re-entrancy guard (`if (this.emitting) return; this.emitting = true;
|
|
74
|
-
try { emit(...) } finally { this.emitting = false; }`). `GameRules` already
|
|
75
|
-
counted the base `amount` and needs no change. Show the multiplier through a
|
|
76
|
-
signal of its own (`combo-changed`), never through `touch-scored`.
|
|
77
|
-
**The trap:** a listener on `touch-scored` that emits `touch-scored` on the
|
|
78
|
-
same node is called again by its own emit — without the guard, the first tap
|
|
79
|
-
recurses until the stack overflows (and re-emitting the *full* amount would
|
|
80
|
-
double-count the base). No offline check sees it: `pix3 check` and
|
|
81
|
-
`pix3 smoke` are green, because smoke taps nothing. Say so, and have the human
|
|
82
|
-
tap three targets in a row.
|
|
83
|
-
- **A third type.** Copy a prefab, give its `core:Hitbox2D` a new group, add a
|
|
84
|
-
node with a third `Spawner` (`create_node` + `add_component`), and append
|
|
85
|
-
`<group>:score:5` to `touchRules`.
|
|
86
|
-
- **Punish misses.** Raise `missPenalty` — a tap that hits nothing then costs a
|
|
87
|
-
life, which turns a relaxed collector into a precision game.
|
|
88
|
-
- **Sudden death.** `startingLives: 1`, or `winMode: survive` with a longer
|
|
89
|
-
`timeLimitSec` so surviving the clock is the win.
|
|
90
|
-
|
|
91
|
-
Replacing a whole script the list above names is expected: use `fs_write` with
|
|
92
|
-
`overwrite: true` and a short `reason` (the size guard only blocks *silent*
|
|
93
|
-
wholesale rewrites).
|
|
94
|
-
|
|
95
|
-
## Ending a run belongs to GameRules
|
|
96
|
-
|
|
97
|
-
The result overlay and RETRY are **owned by `GameRules`**: it hides the overlay on
|
|
98
|
-
start and keeps `retry-button` **disabled**, then `finish(won)` shows the overlay,
|
|
99
|
-
writes the result text, enables RETRY and freezes the `freezeNodes` subtrees
|
|
100
|
-
(every component on them and on what they spawned). For a custom
|
|
101
|
-
win/lose condition call `finish(true|false)` on that component — do NOT show the
|
|
102
|
-
overlay yourself. A hand-rolled ending leaves RETRY on screen with its handler bound
|
|
103
|
-
and `enabled: false`, i.e. a button that can never be pressed (`game_observe` reports
|
|
104
|
-
`control: { enabled: false }`; that is the tell).
|
|
105
|
-
|
|
106
|
-
## Do not touch
|
|
107
|
-
|
|
108
|
-
- The node ids above (rename `name`, never `id`), and the signal names.
|
|
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
|
-
- The prefab hitbox groups must stay in sync with `touchRules`; the group is the
|
|
113
|
-
only thing that separates "good" from "bad".
|
|
114
|
-
|
|
115
|
-
## Playtest contract
|
|
116
|
-
|
|
117
|
-
terminalVisual: static
|
|
118
|
-
terminalRestartRoutine: terminal-retry
|
|
119
|
-
|
|
120
|
-
After the result overlay appears, the screen should remain still until RETRY. The
|
|
121
|
-
playtest samples five rendered frames at 15-frame intervals after the terminal
|
|
122
|
-
phase has held for 120 frames. The `terminal-retry` routine physically taps RETRY
|
|
123
|
-
and checks that the game returns to its playing phase.
|
|
124
|
-
|
|
125
|
-
## Verify
|
|
126
|
-
|
|
127
|
-
1. `play_start` on `scenes/main.pix3scene`.
|
|
128
|
-
2. After ~2 s, children exist under `spawner-targets` and are falling.
|
|
129
|
-
3. `game_input` a tap on one of them → it disappears, `score-label` climbs, and
|
|
130
|
-
the punch-scale fires.
|
|
131
|
-
4. Tap three hazards → `lives-bar` empties and `result-overlay` shows
|
|
132
|
-
`GAME OVER`; `retry-button` restarts the scene.
|
|
1
|
+
# Recipe: recipe-tapper-2d
|
|
2
|
+
|
|
3
|
+
## What this is
|
|
4
|
+
|
|
5
|
+
Objects fall into a field; tapping them is the whole game. One "bad" type
|
|
6
|
+
punishes instead of scoring. A countdown ends the run and a target score decides
|
|
7
|
+
win or lose. Playable before any edit.
|
|
8
|
+
|
|
9
|
+
Same script names and roles as the other recipes — `Spawner`, `TouchRules`,
|
|
10
|
+
`ScoreHud`, `GameRules` — so what you learn here transfers.
|
|
11
|
+
|
|
12
|
+
## Node map
|
|
13
|
+
|
|
14
|
+
| id | what |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| `game-root` | root; hosts `GameRules` + `TouchRules`; all HUD signals emitted here |
|
|
17
|
+
| `game-background` | full-screen `ColorRect2D` (palette background) |
|
|
18
|
+
| `board` | the field (`Group2D`) |
|
|
19
|
+
| `board-floor` | the field's visible plate |
|
|
20
|
+
| `spawner-targets` | spawn band for tappable things; hosts `Spawner` |
|
|
21
|
+
| `spawner-hazards` | spawn band for the bad type; hosts `Spawner` |
|
|
22
|
+
| `hud` | `CanvasLayer2D` overlay, hosts `ScoreHud` |
|
|
23
|
+
| `score-label` / `time-label` / `lives-bar` / `menu-button` | HUD |
|
|
24
|
+
| `result-overlay` / `result-label` / `retry-button` | end screen in `scenes/ui/result.pix3scene`, instanced into main hidden (`visible: false` is editor-only) |
|
|
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
|
+
| collectible | `sprites/ph-target.png` | `prefabs/target.pix3scene` |
|
|
38
|
+
| threat | `sprites/ph-hazard.png` | `prefabs/hazard.pix3scene` |
|
|
39
|
+
|
|
40
|
+
## Tunables
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
tunables:
|
|
44
|
+
targetRate: { node: spawner-targets, component: "user:Spawner", property: intervalSec, min: 0.05, max: 20, default: 0.85 }
|
|
45
|
+
targetMaxAlive: { node: spawner-targets, component: "user:Spawner", property: maxAlive, min: 1, max: 200, default: 8 }
|
|
46
|
+
targetFallSpeed: { node: spawner-targets, component: "user:Spawner", property: driftY, min: -3000, max: 3000, default: -230 }
|
|
47
|
+
targetLifetime: { node: spawner-targets, component: "user:Spawner", property: lifetimeSec, min: 0, max: 120, default: 8 }
|
|
48
|
+
hazardRate: { node: spawner-hazards, component: "user:Spawner", property: intervalSec, min: 0.05, max: 20, default: 2.2 }
|
|
49
|
+
hazardFallSpeed: { node: spawner-hazards, component: "user:Spawner", property: driftY, min: -3000, max: 3000, default: -260 }
|
|
50
|
+
tapRadius: { node: game-root, component: "user:TouchRules", property: tapRadius, min: 0, max: 400, default: 40 }
|
|
51
|
+
missPenalty: { node: game-root, component: "user:TouchRules", property: missPenalty, min: 0, max: 10, default: 0 }
|
|
52
|
+
touchRules: { node: game-root, component: "user:TouchRules", property: rules, default: "target:score:1;hazard:damage:1" }
|
|
53
|
+
winMode: { node: game-root, component: "user:GameRules", property: winMode, default: time }
|
|
54
|
+
targetScore: { node: game-root, component: "user:GameRules", property: targetScore, min: 1, max: 99999, default: 18 }
|
|
55
|
+
timeLimitSec: { node: game-root, component: "user:GameRules", property: timeLimitSec, min: 0, max: 600, default: 30 }
|
|
56
|
+
startingLives: { node: game-root, component: "user:GameRules", property: startingLives, min: 1, max: 20, default: 3 }
|
|
57
|
+
bgColor: { node: game-background, property: color, default: "#141a2e" }
|
|
58
|
+
boardColor: { node: board-floor, property: color, default: "#1d2743" }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`component` present → `set_component_property`; absent → `set_property`. The
|
|
62
|
+
schema clamps out-of-range values rather than rejecting them.
|
|
63
|
+
|
|
64
|
+
## Extension points
|
|
65
|
+
|
|
66
|
+
- **Popping instead of falling (whack-a-mole).** Set `targetFallSpeed: 0` and
|
|
67
|
+
`targetLifetime` to ~1.2 s: things appear in place and vanish if not tapped.
|
|
68
|
+
Set `gridSnap` on the spawner (e.g. 220) and raise `spawnHeight` to get a mole
|
|
69
|
+
grid instead of a band.
|
|
70
|
+
- **Combos / multipliers.** A new script on `hud` that listens for
|
|
71
|
+
`touch-scored` on `game-root`, counts hits inside a time window, and adds
|
|
72
|
+
only the **extra** points: `game-root.emit('touch-scored', amount * (m - 1), x, y)`
|
|
73
|
+
under a re-entrancy guard (`if (this.emitting) return; this.emitting = true;
|
|
74
|
+
try { emit(...) } finally { this.emitting = false; }`). `GameRules` already
|
|
75
|
+
counted the base `amount` and needs no change. Show the multiplier through a
|
|
76
|
+
signal of its own (`combo-changed`), never through `touch-scored`.
|
|
77
|
+
**The trap:** a listener on `touch-scored` that emits `touch-scored` on the
|
|
78
|
+
same node is called again by its own emit — without the guard, the first tap
|
|
79
|
+
recurses until the stack overflows (and re-emitting the *full* amount would
|
|
80
|
+
double-count the base). No offline check sees it: `pix3 check` and
|
|
81
|
+
`pix3 smoke` are green, because smoke taps nothing. Say so, and have the human
|
|
82
|
+
tap three targets in a row.
|
|
83
|
+
- **A third type.** Copy a prefab, give its `core:Hitbox2D` a new group, add a
|
|
84
|
+
node with a third `Spawner` (`create_node` + `add_component`), and append
|
|
85
|
+
`<group>:score:5` to `touchRules`.
|
|
86
|
+
- **Punish misses.** Raise `missPenalty` — a tap that hits nothing then costs a
|
|
87
|
+
life, which turns a relaxed collector into a precision game.
|
|
88
|
+
- **Sudden death.** `startingLives: 1`, or `winMode: survive` with a longer
|
|
89
|
+
`timeLimitSec` so surviving the clock is the win.
|
|
90
|
+
|
|
91
|
+
Replacing a whole script the list above names is expected: use `fs_write` with
|
|
92
|
+
`overwrite: true` and a short `reason` (the size guard only blocks *silent*
|
|
93
|
+
wholesale rewrites).
|
|
94
|
+
|
|
95
|
+
## Ending a run belongs to GameRules
|
|
96
|
+
|
|
97
|
+
The result overlay and RETRY are **owned by `GameRules`**: it hides the overlay on
|
|
98
|
+
start and keeps `retry-button` **disabled**, then `finish(won)` shows the overlay,
|
|
99
|
+
writes the result text, enables RETRY and freezes the `freezeNodes` subtrees
|
|
100
|
+
(every component on them and on what they spawned). For a custom
|
|
101
|
+
win/lose condition call `finish(true|false)` on that component — do NOT show the
|
|
102
|
+
overlay yourself. A hand-rolled ending leaves RETRY on screen with its handler bound
|
|
103
|
+
and `enabled: false`, i.e. a button that can never be pressed (`game_observe` reports
|
|
104
|
+
`control: { enabled: false }`; that is the tell).
|
|
105
|
+
|
|
106
|
+
## Do not touch
|
|
107
|
+
|
|
108
|
+
- The node ids above (rename `name`, never `id`), and the signal names.
|
|
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
|
+
- The prefab hitbox groups must stay in sync with `touchRules`; the group is the
|
|
113
|
+
only thing that separates "good" from "bad".
|
|
114
|
+
|
|
115
|
+
## Playtest contract
|
|
116
|
+
|
|
117
|
+
terminalVisual: static
|
|
118
|
+
terminalRestartRoutine: terminal-retry
|
|
119
|
+
|
|
120
|
+
After the result overlay appears, the screen should remain still until RETRY. The
|
|
121
|
+
playtest samples five rendered frames at 15-frame intervals after the terminal
|
|
122
|
+
phase has held for 120 frames. The `terminal-retry` routine physically taps RETRY
|
|
123
|
+
and checks that the game returns to its playing phase.
|
|
124
|
+
|
|
125
|
+
## Verify
|
|
126
|
+
|
|
127
|
+
1. `play_start` on `scenes/main.pix3scene`.
|
|
128
|
+
2. After ~2 s, children exist under `spawner-targets` and are falling.
|
|
129
|
+
3. `game_input` a tap on one of them → it disappears, `score-label` climbs, and
|
|
130
|
+
the punch-scale fires.
|
|
131
|
+
4. Tap three hazards → `lives-bar` empties and `result-overlay` shows
|
|
132
|
+
`GAME OVER`; `retry-button` restarts the scene.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 1,
|
|
3
|
-
"proven": []
|
|
4
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"proven": []
|
|
4
|
+
}
|
|
@@ -1,23 +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
|
-
}
|
|
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
|
+
}
|