diff --git a/art/sprites/README.md b/art/sprites/README.md
index 1ffe1bd..f65ebf4 100644
--- a/art/sprites/README.md
+++ b/art/sprites/README.md
@@ -18,3 +18,4 @@ Sprite reference boards: the hero sheet (approved) and the animation, enemy, ite
| [](fx-leaves.md)
**[FX · falling leaves](fx-leaves.md)** | [](fx-ground.md)
**[FX · footstep puffs](fx-ground.md)** |
| [](fx-water.md)
**[FX · pond ripples and lily pad](fx-water.md)** | [](dexter-prologue.md)
**[Dexter · prologue additions](dexter-prologue.md)** |
| [](menu-key.md)
**[Menu · the Hidden Garden at first light](menu-key.md)** | [](it-props.md)
**[Props · the storm drain's IT details](it-props.md)** |
+| [](rubber-chicken.md)
**[Secret · the rubber chicken](rubber-chicken.md)** | |
diff --git a/art/sprites/boards.json b/art/sprites/boards.json
index e17c134..8b22525 100644
--- a/art/sprites/boards.json
+++ b/art/sprites/boards.json
@@ -1170,6 +1170,33 @@
]
},
"review": "🟨 Awaiting Alexander's review (25 Oct). First draw: three distinct props in the food sheet's look. The board's 1 px balloon string broke up when crushed, so the game draws the string; the eyes are two pieces per frame (\"parts\": 2). Sizes per animation: balloon about 18 px, eyes 7 px, boat 9 px against the 76 px hero. In the drain: previews/it-props-in-drain.png."
+ },
+ {
+ "id": "rubber-chicken",
+ "title": "Secret · the rubber chicken",
+ "factor": 2,
+ "inputs": [
+ "repo:art/sprites/images/mensa-food-preview.png",
+ "repo:art/direction/images/style-master-preview.png"
+ ],
+ "prompt": "On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground shadows, no dust under the objects), with NO text at all - no titles, no captions, no frame numbers, no labels, no readable words or logos: Image 1 is an approved prop sheet from this game: draw this sheet in exactly that pixel-art look - small detailed props on magenta, the same pixel grain and the same soft warm rim light. Prop sprite sheet of ONE classic squeaky RUBBER CHICKEN toy - the long-necked plucked yellow rubber chicken with a red comb, an open orange beak, tiny closed eyes, skinny dangling legs - lying on its side on the ground, side view, slightly worn and dusty as if it had been lost in a garden for a while. 1 row, exactly 2 frames: frame 1 resting, frame 2 the same chicken squeezed in the middle, its body pinched thin and its beak wide open mid-squeak, neck stretched. Both frames at the SAME pixel scale, standing on the SAME invisible ground line, evenly spaced with a wide clear magenta gap between them. Draw distinct key poses. Count the frames: the row has EXACTLY 2. No people, no hands, no scenery.",
+ "atlas": {
+ "height": 9,
+ "rows": [
+ [
+ {
+ "anim": "rest",
+ "frames": 1
+ },
+ {
+ "anim": "squeak",
+ "frames": 1,
+ "loop": false
+ }
+ ]
+ ]
+ },
+ "review": "🟨 Awaiting Alexander's review (25 Oct). Playtest T2 (Lex): the rubber chicken must be in the game. First draw, two frames (resting, squeezed mid-squeak); about 34 x 9 px in the game, a little bigger than a real one so it reads next to the 76 px hero."
}
]
}
diff --git a/art/sprites/boss-jenny-2.md b/art/sprites/boss-jenny-2.md
index f0693e3..521ba86 100644
--- a/art/sprites/boss-jenny-2.md
+++ b/art/sprites/boss-jenny-2.md
@@ -1,7 +1,7 @@
# Boss · Jenny, stunned and debris
-[← Boss · Jenny, attacks](boss-jenny.md) · [Sprite boards](README.md) · **17 / 28** · [Item · pocket computer →](pocket-computer.md)
+[← Boss · Jenny, attacks](boss-jenny.md) · [Sprite boards](README.md) · **17 / 29** · [Item · pocket computer →](pocket-computer.md)

@@ -29,5 +29,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/boss-jenny-2.png` · rebuild: `python scripts/make_boards.py draw boss-jenny-2 --redo --config art/sprites/boards.json`
-[← Boss · Jenny, attacks](boss-jenny.md) · [Sprite boards](README.md) · **17 / 28** · [Item · pocket computer →](pocket-computer.md)
+[← Boss · Jenny, attacks](boss-jenny.md) · [Sprite boards](README.md) · **17 / 29** · [Item · pocket computer →](pocket-computer.md)
diff --git a/art/sprites/boss-jenny.md b/art/sprites/boss-jenny.md
index ea99c18..f03ded6 100644
--- a/art/sprites/boss-jenny.md
+++ b/art/sprites/boss-jenny.md
@@ -1,7 +1,7 @@
# Boss · Jenny, attacks
-[← FX · black fluid](fx.md) · [Sprite boards](README.md) · **16 / 28** · [Boss · Jenny, stunned and debris →](boss-jenny-2.md)
+[← FX · black fluid](fx.md) · [Sprite boards](README.md) · **16 / 29** · [Boss · Jenny, stunned and debris →](boss-jenny-2.md)

@@ -29,5 +29,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/boss-jenny.png` · rebuild: `python scripts/make_boards.py draw boss-jenny --redo --config art/sprites/boards.json`
-[← FX · black fluid](fx.md) · [Sprite boards](README.md) · **16 / 28** · [Boss · Jenny, stunned and debris →](boss-jenny-2.md)
+[← FX · black fluid](fx.md) · [Sprite boards](README.md) · **16 / 29** · [Boss · Jenny, stunned and debris →](boss-jenny-2.md)
diff --git a/art/sprites/chair-block.md b/art/sprites/chair-block.md
index d1a77c7..f3fdbc1 100644
--- a/art/sprites/chair-block.md
+++ b/art/sprites/chair-block.md
@@ -1,7 +1,7 @@
# Chair · block
-[← Chair · overhead slam](chair-overhead.md) · [Sprite boards](README.md) · **11 / 28** · [Enemy · drone →](enemy-drone.md)
+[← Chair · overhead slam](chair-overhead.md) · [Sprite boards](README.md) · **11 / 29** · [Enemy · drone →](enemy-drone.md)

@@ -30,5 +30,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/chair-block.png` · rebuild: `python scripts/make_boards.py draw chair-block --redo --config art/sprites/boards.json`
-[← Chair · overhead slam](chair-overhead.md) · [Sprite boards](README.md) · **11 / 28** · [Enemy · drone →](enemy-drone.md)
+[← Chair · overhead slam](chair-overhead.md) · [Sprite boards](README.md) · **11 / 29** · [Enemy · drone →](enemy-drone.md)
diff --git a/art/sprites/chair-overhead.md b/art/sprites/chair-overhead.md
index 04c3eb2..4b9385c 100644
--- a/art/sprites/chair-overhead.md
+++ b/art/sprites/chair-overhead.md
@@ -1,7 +1,7 @@
# Chair · overhead slam
-[← Chair · side sweep](chair-side.md) · [Sprite boards](README.md) · **10 / 28** · [Chair · block →](chair-block.md)
+[← Chair · side sweep](chair-side.md) · [Sprite boards](README.md) · **10 / 29** · [Chair · block →](chair-block.md)

@@ -31,5 +31,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/chair-overhead.png` · rebuild: `python scripts/make_boards.py draw chair-overhead --redo --config art/sprites/boards.json`
-[← Chair · side sweep](chair-side.md) · [Sprite boards](README.md) · **10 / 28** · [Chair · block →](chair-block.md)
+[← Chair · side sweep](chair-side.md) · [Sprite boards](README.md) · **10 / 29** · [Chair · block →](chair-block.md)
diff --git a/art/sprites/chair-side.md b/art/sprites/chair-side.md
index 1ddcd98..83e3229 100644
--- a/art/sprites/chair-side.md
+++ b/art/sprites/chair-side.md
@@ -1,7 +1,7 @@
# Chair · side sweep
-[← The student · ladder top and bottom](hero-ladder.md) · [Sprite boards](README.md) · **9 / 28** · [Chair · overhead slam →](chair-overhead.md)
+[← The student · ladder top and bottom](hero-ladder.md) · [Sprite boards](README.md) · **9 / 29** · [Chair · overhead slam →](chair-overhead.md)

@@ -30,5 +30,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/chair-side.png` · rebuild: `python scripts/make_boards.py draw chair-side --redo --config art/sprites/boards.json`
-[← The student · ladder top and bottom](hero-ladder.md) · [Sprite boards](README.md) · **9 / 28** · [Chair · overhead slam →](chair-overhead.md)
+[← The student · ladder top and bottom](hero-ladder.md) · [Sprite boards](README.md) · **9 / 29** · [Chair · overhead slam →](chair-overhead.md)
diff --git a/art/sprites/coffee-machine.md b/art/sprites/coffee-machine.md
index 6c766d5..576d057 100644
--- a/art/sprites/coffee-machine.md
+++ b/art/sprites/coffee-machine.md
@@ -1,7 +1,7 @@
# Prop · coffee machine (save point)
-[← Mensa · food pickups](mensa-food.md) · [Sprite boards](README.md) · **20 / 28** · [Dexter · moves →](dexter.md)
+[← Mensa · food pickups](mensa-food.md) · [Sprite boards](README.md) · **20 / 29** · [Dexter · moves →](dexter.md)

@@ -27,5 +27,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/coffee-machine.png` · rebuild: `python scripts/make_boards.py draw coffee-machine --redo --config art/sprites/boards.json`
-[← Mensa · food pickups](mensa-food.md) · [Sprite boards](README.md) · **20 / 28** · [Dexter · moves →](dexter.md)
+[← Mensa · food pickups](mensa-food.md) · [Sprite boards](README.md) · **20 / 29** · [Dexter · moves →](dexter.md)
diff --git a/art/sprites/dexter-acts.md b/art/sprites/dexter-acts.md
index 2d07461..dbba39d 100644
--- a/art/sprites/dexter-acts.md
+++ b/art/sprites/dexter-acts.md
@@ -1,7 +1,7 @@
# Dexter · gestures
-[← Dexter · moves](dexter.md) · [Sprite boards](README.md) · **22 / 28** · [FX · falling leaves →](fx-leaves.md)
+[← Dexter · moves](dexter.md) · [Sprite boards](README.md) · **22 / 29** · [FX · falling leaves →](fx-leaves.md)

@@ -30,5 +30,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/dexter-acts.png` · rebuild: `python scripts/make_boards.py draw dexter-acts --redo --config art/sprites/boards.json`
-[← Dexter · moves](dexter.md) · [Sprite boards](README.md) · **22 / 28** · [FX · falling leaves →](fx-leaves.md)
+[← Dexter · moves](dexter.md) · [Sprite boards](README.md) · **22 / 29** · [FX · falling leaves →](fx-leaves.md)
diff --git a/art/sprites/dexter-prologue.md b/art/sprites/dexter-prologue.md
index ab4b37f..3feda47 100644
--- a/art/sprites/dexter-prologue.md
+++ b/art/sprites/dexter-prologue.md
@@ -1,7 +1,7 @@
# Dexter · prologue additions
-[← FX · pond ripples and lily pad](fx-water.md) · [Sprite boards](README.md) · **26 / 28** · [Menu · the Hidden Garden at first light →](menu-key.md)
+[← FX · pond ripples and lily pad](fx-water.md) · [Sprite boards](README.md) · **26 / 29** · [Menu · the Hidden Garden at first light →](menu-key.md)

@@ -29,5 +29,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/dexter-prologue.png` · rebuild: `python scripts/make_boards.py draw dexter-prologue --redo --config art/sprites/boards.json`
-[← FX · pond ripples and lily pad](fx-water.md) · [Sprite boards](README.md) · **26 / 28** · [Menu · the Hidden Garden at first light →](menu-key.md)
+[← FX · pond ripples and lily pad](fx-water.md) · [Sprite boards](README.md) · **26 / 29** · [Menu · the Hidden Garden at first light →](menu-key.md)
diff --git a/art/sprites/dexter.md b/art/sprites/dexter.md
index 39e0854..46db0a2 100644
--- a/art/sprites/dexter.md
+++ b/art/sprites/dexter.md
@@ -1,7 +1,7 @@
# Dexter · moves
-[← Prop · coffee machine (save point)](coffee-machine.md) · [Sprite boards](README.md) · **21 / 28** · [Dexter · gestures →](dexter-acts.md)
+[← Prop · coffee machine (save point)](coffee-machine.md) · [Sprite boards](README.md) · **21 / 29** · [Dexter · gestures →](dexter-acts.md)

@@ -30,5 +30,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/dexter.png` · rebuild: `python scripts/make_boards.py draw dexter --redo --config art/sprites/boards.json`
-[← Prop · coffee machine (save point)](coffee-machine.md) · [Sprite boards](README.md) · **21 / 28** · [Dexter · gestures →](dexter-acts.md)
+[← Prop · coffee machine (save point)](coffee-machine.md) · [Sprite boards](README.md) · **21 / 29** · [Dexter · gestures →](dexter-acts.md)
diff --git a/art/sprites/enemy-crab.md b/art/sprites/enemy-crab.md
index 8487f8a..38906db 100644
--- a/art/sprites/enemy-crab.md
+++ b/art/sprites/enemy-crab.md
@@ -1,7 +1,7 @@
# Enemy · crab
-[← Enemy · rover](enemy-rover.md) · [Sprite boards](README.md) · **14 / 28** · [FX · black fluid →](fx.md)
+[← Enemy · rover](enemy-rover.md) · [Sprite boards](README.md) · **14 / 29** · [FX · black fluid →](fx.md)

@@ -29,5 +29,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/enemy-crab.png` · rebuild: `python scripts/make_boards.py draw enemy-crab --redo --config art/sprites/boards.json`
-[← Enemy · rover](enemy-rover.md) · [Sprite boards](README.md) · **14 / 28** · [FX · black fluid →](fx.md)
+[← Enemy · rover](enemy-rover.md) · [Sprite boards](README.md) · **14 / 29** · [FX · black fluid →](fx.md)
diff --git a/art/sprites/enemy-drone.md b/art/sprites/enemy-drone.md
index 404cf3b..4d2b4b1 100644
--- a/art/sprites/enemy-drone.md
+++ b/art/sprites/enemy-drone.md
@@ -1,7 +1,7 @@
# Enemy · drone
-[← Chair · block](chair-block.md) · [Sprite boards](README.md) · **12 / 28** · [Enemy · rover →](enemy-rover.md)
+[← Chair · block](chair-block.md) · [Sprite boards](README.md) · **12 / 29** · [Enemy · rover →](enemy-rover.md)

@@ -30,5 +30,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/enemy-drone.png` · rebuild: `python scripts/make_boards.py draw enemy-drone --redo --config art/sprites/boards.json`
-[← Chair · block](chair-block.md) · [Sprite boards](README.md) · **12 / 28** · [Enemy · rover →](enemy-rover.md)
+[← Chair · block](chair-block.md) · [Sprite boards](README.md) · **12 / 29** · [Enemy · rover →](enemy-rover.md)
diff --git a/art/sprites/enemy-rover.md b/art/sprites/enemy-rover.md
index dea7a5e..8976818 100644
--- a/art/sprites/enemy-rover.md
+++ b/art/sprites/enemy-rover.md
@@ -1,7 +1,7 @@
# Enemy · rover
-[← Enemy · drone](enemy-drone.md) · [Sprite boards](README.md) · **13 / 28** · [Enemy · crab →](enemy-crab.md)
+[← Enemy · drone](enemy-drone.md) · [Sprite boards](README.md) · **13 / 29** · [Enemy · crab →](enemy-crab.md)

@@ -29,5 +29,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/enemy-rover.png` · rebuild: `python scripts/make_boards.py draw enemy-rover --redo --config art/sprites/boards.json`
-[← Enemy · drone](enemy-drone.md) · [Sprite boards](README.md) · **13 / 28** · [Enemy · crab →](enemy-crab.md)
+[← Enemy · drone](enemy-drone.md) · [Sprite boards](README.md) · **13 / 29** · [Enemy · crab →](enemy-crab.md)
diff --git a/art/sprites/fx-ground.md b/art/sprites/fx-ground.md
index 0b10d87..717f2cb 100644
--- a/art/sprites/fx-ground.md
+++ b/art/sprites/fx-ground.md
@@ -1,7 +1,7 @@
# FX · footstep puffs
-[← FX · falling leaves](fx-leaves.md) · [Sprite boards](README.md) · **24 / 28** · [FX · pond ripples and lily pad →](fx-water.md)
+[← FX · falling leaves](fx-leaves.md) · [Sprite boards](README.md) · **24 / 29** · [FX · pond ripples and lily pad →](fx-water.md)

@@ -29,5 +29,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/fx-ground.png` · rebuild: `python scripts/make_boards.py draw fx-ground --redo --config art/sprites/boards.json`
-[← FX · falling leaves](fx-leaves.md) · [Sprite boards](README.md) · **24 / 28** · [FX · pond ripples and lily pad →](fx-water.md)
+[← FX · falling leaves](fx-leaves.md) · [Sprite boards](README.md) · **24 / 29** · [FX · pond ripples and lily pad →](fx-water.md)
diff --git a/art/sprites/fx-leaves.md b/art/sprites/fx-leaves.md
index 2a8b0df..f6b0423 100644
--- a/art/sprites/fx-leaves.md
+++ b/art/sprites/fx-leaves.md
@@ -1,7 +1,7 @@
# FX · falling leaves
-[← Dexter · gestures](dexter-acts.md) · [Sprite boards](README.md) · **23 / 28** · [FX · footstep puffs →](fx-ground.md)
+[← Dexter · gestures](dexter-acts.md) · [Sprite boards](README.md) · **23 / 29** · [FX · footstep puffs →](fx-ground.md)

@@ -29,5 +29,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/fx-leaves.png` · rebuild: `python scripts/make_boards.py draw fx-leaves --redo --config art/sprites/boards.json`
-[← Dexter · gestures](dexter-acts.md) · [Sprite boards](README.md) · **23 / 28** · [FX · footstep puffs →](fx-ground.md)
+[← Dexter · gestures](dexter-acts.md) · [Sprite boards](README.md) · **23 / 29** · [FX · footstep puffs →](fx-ground.md)
diff --git a/art/sprites/fx-water.md b/art/sprites/fx-water.md
index d768b26..0955222 100644
--- a/art/sprites/fx-water.md
+++ b/art/sprites/fx-water.md
@@ -1,7 +1,7 @@
# FX · pond ripples and lily pad
-[← FX · footstep puffs](fx-ground.md) · [Sprite boards](README.md) · **25 / 28** · [Dexter · prologue additions →](dexter-prologue.md)
+[← FX · footstep puffs](fx-ground.md) · [Sprite boards](README.md) · **25 / 29** · [Dexter · prologue additions →](dexter-prologue.md)

@@ -28,5 +28,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/fx-water.png` · rebuild: `python scripts/make_boards.py draw fx-water --redo --config art/sprites/boards.json`
-[← FX · footstep puffs](fx-ground.md) · [Sprite boards](README.md) · **25 / 28** · [Dexter · prologue additions →](dexter-prologue.md)
+[← FX · footstep puffs](fx-ground.md) · [Sprite boards](README.md) · **25 / 29** · [Dexter · prologue additions →](dexter-prologue.md)
diff --git a/art/sprites/fx.md b/art/sprites/fx.md
index 0d222fb..c445175 100644
--- a/art/sprites/fx.md
+++ b/art/sprites/fx.md
@@ -1,7 +1,7 @@
# FX · black fluid
-[← Enemy · crab](enemy-crab.md) · [Sprite boards](README.md) · **15 / 28** · [Boss · Jenny, attacks →](boss-jenny.md)
+[← Enemy · crab](enemy-crab.md) · [Sprite boards](README.md) · **15 / 29** · [Boss · Jenny, attacks →](boss-jenny.md)

@@ -26,5 +26,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/fx.png` · rebuild: `python scripts/make_boards.py draw fx --redo --config art/sprites/boards.json`
-[← Enemy · crab](enemy-crab.md) · [Sprite boards](README.md) · **15 / 28** · [Boss · Jenny, attacks →](boss-jenny.md)
+[← Enemy · crab](enemy-crab.md) · [Sprite boards](README.md) · **15 / 29** · [Boss · Jenny, attacks →](boss-jenny.md)
diff --git a/art/sprites/hero-air.md b/art/sprites/hero-air.md
index 641c4a8..cd605b5 100644
--- a/art/sprites/hero-air.md
+++ b/art/sprites/hero-air.md
@@ -1,7 +1,7 @@
# The student · jump, fall, climb
-[← The student · death](hero-death.md) · [Sprite boards](README.md) · **7 / 28** · [The student · ladder top and bottom →](hero-ladder.md)
+[← The student · death](hero-death.md) · [Sprite boards](README.md) · **7 / 29** · [The student · ladder top and bottom →](hero-ladder.md)

@@ -26,5 +26,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/hero-air.png` · rebuild: `python scripts/make_boards.py draw hero-air --redo --config art/sprites/boards.json`
-[← The student · death](hero-death.md) · [Sprite boards](README.md) · **7 / 28** · [The student · ladder top and bottom →](hero-ladder.md)
+[← The student · death](hero-death.md) · [Sprite boards](README.md) · **7 / 29** · [The student · ladder top and bottom →](hero-ladder.md)
diff --git a/art/sprites/hero-death.md b/art/sprites/hero-death.md
index e5155bf..50b1c8e 100644
--- a/art/sprites/hero-death.md
+++ b/art/sprites/hero-death.md
@@ -1,7 +1,7 @@
# The student · death
-[← The student · hurt and interact](hero-hurt.md) · [Sprite boards](README.md) · **6 / 28** · [The student · jump, fall, climb →](hero-air.md)
+[← The student · hurt and interact](hero-hurt.md) · [Sprite boards](README.md) · **6 / 29** · [The student · jump, fall, climb →](hero-air.md)

@@ -26,5 +26,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/hero-death.png` · rebuild: `python scripts/make_boards.py draw hero-death --redo --config art/sprites/boards.json`
-[← The student · hurt and interact](hero-hurt.md) · [Sprite boards](README.md) · **6 / 28** · [The student · jump, fall, climb →](hero-air.md)
+[← The student · hurt and interact](hero-hurt.md) · [Sprite boards](README.md) · **6 / 29** · [The student · jump, fall, climb →](hero-air.md)
diff --git a/art/sprites/hero-hurt.md b/art/sprites/hero-hurt.md
index 544a68a..6204a09 100644
--- a/art/sprites/hero-hurt.md
+++ b/art/sprites/hero-hurt.md
@@ -1,7 +1,7 @@
# The student · hurt and interact
-[← The student · run](hero-run.md) · [Sprite boards](README.md) · **5 / 28** · [The student · death →](hero-death.md)
+[← The student · run](hero-run.md) · [Sprite boards](README.md) · **5 / 29** · [The student · death →](hero-death.md)

@@ -26,5 +26,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/hero-hurt.png` · rebuild: `python scripts/make_boards.py draw hero-hurt --redo --config art/sprites/boards.json`
-[← The student · run](hero-run.md) · [Sprite boards](README.md) · **5 / 28** · [The student · death →](hero-death.md)
+[← The student · run](hero-run.md) · [Sprite boards](README.md) · **5 / 29** · [The student · death →](hero-death.md)
diff --git a/art/sprites/hero-idle.md b/art/sprites/hero-idle.md
index abcd584..e67d68e 100644
--- a/art/sprites/hero-idle.md
+++ b/art/sprites/hero-idle.md
@@ -1,7 +1,7 @@
# The student · idle variants
-[← The student · walk](hero-move.md) · [Sprite boards](README.md) · **3 / 28** · [The student · run →](hero-run.md)
+[← The student · walk](hero-move.md) · [Sprite boards](README.md) · **3 / 29** · [The student · run →](hero-run.md)

@@ -26,5 +26,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/hero-idle.png` · rebuild: `python scripts/make_boards.py draw hero-idle --redo --config art/sprites/boards.json`
-[← The student · walk](hero-move.md) · [Sprite boards](README.md) · **3 / 28** · [The student · run →](hero-run.md)
+[← The student · walk](hero-move.md) · [Sprite boards](README.md) · **3 / 29** · [The student · run →](hero-run.md)
diff --git a/art/sprites/hero-ladder.md b/art/sprites/hero-ladder.md
index 35fffed..7f5fa05 100644
--- a/art/sprites/hero-ladder.md
+++ b/art/sprites/hero-ladder.md
@@ -1,7 +1,7 @@
# The student · ladder top and bottom
-[← The student · jump, fall, climb](hero-air.md) · [Sprite boards](README.md) · **8 / 28** · [Chair · side sweep →](chair-side.md)
+[← The student · jump, fall, climb](hero-air.md) · [Sprite boards](README.md) · **8 / 29** · [Chair · side sweep →](chair-side.md)

@@ -26,5 +26,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/hero-ladder.png` · rebuild: `python scripts/make_boards.py draw hero-ladder --redo --config art/sprites/boards.json`
-[← The student · jump, fall, climb](hero-air.md) · [Sprite boards](README.md) · **8 / 28** · [Chair · side sweep →](chair-side.md)
+[← The student · jump, fall, climb](hero-air.md) · [Sprite boards](README.md) · **8 / 29** · [Chair · side sweep →](chair-side.md)
diff --git a/art/sprites/hero-move.md b/art/sprites/hero-move.md
index cd0e74d..b710510 100644
--- a/art/sprites/hero-move.md
+++ b/art/sprites/hero-move.md
@@ -1,7 +1,7 @@
# The student · walk
-[← The student · hero design](hero.md) · [Sprite boards](README.md) · **2 / 28** · [The student · idle variants →](hero-idle.md)
+[← The student · hero design](hero.md) · [Sprite boards](README.md) · **2 / 29** · [The student · idle variants →](hero-idle.md)

@@ -26,5 +26,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/hero-move.png` · rebuild: `python scripts/make_boards.py draw hero-move --redo --config art/sprites/boards.json`
-[← The student · hero design](hero.md) · [Sprite boards](README.md) · **2 / 28** · [The student · idle variants →](hero-idle.md)
+[← The student · hero design](hero.md) · [Sprite boards](README.md) · **2 / 29** · [The student · idle variants →](hero-idle.md)
diff --git a/art/sprites/hero-run.md b/art/sprites/hero-run.md
index e361ba3..8987cd1 100644
--- a/art/sprites/hero-run.md
+++ b/art/sprites/hero-run.md
@@ -1,7 +1,7 @@
# The student · run
-[← The student · idle variants](hero-idle.md) · [Sprite boards](README.md) · **4 / 28** · [The student · hurt and interact →](hero-hurt.md)
+[← The student · idle variants](hero-idle.md) · [Sprite boards](README.md) · **4 / 29** · [The student · hurt and interact →](hero-hurt.md)

@@ -26,5 +26,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/hero-run.png` · rebuild: `python scripts/make_boards.py draw hero-run --redo --config art/sprites/boards.json`
-[← The student · idle variants](hero-idle.md) · [Sprite boards](README.md) · **4 / 28** · [The student · hurt and interact →](hero-hurt.md)
+[← The student · idle variants](hero-idle.md) · [Sprite boards](README.md) · **4 / 29** · [The student · hurt and interact →](hero-hurt.md)
diff --git a/art/sprites/hero.md b/art/sprites/hero.md
index d63239e..2580a90 100644
--- a/art/sprites/hero.md
+++ b/art/sprites/hero.md
@@ -1,7 +1,7 @@
# The student · hero design
-[← Sprite boards](README.md) · [Sprite boards](README.md) · **1 / 28** · [The student · walk →](hero-move.md)
+[← Sprite boards](README.md) · [Sprite boards](README.md) · **1 / 29** · [The student · walk →](hero-move.md)

@@ -21,5 +21,5 @@ Keep the body, height (6 ft adult), proportions, pose, backpack, grey slim jeans
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/hero.png` · rebuild: `python scripts/make_boards.py draw hero --redo --config art/sprites/boards.json`
-[← Sprite boards](README.md) · [Sprite boards](README.md) · **1 / 28** · [The student · walk →](hero-move.md)
+[← Sprite boards](README.md) · [Sprite boards](README.md) · **1 / 29** · [The student · walk →](hero-move.md)
diff --git a/art/sprites/images/rubber-chicken-preview.png b/art/sprites/images/rubber-chicken-preview.png
new file mode 100644
index 0000000..5fc8421
Binary files /dev/null and b/art/sprites/images/rubber-chicken-preview.png differ
diff --git a/art/sprites/images/rubber-chicken.png b/art/sprites/images/rubber-chicken.png
new file mode 100644
index 0000000..e0bf7b7
Binary files /dev/null and b/art/sprites/images/rubber-chicken.png differ
diff --git a/art/sprites/it-props.md b/art/sprites/it-props.md
index c0e0b0f..ce3b0a7 100644
--- a/art/sprites/it-props.md
+++ b/art/sprites/it-props.md
@@ -1,7 +1,7 @@
# Props · the storm drain's IT details
-[← Menu · the Hidden Garden at first light](menu-key.md) · [Sprite boards](README.md) · **28 / 28** · [Sprite boards →](README.md)
+[← Menu · the Hidden Garden at first light](menu-key.md) · [Sprite boards](README.md) · **28 / 29** · [Secret · the rubber chicken →](rubber-chicken.md)

@@ -27,5 +27,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/it-props.png` · rebuild: `python scripts/make_boards.py draw it-props --redo --config art/sprites/boards.json`
-[← Menu · the Hidden Garden at first light](menu-key.md) · [Sprite boards](README.md) · **28 / 28** · [Sprite boards →](README.md)
+[← Menu · the Hidden Garden at first light](menu-key.md) · [Sprite boards](README.md) · **28 / 29** · [Secret · the rubber chicken →](rubber-chicken.md)
diff --git a/art/sprites/mensa-food.md b/art/sprites/mensa-food.md
index ba4f631..adb77e1 100644
--- a/art/sprites/mensa-food.md
+++ b/art/sprites/mensa-food.md
@@ -1,7 +1,7 @@
# Mensa · food pickups
-[← Item · pocket computer](pocket-computer.md) · [Sprite boards](README.md) · **19 / 28** · [Prop · coffee machine (save point) →](coffee-machine.md)
+[← Item · pocket computer](pocket-computer.md) · [Sprite boards](README.md) · **19 / 29** · [Prop · coffee machine (save point) →](coffee-machine.md)

@@ -30,5 +30,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/mensa-food.png` · rebuild: `python scripts/make_boards.py draw mensa-food --redo --config art/sprites/boards.json`
-[← Item · pocket computer](pocket-computer.md) · [Sprite boards](README.md) · **19 / 28** · [Prop · coffee machine (save point) →](coffee-machine.md)
+[← Item · pocket computer](pocket-computer.md) · [Sprite boards](README.md) · **19 / 29** · [Prop · coffee machine (save point) →](coffee-machine.md)
diff --git a/art/sprites/menu-key.md b/art/sprites/menu-key.md
index 6e54ef4..15974b5 100644
--- a/art/sprites/menu-key.md
+++ b/art/sprites/menu-key.md
@@ -1,7 +1,7 @@
# Menu · the Hidden Garden at first light
-[← Dexter · prologue additions](dexter-prologue.md) · [Sprite boards](README.md) · **27 / 28** · [Props · the storm drain's IT details →](it-props.md)
+[← Dexter · prologue additions](dexter-prologue.md) · [Sprite boards](README.md) · **27 / 29** · [Props · the storm drain's IT details →](it-props.md)

@@ -22,5 +22,5 @@ Key art for the game's title menu, a full 16:9 picture in exactly the hi-bit pix
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/menu-key.png` · rebuild: `python scripts/make_boards.py draw menu-key --redo --config art/sprites/boards.json`
-[← Dexter · prologue additions](dexter-prologue.md) · [Sprite boards](README.md) · **27 / 28** · [Props · the storm drain's IT details →](it-props.md)
+[← Dexter · prologue additions](dexter-prologue.md) · [Sprite boards](README.md) · **27 / 29** · [Props · the storm drain's IT details →](it-props.md)
diff --git a/art/sprites/pocket-computer.md b/art/sprites/pocket-computer.md
index 2958bb4..c77a836 100644
--- a/art/sprites/pocket-computer.md
+++ b/art/sprites/pocket-computer.md
@@ -1,7 +1,7 @@
# Item · pocket computer
-[← Boss · Jenny, stunned and debris](boss-jenny-2.md) · [Sprite boards](README.md) · **18 / 28** · [Mensa · food pickups →](mensa-food.md)
+[← Boss · Jenny, stunned and debris](boss-jenny-2.md) · [Sprite boards](README.md) · **18 / 29** · [Mensa · food pickups →](mensa-food.md)

@@ -28,5 +28,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh
Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/pocket-computer.png` · rebuild: `python scripts/make_boards.py draw pocket-computer --redo --config art/sprites/boards.json`
-[← Boss · Jenny, stunned and debris](boss-jenny-2.md) · [Sprite boards](README.md) · **18 / 28** · [Mensa · food pickups →](mensa-food.md)
+[← Boss · Jenny, stunned and debris](boss-jenny-2.md) · [Sprite boards](README.md) · **18 / 29** · [Mensa · food pickups →](mensa-food.md)
diff --git a/art/sprites/previews/rubber-chicken-in-garden.png b/art/sprites/previews/rubber-chicken-in-garden.png
new file mode 100644
index 0000000..b35b291
Binary files /dev/null and b/art/sprites/previews/rubber-chicken-in-garden.png differ
diff --git a/art/sprites/rubber-chicken.md b/art/sprites/rubber-chicken.md
new file mode 100644
index 0000000..79a705d
--- /dev/null
+++ b/art/sprites/rubber-chicken.md
@@ -0,0 +1,24 @@
+# Secret · the rubber chicken
+
+
+[← Props · the storm drain's IT details](it-props.md) · [Sprite boards](README.md) · **29 / 29** · [Sprite boards →](README.md)
+
+
+
+
+**Review:** 🟨 Awaiting Alexander's review (25 Oct). Playtest T2 (Lex): the rubber chicken must be in the game. First draw, two frames (resting, squeezed mid-squeak); about 34 x 9 px in the game, a little bigger than a real one so it reads next to the 76 px hero.
+
+## Brief
+
+On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground shadows, no dust under the objects), with NO text at all - no titles, no captions, no frame numbers, no labels, no readable words or logos: Image 1 is an approved prop sheet from this game: draw this sheet in exactly that pixel-art look - small detailed props on magenta, the same pixel grain and the same soft warm rim light. Prop sprite sheet of ONE classic squeaky RUBBER CHICKEN toy - the long-necked plucked yellow rubber chicken with a red comb, an open orange beak, tiny closed eyes, skinny dangling legs - lying on its side on the ground, side view, slightly worn and dusty as if it had been lost in a garden for a while. 1 row, exactly 2 frames: frame 1 resting, frame 2 the same chicken squeezed in the middle, its body pinched thin and its beak wide open mid-squeak, neck stretched. Both frames at the SAME pixel scale, standing on the SAME invisible ground line, evenly spaced with a wide clear magenta gap between them. Draw distinct key poses. Count the frames: the row has EXACTLY 2. No people, no hands, no scenery.
+
+## Inputs
+
+- [mensa-food-preview.png](images/mensa-food-preview.png)
+- [style-master-preview.png](../direction/images/style-master-preview.png)
+
+Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/rubber-chicken.png` · rebuild: `python scripts/make_boards.py draw rubber-chicken --redo --config art/sprites/boards.json`
+
+
+[← Props · the storm drain's IT details](it-props.md) · [Sprite boards](README.md) · **29 / 29** · [Sprite boards →](README.md)
+
diff --git a/art/stages/hidden-garden/hg-0-0.clean.collision.json b/art/stages/hidden-garden/hg-0-0.clean.collision.json
index d2f3acf..caaa643 100644
--- a/art/stages/hidden-garden/hg-0-0.clean.collision.json
+++ b/art/stages/hidden-garden/hg-0-0.clean.collision.json
@@ -129,6 +129,15 @@
"h": 76,
"note": "at the fallen birch: Dexter hops onto it and looks back",
"act": "wait"
+ },
+ {
+ "id": "rubber-chicken",
+ "kind": "rubber-chicken",
+ "x": 28,
+ "y": 434,
+ "w": 44,
+ "h": 30,
+ "note": "A secret (playtest T2): the rubber chicken, lost behind the gate pillar, left of where you start. It squeaks."
}
],
"spawn": [
diff --git a/docs/07-ASSET-MANIFEST.md b/docs/07-ASSET-MANIFEST.md
index 36e1fb7..05a9d6f 100644
--- a/docs/07-ASSET-MANIFEST.md
+++ b/docs/07-ASSET-MANIFEST.md
@@ -65,7 +65,7 @@ Frame lists approved as they are (A14).
## Audio (synthesized in code)
-Footsteps (girder, grass), chair whoosh + hit, rover motor, crab servos, drone hum, Jenny engine + crash, fluid absorb, crash-dialog "ding", one ambient loop per region, one boss loop.
+Footsteps (girder, grass), chair whoosh + hit, rover motor, crab servos, drone hum, Jenny engine + crash, fluid absorb, crash-dialog "ding", rubber squeak, one ambient loop per region, one boss loop.
Every sound is **synthesized at runtime with Web Audio** – no audio files, so nothing to license and nothing that breaks decision 14 (no audio in Git). Recipes are plain data in `game/src/fx/sound/recipes.ts` (effects) and `loops.ts` (Hidden Garden, Rusty Creek, Jenny); the `Audio` scene plays them. Game code calls `sound.play('chair-whoosh')` / `sound.loop('boss-jenny')`; hits, defeats, the boss and region turns are wired to the game events, and the anomaly state plays the region loop slightly flat. M mutes.
diff --git a/docs/meetings/playtest-24-oct.md b/docs/meetings/playtest-24-oct.md
index 695474a..e8ebb02 100644
--- a/docs/meetings/playtest-24-oct.md
+++ b/docs/meetings/playtest-24-oct.md
@@ -55,7 +55,7 @@ Vatsal and Lex played the live slice on 24 Oct and recorded what to fix ([A15](t
| # | What | Status |
| --- | --- | --- |
| T1 | The jam theme **"It's spooky outside"** must show | ✅ under the title ("DEGDEV 3 - It's spooky outside."), as the loader's last line ("WARNING: It's spooky outside.") and in the credits |
-| T2 | The **rubber chicken** must be in the game somehow | ❓ smallest version: a rubber chicken prop as a secret pickup or on Dexter's bench; it squeaks |
+| T2 | The **rubber chicken** must be in the game somehow | ✅ a secret: lost behind the garden's gate pillar, left of where you start; walk into it and it squeaks (sprite board `rubber-chicken`, synthesized squeak) |
## Scale – hero against the world
diff --git a/game/public/assets/sprites/rubber-chicken.anims.json b/game/public/assets/sprites/rubber-chicken.anims.json
new file mode 100644
index 0000000..e81ba92
--- /dev/null
+++ b/game/public/assets/sprites/rubber-chicken.anims.json
@@ -0,0 +1,12 @@
+{
+ "rest": {
+ "frames": 1,
+ "fps": 12,
+ "loop": true
+ },
+ "squeak": {
+ "frames": 1,
+ "fps": 12,
+ "loop": false
+ }
+}
diff --git a/game/public/assets/sprites/rubber-chicken.json b/game/public/assets/sprites/rubber-chicken.json
new file mode 100644
index 0000000..1d1f896
--- /dev/null
+++ b/game/public/assets/sprites/rubber-chicken.json
@@ -0,0 +1,62 @@
+{
+ "frames": {
+ "rest-00": {
+ "frame": {
+ "x": 0,
+ "y": 0,
+ "w": 44,
+ "h": 14
+ },
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {
+ "x": 0,
+ "y": 0,
+ "w": 44,
+ "h": 14
+ },
+ "sourceSize": {
+ "w": 44,
+ "h": 14
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 1
+ }
+ },
+ "squeak-00": {
+ "frame": {
+ "x": 0,
+ "y": 16,
+ "w": 44,
+ "h": 14
+ },
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {
+ "x": 0,
+ "y": 0,
+ "w": 44,
+ "h": 14
+ },
+ "sourceSize": {
+ "w": 44,
+ "h": 14
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 1
+ }
+ }
+ },
+ "meta": {
+ "app": "scripts/export_atlas.py",
+ "image": "rubber-chicken.png",
+ "format": "RGBA8888",
+ "size": {
+ "w": 44,
+ "h": 30
+ },
+ "scale": "1"
+ }
+}
diff --git a/game/public/assets/sprites/rubber-chicken.png b/game/public/assets/sprites/rubber-chicken.png
new file mode 100644
index 0000000..9584315
Binary files /dev/null and b/game/public/assets/sprites/rubber-chicken.png differ
diff --git a/game/src/core/scaffold.ts b/game/src/core/scaffold.ts
index 7918cdb..79e8c59 100644
--- a/game/src/core/scaffold.ts
+++ b/game/src/core/scaffold.ts
@@ -14,6 +14,7 @@ import { BODY_WIDTH } from '../player/body';
import { Triggers } from '../stage/triggers';
import { Rooms } from '../stage/rooms';
import { Props } from '../stage/props';
+import { addChickens } from '../stage/chicken';
import { DebugOverlay, type EnemyLike, type JennyLike, type DexterLike } from '../stage/debugOverlay';
import { fallCapture } from '../stage/fallCapture';
import { GameMenu } from '../ui/menu';
@@ -65,6 +66,7 @@ export class Stage extends Phaser.Scene {
this.triggers = new Triggers(data.collisions, data.manifest.region);
this.rooms = new Rooms(this, this.player, data.collisions, data.manifest.region);
this.props = new Props(this, data.collisions, () => this.player?.body, data.state);
+ addChickens(this, data.collisions);
this.follow = import.meta.env.DEV && new URLSearchParams(location.search).has('inspect')
? undefined : cameraFollow(this, data.manifest);
this.overlay = new DebugOverlay(this, stage, legend, data.collisions, {
diff --git a/game/src/fx/sound/recipes.ts b/game/src/fx/sound/recipes.ts
index 329a4fe..738bf80 100644
--- a/game/src/fx/sound/recipes.ts
+++ b/game/src/fx/sound/recipes.ts
@@ -27,7 +27,8 @@ export type SoundId =
| 'rover-motor' | 'crab-servo' | 'drone-hum'
| 'jenny-engine' | 'jenny-crash'
| 'fluid-absorb' | 'crash-ding'
- | 'player-hurt' | 'enemy-defeated';
+ | 'player-hurt' | 'enemy-defeated'
+ | 'rubber-squeak';
export const RECIPES: Record = {
'footstep-girder': [
@@ -79,6 +80,11 @@ export const RECIPES: Record = {
{ wave: 'square', from: 600, to: 150, dur: 0.18, gain: 0.2 },
{ wave: 'noise', dur: 0.1, gain: 0.18, filter: { type: 'lowpass', from: 2000 } },
],
+ // The rubber chicken (playtest T2): squeezed, a rising squeal through the narrow beak, let go, a falling one.
+ 'rubber-squeak': [
+ { wave: 'sawtooth', from: 620, to: 1500, dur: 0.16, attack: 0.02, gain: 0.22, filter: { type: 'bandpass', from: 1300, q: 5 } },
+ { wave: 'sawtooth', from: 1450, to: 760, start: 0.17, dur: 0.22, attack: 0.02, gain: 0.18, filter: { type: 'bandpass', from: 1100, q: 5 } },
+ ],
};
/** The loudest a recipe can get: every voice's peak at once. Kept under 0.9 so nothing clips. */
diff --git a/game/src/fx/sound/sound.test.ts b/game/src/fx/sound/sound.test.ts
index c8ff3eb..72e910e 100644
--- a/game/src/fx/sound/sound.test.ts
+++ b/game/src/fx/sound/sound.test.ts
@@ -13,6 +13,7 @@ const MANIFEST: [string, SoundId[]][] = [
['Jenny engine + crash', ['jenny-engine', 'jenny-crash']],
['fluid absorb', ['fluid-absorb']],
['ding', ['crash-ding']],
+ ['rubber squeak', ['rubber-squeak']],
];
describe('synthesized sound', () => {
diff --git a/game/src/stage/boards.json b/game/src/stage/boards.json
index a2e846a..f7125f3 100644
--- a/game/src/stage/boards.json
+++ b/game/src/stage/boards.json
@@ -8,5 +8,5 @@
"boss-jenny", "boss-jenny-2", "coffee-machine",
"dexter", "dexter-acts", "dexter-prologue", "pocket-computer", "mensa-food"
],
- "optional": ["it-props"]
+ "optional": ["it-props", "rubber-chicken"]
}
diff --git a/game/src/stage/chicken.ts b/game/src/stage/chicken.ts
new file mode 100644
index 0000000..2a6ee6d
--- /dev/null
+++ b/game/src/stage/chicken.ts
@@ -0,0 +1,30 @@
+import type Phaser from 'phaser';
+import { gameEvents } from '../core/events';
+import { sound } from '../fx/sound/Audio';
+import type { CollisionChunk } from './collision';
+import { chickenFrame, chickens } from './chickenModel';
+import { PROP_DEPTH } from './depth';
+
+const BOARD = 'rubber-chicken';
+
+/** Draws the region's rubber chickens and squeezes them (model: `chickenModel.ts`). */
+export function addChickens(scene: Phaser.Scene, chunks: CollisionChunk[]): void {
+ const boxes = chickens(chunks);
+ if (!boxes.length || !scene.textures.exists(BOARD)) return;
+ const sprites = new Map(boxes.map(box => [box.id, scene.add.sprite(box.x + box.w / 2, box.y + box.h, BOARD, 'rest-00')
+ .setOrigin(0.5, 1).setDepth(PROP_DEPTH)]));
+ const squeezed = new Map();
+ const off = gameEvents.on('trigger-entered', ({ kind, id }) => {
+ if (kind !== 'rubber-chicken') return;
+ squeezed.set(id, scene.time.now);
+ sound.play('rubber-squeak');
+ });
+ const update = () => {
+ for (const [id, sprite] of sprites) {
+ const at = squeezed.get(id);
+ sprite.setFrame(chickenFrame(at === undefined ? undefined : scene.time.now - at));
+ }
+ };
+ scene.events.on('update', update);
+ scene.events.once('shutdown', () => { off(); scene.events.off('update', update); });
+}
diff --git a/game/src/stage/chickenModel.test.ts b/game/src/stage/chickenModel.test.ts
new file mode 100644
index 0000000..1c1e4f3
--- /dev/null
+++ b/game/src/stage/chickenModel.test.ts
@@ -0,0 +1,20 @@
+import { describe, expect, it } from 'vitest';
+import { chickenFrame, chickens, SQUEEZE_MS } from './chickenModel';
+import type { Geometry } from './collision';
+import garden from '../../../art/stages/hidden-garden/hg-0-0.clean.collision.json';
+
+const world = (interact: Geometry['interact']) => ({ world: { interact } as Geometry });
+
+describe('the rubber chicken', () => {
+ it('is hidden in the garden, left of where you start', () => {
+ const [chicken] = chickens([world(garden.interact as Geometry['interact'])]);
+ const start = garden.spawn.find(s => s.kind === 'player')!;
+ expect(chicken.id).toBe('rubber-chicken');
+ expect(chicken.x + chicken.w).toBeLessThan(start.x);
+ });
+ it('shows its squeezed frame for a moment after each squeeze', () => {
+ expect(chickenFrame(undefined)).toBe('rest-00');
+ expect(chickenFrame(0)).toBe('squeak-00');
+ expect(chickenFrame(SQUEEZE_MS)).toBe('rest-00');
+ });
+});
diff --git a/game/src/stage/chickenModel.ts b/game/src/stage/chickenModel.ts
new file mode 100644
index 0000000..59269e8
--- /dev/null
+++ b/game/src/stage/chickenModel.ts
@@ -0,0 +1,15 @@
+import type { Box, CollisionChunk } from './collision';
+
+/**
+ * The rubber chicken (playtest T2, a jam requirement): a secret, lost behind the Hidden Garden's gate pillar, left
+ * of where you start. It is an `interact` box of kind `rubber-chicken`; walking into it squeezes it – the squeak
+ * and its squeezed frame – every time. Nothing else: no pickup, no words.
+ */
+export const SQUEEZE_MS = 260;
+
+/** The chicken boxes of a region. */
+export const chickens = (chunks: Pick[]): Box[] =>
+ chunks.flatMap(chunk => chunk.world.interact).filter(box => box.kind === 'rubber-chicken');
+
+/** Which frame shows, `since` ms after the last squeeze (undefined: never squeezed). */
+export const chickenFrame = (since: number | undefined) => since !== undefined && since < SQUEEZE_MS ? 'squeak-00' : 'rest-00';