diff --git a/art/sprites/README.md b/art/sprites/README.md
index 895376a..03f1765 100644
--- a/art/sprites/README.md
+++ b/art/sprites/README.md
@@ -19,4 +19,4 @@ Sprite reference boards: the hero sheet (approved) and the animation, enemy, ite
| [](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)** | [](arena-platform.md)
**[Prop · Jenny's arena platforms](arena-platform.md)** |
-| [](arena-chain.md)
**[Prop · the platforms' chains](arena-chain.md)** | |
+| [](arena-chain.md)
**[Prop · the platforms' chains](arena-chain.md)** | [](arena-wall.md)
**[Prop · Jenny's arena wall](arena-wall.md)** |
diff --git a/art/sprites/arena-chain.md b/art/sprites/arena-chain.md
index cc7b401..55f8af9 100644
--- a/art/sprites/arena-chain.md
+++ b/art/sprites/arena-chain.md
@@ -1,7 +1,7 @@
# Prop · the platforms' chains
-[← Prop · Jenny's arena platforms](arena-platform.md) · [Sprite boards](README.md) · **31 / 31** · [Sprite boards →](README.md)
+[← Prop · Jenny's arena platforms](arena-platform.md) · [Sprite boards](README.md) · **31 / 32** · [Prop · Jenny's arena wall →](arena-wall.md)

@@ -21,5 +21,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/arena-chain.png` · rebuild: `python scripts/make_boards.py draw arena-chain --redo --config art/sprites/boards.json`
-[← Prop · Jenny's arena platforms](arena-platform.md) · [Sprite boards](README.md) · **31 / 31** · [Sprite boards →](README.md)
+[← Prop · Jenny's arena platforms](arena-platform.md) · [Sprite boards](README.md) · **31 / 32** · [Prop · Jenny's arena wall →](arena-wall.md)
diff --git a/art/sprites/arena-platform.md b/art/sprites/arena-platform.md
index 4fd3f85..87e693b 100644
--- a/art/sprites/arena-platform.md
+++ b/art/sprites/arena-platform.md
@@ -1,7 +1,7 @@
# Prop · Jenny's arena platforms
-[← Secret · the rubber chicken](rubber-chicken.md) · [Sprite boards](README.md) · **30 / 31** · [Prop · the platforms' chains →](arena-chain.md)
+[← Secret · the rubber chicken](rubber-chicken.md) · [Sprite boards](README.md) · **30 / 32** · [Prop · the platforms' chains →](arena-chain.md)

@@ -21,5 +21,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/arena-platform.png` · rebuild: `python scripts/make_boards.py draw arena-platform --redo --config art/sprites/boards.json`
-[← Secret · the rubber chicken](rubber-chicken.md) · [Sprite boards](README.md) · **30 / 31** · [Prop · the platforms' chains →](arena-chain.md)
+[← Secret · the rubber chicken](rubber-chicken.md) · [Sprite boards](README.md) · **30 / 32** · [Prop · the platforms' chains →](arena-chain.md)
diff --git a/art/sprites/arena-wall.md b/art/sprites/arena-wall.md
new file mode 100644
index 0000000..debb38a
--- /dev/null
+++ b/art/sprites/arena-wall.md
@@ -0,0 +1,25 @@
+# Prop · Jenny's arena wall
+
+
+[← Prop · the platforms' chains](arena-chain.md) · [Sprite boards](README.md) · **32 / 32** · [Sprite boards →](README.md)
+
+
+
+
+**Review:** 🟨 Awaiting Alexander's review (25 Oct). Playtest J7: the arena's walls were code-drawn rectangles with hazard stripes. First draw: a barrier column of red-and-white crowd-control panels strapped to a rusted pole, with a caged warning lamp (lit, dark). 300 px tall in the game, as tall as the walls. The lamp's halo specks are dropped by "despeckle".
+
+## 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 - the same pixel grain, palette and warm rim light. Prop sprite sheet of ONE tall narrow BARRIER COLUMN that closes a race-track arena on a rusty steel girder maze: a stack of old red-and-white striped plastic and steel crowd-control barrier panels and a rusted scaffold pole, lashed together with orange ratchet straps, standing straight up, about TEN times as tall as it is wide, its top crowned by a small amber rotating warning lamp in a cage (like the race car from image 2 might have hit it before: a few dents, black tyre marks). Strict front view, the same in every frame. 1 row, exactly 2 frames: frame 1 the warning lamp lit bright amber with a glow, frame 2 the same column with the lamp dark. Both frames identical apart from the lamp, same scale, standing on the SAME invisible ground line, with a wide clear magenta gap between them. Count the frames: the row has EXACTLY 2. No people, no car, no scenery, no ground.
+
+## Inputs
+
+- [coffee-machine-preview.png](images/coffee-machine-preview.png)
+- [boss-jenny-preview.png](images/boss-jenny-preview.png)
+- [style-master-preview.png](../direction/images/style-master-preview.png)
+
+Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/arena-wall.png` · rebuild: `python scripts/make_boards.py draw arena-wall --redo --config art/sprites/boards.json`
+
+
+[← Prop · the platforms' chains](arena-chain.md) · [Sprite boards](README.md) · **32 / 32** · [Sprite boards →](README.md)
+
diff --git a/art/sprites/boards.json b/art/sprites/boards.json
index 714e018..09bc037 100644
--- a/art/sprites/boards.json
+++ b/art/sprites/boards.json
@@ -1244,6 +1244,31 @@
]
},
"review": "🟨 Awaiting Alexander's review (25 Oct). The platforms hang from the girder over the arena: scripts/arena_platforms.py tiles this chain's link period from each platform end up to the girder's underside. The second frame came out as a second chain instead of an eye-bolt, so it is unused."
+ },
+ {
+ "id": "arena-wall",
+ "title": "Prop · Jenny's arena wall",
+ "factor": 2,
+ "inputs": [
+ "repo:art/sprites/images/coffee-machine-preview.png",
+ "repo:art/sprites/images/boss-jenny-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 - the same pixel grain, palette and warm rim light. Prop sprite sheet of ONE tall narrow BARRIER COLUMN that closes a race-track arena on a rusty steel girder maze: a stack of old red-and-white striped plastic and steel crowd-control barrier panels and a rusted scaffold pole, lashed together with orange ratchet straps, standing straight up, about TEN times as tall as it is wide, its top crowned by a small amber rotating warning lamp in a cage (like the race car from image 2 might have hit it before: a few dents, black tyre marks). Strict front view, the same in every frame. 1 row, exactly 2 frames: frame 1 the warning lamp lit bright amber with a glow, frame 2 the same column with the lamp dark. Both frames identical apart from the lamp, same scale, standing on the SAME invisible ground line, with a wide clear magenta gap between them. Count the frames: the row has EXACTLY 2. No people, no car, no scenery, no ground.",
+ "atlas": {
+ "height": 300,
+ "rows": [
+ [
+ {
+ "anim": "lamp",
+ "frames": 2,
+ "fps": 3
+ }
+ ]
+ ]
+ },
+ "despeckle": 12,
+ "review": "🟨 Awaiting Alexander's review (25 Oct). Playtest J7: the arena's walls were code-drawn rectangles with hazard stripes. First draw: a barrier column of red-and-white crowd-control panels strapped to a rusted pole, with a caged warning lamp (lit, dark). 300 px tall in the game, as tall as the walls. The lamp's halo specks are dropped by \"despeckle\"."
}
]
}
diff --git a/art/sprites/boss-jenny-2.md b/art/sprites/boss-jenny-2.md
index a8320af..97ccba8 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 / 31** · [Item · pocket computer →](pocket-computer.md)
+[← Boss · Jenny, attacks](boss-jenny.md) · [Sprite boards](README.md) · **17 / 32** · [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 / 31** · [Item · pocket computer →](pocket-computer.md)
+[← Boss · Jenny, attacks](boss-jenny.md) · [Sprite boards](README.md) · **17 / 32** · [Item · pocket computer →](pocket-computer.md)
diff --git a/art/sprites/boss-jenny.md b/art/sprites/boss-jenny.md
index 84e15dc..d131268 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 / 31** · [Boss · Jenny, stunned and debris →](boss-jenny-2.md)
+[← FX · black fluid](fx.md) · [Sprite boards](README.md) · **16 / 32** · [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 / 31** · [Boss · Jenny, stunned and debris →](boss-jenny-2.md)
+[← FX · black fluid](fx.md) · [Sprite boards](README.md) · **16 / 32** · [Boss · Jenny, stunned and debris →](boss-jenny-2.md)
diff --git a/art/sprites/chair-block.md b/art/sprites/chair-block.md
index 523a2ab..85ced64 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 / 31** · [Enemy · drone →](enemy-drone.md)
+[← Chair · overhead slam](chair-overhead.md) · [Sprite boards](README.md) · **11 / 32** · [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 / 31** · [Enemy · drone →](enemy-drone.md)
+[← Chair · overhead slam](chair-overhead.md) · [Sprite boards](README.md) · **11 / 32** · [Enemy · drone →](enemy-drone.md)
diff --git a/art/sprites/chair-overhead.md b/art/sprites/chair-overhead.md
index b26b124..93298e4 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 / 31** · [Chair · block →](chair-block.md)
+[← Chair · side sweep](chair-side.md) · [Sprite boards](README.md) · **10 / 32** · [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 / 31** · [Chair · block →](chair-block.md)
+[← Chair · side sweep](chair-side.md) · [Sprite boards](README.md) · **10 / 32** · [Chair · block →](chair-block.md)
diff --git a/art/sprites/chair-side.md b/art/sprites/chair-side.md
index 22e0bd8..ef7fdc8 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 / 31** · [Chair · overhead slam →](chair-overhead.md)
+[← The student · ladder top and bottom](hero-ladder.md) · [Sprite boards](README.md) · **9 / 32** · [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 / 31** · [Chair · overhead slam →](chair-overhead.md)
+[← The student · ladder top and bottom](hero-ladder.md) · [Sprite boards](README.md) · **9 / 32** · [Chair · overhead slam →](chair-overhead.md)
diff --git a/art/sprites/coffee-machine.md b/art/sprites/coffee-machine.md
index ce15838..514b0cd 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 / 31** · [Dexter · moves →](dexter.md)
+[← Mensa · food pickups](mensa-food.md) · [Sprite boards](README.md) · **20 / 32** · [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 / 31** · [Dexter · moves →](dexter.md)
+[← Mensa · food pickups](mensa-food.md) · [Sprite boards](README.md) · **20 / 32** · [Dexter · moves →](dexter.md)
diff --git a/art/sprites/dexter-acts.md b/art/sprites/dexter-acts.md
index 4434bb1..f37f285 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 / 31** · [FX · falling leaves →](fx-leaves.md)
+[← Dexter · moves](dexter.md) · [Sprite boards](README.md) · **22 / 32** · [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 / 31** · [FX · falling leaves →](fx-leaves.md)
+[← Dexter · moves](dexter.md) · [Sprite boards](README.md) · **22 / 32** · [FX · falling leaves →](fx-leaves.md)
diff --git a/art/sprites/dexter-prologue.md b/art/sprites/dexter-prologue.md
index 2505f77..c1ecee2 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 / 31** · [Menu · the Hidden Garden at first light →](menu-key.md)
+[← FX · pond ripples and lily pad](fx-water.md) · [Sprite boards](README.md) · **26 / 32** · [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 / 31** · [Menu · the Hidden Garden at first light →](menu-key.md)
+[← FX · pond ripples and lily pad](fx-water.md) · [Sprite boards](README.md) · **26 / 32** · [Menu · the Hidden Garden at first light →](menu-key.md)
diff --git a/art/sprites/dexter.md b/art/sprites/dexter.md
index dd76632..97ab03c 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 / 31** · [Dexter · gestures →](dexter-acts.md)
+[← Prop · coffee machine (save point)](coffee-machine.md) · [Sprite boards](README.md) · **21 / 32** · [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 / 31** · [Dexter · gestures →](dexter-acts.md)
+[← Prop · coffee machine (save point)](coffee-machine.md) · [Sprite boards](README.md) · **21 / 32** · [Dexter · gestures →](dexter-acts.md)
diff --git a/art/sprites/enemy-crab.md b/art/sprites/enemy-crab.md
index fdd1ee2..b88eeab 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 / 31** · [FX · black fluid →](fx.md)
+[← Enemy · rover](enemy-rover.md) · [Sprite boards](README.md) · **14 / 32** · [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 / 31** · [FX · black fluid →](fx.md)
+[← Enemy · rover](enemy-rover.md) · [Sprite boards](README.md) · **14 / 32** · [FX · black fluid →](fx.md)
diff --git a/art/sprites/enemy-drone.md b/art/sprites/enemy-drone.md
index 6f94a3f..d4902d6 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 / 31** · [Enemy · rover →](enemy-rover.md)
+[← Chair · block](chair-block.md) · [Sprite boards](README.md) · **12 / 32** · [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 / 31** · [Enemy · rover →](enemy-rover.md)
+[← Chair · block](chair-block.md) · [Sprite boards](README.md) · **12 / 32** · [Enemy · rover →](enemy-rover.md)
diff --git a/art/sprites/enemy-rover.md b/art/sprites/enemy-rover.md
index ce72f0f..01159b2 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 / 31** · [Enemy · crab →](enemy-crab.md)
+[← Enemy · drone](enemy-drone.md) · [Sprite boards](README.md) · **13 / 32** · [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 / 31** · [Enemy · crab →](enemy-crab.md)
+[← Enemy · drone](enemy-drone.md) · [Sprite boards](README.md) · **13 / 32** · [Enemy · crab →](enemy-crab.md)
diff --git a/art/sprites/fx-ground.md b/art/sprites/fx-ground.md
index 6f1fbc8..0acea41 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 / 31** · [FX · pond ripples and lily pad →](fx-water.md)
+[← FX · falling leaves](fx-leaves.md) · [Sprite boards](README.md) · **24 / 32** · [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 / 31** · [FX · pond ripples and lily pad →](fx-water.md)
+[← FX · falling leaves](fx-leaves.md) · [Sprite boards](README.md) · **24 / 32** · [FX · pond ripples and lily pad →](fx-water.md)
diff --git a/art/sprites/fx-leaves.md b/art/sprites/fx-leaves.md
index a09a308..1c796b8 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 / 31** · [FX · footstep puffs →](fx-ground.md)
+[← Dexter · gestures](dexter-acts.md) · [Sprite boards](README.md) · **23 / 32** · [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 / 31** · [FX · footstep puffs →](fx-ground.md)
+[← Dexter · gestures](dexter-acts.md) · [Sprite boards](README.md) · **23 / 32** · [FX · footstep puffs →](fx-ground.md)
diff --git a/art/sprites/fx-water.md b/art/sprites/fx-water.md
index 718436e..da78d76 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 / 31** · [Dexter · prologue additions →](dexter-prologue.md)
+[← FX · footstep puffs](fx-ground.md) · [Sprite boards](README.md) · **25 / 32** · [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 / 31** · [Dexter · prologue additions →](dexter-prologue.md)
+[← FX · footstep puffs](fx-ground.md) · [Sprite boards](README.md) · **25 / 32** · [Dexter · prologue additions →](dexter-prologue.md)
diff --git a/art/sprites/fx.md b/art/sprites/fx.md
index d129282..112bac9 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 / 31** · [Boss · Jenny, attacks →](boss-jenny.md)
+[← Enemy · crab](enemy-crab.md) · [Sprite boards](README.md) · **15 / 32** · [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 / 31** · [Boss · Jenny, attacks →](boss-jenny.md)
+[← Enemy · crab](enemy-crab.md) · [Sprite boards](README.md) · **15 / 32** · [Boss · Jenny, attacks →](boss-jenny.md)
diff --git a/art/sprites/hero-air.md b/art/sprites/hero-air.md
index a79eca9..ba55d04 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 / 31** · [The student · ladder top and bottom →](hero-ladder.md)
+[← The student · death](hero-death.md) · [Sprite boards](README.md) · **7 / 32** · [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 / 31** · [The student · ladder top and bottom →](hero-ladder.md)
+[← The student · death](hero-death.md) · [Sprite boards](README.md) · **7 / 32** · [The student · ladder top and bottom →](hero-ladder.md)
diff --git a/art/sprites/hero-death.md b/art/sprites/hero-death.md
index b9bcb3f..18a506a 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 / 31** · [The student · jump, fall, climb →](hero-air.md)
+[← The student · hurt and interact](hero-hurt.md) · [Sprite boards](README.md) · **6 / 32** · [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 / 31** · [The student · jump, fall, climb →](hero-air.md)
+[← The student · hurt and interact](hero-hurt.md) · [Sprite boards](README.md) · **6 / 32** · [The student · jump, fall, climb →](hero-air.md)
diff --git a/art/sprites/hero-hurt.md b/art/sprites/hero-hurt.md
index 9199c1b..a976659 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 / 31** · [The student · death →](hero-death.md)
+[← The student · run](hero-run.md) · [Sprite boards](README.md) · **5 / 32** · [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 / 31** · [The student · death →](hero-death.md)
+[← The student · run](hero-run.md) · [Sprite boards](README.md) · **5 / 32** · [The student · death →](hero-death.md)
diff --git a/art/sprites/hero-idle.md b/art/sprites/hero-idle.md
index b58e4f7..7f2d217 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 / 31** · [The student · run →](hero-run.md)
+[← The student · walk](hero-move.md) · [Sprite boards](README.md) · **3 / 32** · [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 / 31** · [The student · run →](hero-run.md)
+[← The student · walk](hero-move.md) · [Sprite boards](README.md) · **3 / 32** · [The student · run →](hero-run.md)
diff --git a/art/sprites/hero-ladder.md b/art/sprites/hero-ladder.md
index 2616b9c..46de9a6 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 / 31** · [Chair · side sweep →](chair-side.md)
+[← The student · jump, fall, climb](hero-air.md) · [Sprite boards](README.md) · **8 / 32** · [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 / 31** · [Chair · side sweep →](chair-side.md)
+[← The student · jump, fall, climb](hero-air.md) · [Sprite boards](README.md) · **8 / 32** · [Chair · side sweep →](chair-side.md)
diff --git a/art/sprites/hero-move.md b/art/sprites/hero-move.md
index fd96aa5..0189ebe 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 / 31** · [The student · idle variants →](hero-idle.md)
+[← The student · hero design](hero.md) · [Sprite boards](README.md) · **2 / 32** · [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 / 31** · [The student · idle variants →](hero-idle.md)
+[← The student · hero design](hero.md) · [Sprite boards](README.md) · **2 / 32** · [The student · idle variants →](hero-idle.md)
diff --git a/art/sprites/hero-run.md b/art/sprites/hero-run.md
index 6c8a070..cd8da67 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 / 31** · [The student · hurt and interact →](hero-hurt.md)
+[← The student · idle variants](hero-idle.md) · [Sprite boards](README.md) · **4 / 32** · [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 / 31** · [The student · hurt and interact →](hero-hurt.md)
+[← The student · idle variants](hero-idle.md) · [Sprite boards](README.md) · **4 / 32** · [The student · hurt and interact →](hero-hurt.md)
diff --git a/art/sprites/hero.md b/art/sprites/hero.md
index 687c795..6d24bb6 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 / 31** · [The student · walk →](hero-move.md)
+[← Sprite boards](README.md) · [Sprite boards](README.md) · **1 / 32** · [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 / 31** · [The student · walk →](hero-move.md)
+[← Sprite boards](README.md) · [Sprite boards](README.md) · **1 / 32** · [The student · walk →](hero-move.md)
diff --git a/art/sprites/images/arena-wall-preview.png b/art/sprites/images/arena-wall-preview.png
new file mode 100644
index 0000000..5e2e523
Binary files /dev/null and b/art/sprites/images/arena-wall-preview.png differ
diff --git a/art/sprites/images/arena-wall.png b/art/sprites/images/arena-wall.png
new file mode 100644
index 0000000..c5d2825
Binary files /dev/null and b/art/sprites/images/arena-wall.png differ
diff --git a/art/sprites/it-props.md b/art/sprites/it-props.md
index 858c6c3..9c27acf 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 / 31** · [Secret · the rubber chicken →](rubber-chicken.md)
+[← Menu · the Hidden Garden at first light](menu-key.md) · [Sprite boards](README.md) · **28 / 32** · [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 / 31** · [Secret · the rubber chicken →](rubber-chicken.md)
+[← Menu · the Hidden Garden at first light](menu-key.md) · [Sprite boards](README.md) · **28 / 32** · [Secret · the rubber chicken →](rubber-chicken.md)
diff --git a/art/sprites/mensa-food.md b/art/sprites/mensa-food.md
index 9be2e1e..ae744d6 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 / 31** · [Prop · coffee machine (save point) →](coffee-machine.md)
+[← Item · pocket computer](pocket-computer.md) · [Sprite boards](README.md) · **19 / 32** · [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 / 31** · [Prop · coffee machine (save point) →](coffee-machine.md)
+[← Item · pocket computer](pocket-computer.md) · [Sprite boards](README.md) · **19 / 32** · [Prop · coffee machine (save point) →](coffee-machine.md)
diff --git a/art/sprites/menu-key.md b/art/sprites/menu-key.md
index 31c7b9a..b5f0dd1 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 / 31** · [Props · the storm drain's IT details →](it-props.md)
+[← Dexter · prologue additions](dexter-prologue.md) · [Sprite boards](README.md) · **27 / 32** · [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 / 31** · [Props · the storm drain's IT details →](it-props.md)
+[← Dexter · prologue additions](dexter-prologue.md) · [Sprite boards](README.md) · **27 / 32** · [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 2395a6f..b8e4d78 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 / 31** · [Mensa · food pickups →](mensa-food.md)
+[← Boss · Jenny, stunned and debris](boss-jenny-2.md) · [Sprite boards](README.md) · **18 / 32** · [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 / 31** · [Mensa · food pickups →](mensa-food.md)
+[← Boss · Jenny, stunned and debris](boss-jenny-2.md) · [Sprite boards](README.md) · **18 / 32** · [Mensa · food pickups →](mensa-food.md)
diff --git a/art/sprites/rubber-chicken.md b/art/sprites/rubber-chicken.md
index cc998a9..eb4216d 100644
--- a/art/sprites/rubber-chicken.md
+++ b/art/sprites/rubber-chicken.md
@@ -1,7 +1,7 @@
# Secret · the rubber chicken
-[← Props · the storm drain's IT details](it-props.md) · [Sprite boards](README.md) · **29 / 31** · [Prop · Jenny's arena platforms →](arena-platform.md)
+[← Props · the storm drain's IT details](it-props.md) · [Sprite boards](README.md) · **29 / 32** · [Prop · Jenny's arena platforms →](arena-platform.md)

@@ -20,5 +20,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/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 / 31** · [Prop · Jenny's arena platforms →](arena-platform.md)
+[← Props · the storm drain's IT details](it-props.md) · [Sprite boards](README.md) · **29 / 32** · [Prop · Jenny's arena platforms →](arena-platform.md)
diff --git a/art/stages/rusty-creek/previews/arena-platforms.png b/art/stages/rusty-creek/previews/arena-platforms.png
index 2223980..e62807c 100644
Binary files a/art/stages/rusty-creek/previews/arena-platforms.png and b/art/stages/rusty-creek/previews/arena-platforms.png differ
diff --git a/game/public/assets/sprites/arena-wall.anims.json b/game/public/assets/sprites/arena-wall.anims.json
new file mode 100644
index 0000000..f5b34bc
--- /dev/null
+++ b/game/public/assets/sprites/arena-wall.anims.json
@@ -0,0 +1,7 @@
+{
+ "lamp": {
+ "frames": 2,
+ "fps": 3,
+ "loop": true
+ }
+}
diff --git a/game/public/assets/sprites/arena-wall.json b/game/public/assets/sprites/arena-wall.json
new file mode 100644
index 0000000..8fa8aed
--- /dev/null
+++ b/game/public/assets/sprites/arena-wall.json
@@ -0,0 +1,62 @@
+{
+ "frames": {
+ "lamp-00": {
+ "frame": {
+ "x": 0,
+ "y": 0,
+ "w": 56,
+ "h": 304
+ },
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {
+ "x": 0,
+ "y": 0,
+ "w": 56,
+ "h": 304
+ },
+ "sourceSize": {
+ "w": 56,
+ "h": 304
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 1
+ }
+ },
+ "lamp-01": {
+ "frame": {
+ "x": 58,
+ "y": 0,
+ "w": 56,
+ "h": 304
+ },
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {
+ "x": 0,
+ "y": 0,
+ "w": 56,
+ "h": 304
+ },
+ "sourceSize": {
+ "w": 56,
+ "h": 304
+ },
+ "pivot": {
+ "x": 0.5,
+ "y": 1
+ }
+ }
+ },
+ "meta": {
+ "app": "scripts/export_atlas.py",
+ "image": "arena-wall.png",
+ "format": "RGBA8888",
+ "size": {
+ "w": 114,
+ "h": 304
+ },
+ "scale": "1"
+ }
+}
diff --git a/game/public/assets/sprites/arena-wall.png b/game/public/assets/sprites/arena-wall.png
new file mode 100644
index 0000000..1d7f1b5
Binary files /dev/null and b/game/public/assets/sprites/arena-wall.png differ
diff --git a/game/src/enemies/jennyBoss.ts b/game/src/enemies/jennyBoss.ts
index 6ce953a..8898549 100644
--- a/game/src/enemies/jennyBoss.ts
+++ b/game/src/enemies/jennyBoss.ts
@@ -59,7 +59,7 @@ export class JennyBoss {
private parts = new Map();
private shadows = new Map();
/** The arena's two walls, drawn while they are closed so they are never invisible. */
- private barriers: Phaser.GameObjects.Graphics[] = [];
+ private barriers: (Phaser.GameObjects.Graphics | Phaser.GameObjects.Sprite)[] = [];
/** Her path in the rev: a strip on the floor from her nose to the wall she will hit. */
private path?: Phaser.GameObjects.Rectangle;
/** Stars circling over her while she is stunned: she is dazed, hit her now. */
@@ -124,8 +124,17 @@ export class JennyBoss {
this.hud?.model?.showBoss(NAME, JENNY_HEALTH);
}
- /** A closed wall: a rusted barrier with hazard stripes, so it reads as "not until she is beaten". */
- private drawBarrier(box: Box): Phaser.GameObjects.Graphics {
+ /**
+ * A closed wall reads as "not until she is beaten": the barrier column (`arena-wall` board, its warning lamp
+ * blinking), standing on the wall's foot; without the board, a rusted barrier with hazard stripes.
+ */
+ private drawBarrier(box: Box): Phaser.GameObjects.Graphics | Phaser.GameObjects.Sprite {
+ if (this.scene.textures.exists('arena-wall')) {
+ if (!this.scene.anims.exists('arena-wall:lamp')) this.scene.anims.create({ key: 'arena-wall:lamp', frameRate: 3, repeat: -1,
+ frames: this.scene.anims.generateFrameNames('arena-wall', { prefix: 'lamp-', start: 0, end: 1, zeroPad: 2 }) });
+ return this.scene.add.sprite(box.x + box.w / 2, box.y + box.h, 'arena-wall', 'lamp-00').setOrigin(0.5, 1)
+ .setDepth(DEPTH + 3).setAlpha(0).play('arena-wall:lamp');
+ }
const g = this.scene.add.graphics({ x: box.x, y: box.y }).setDepth(DEPTH + 3).setAlpha(0);
g.fillStyle(0x3a2418, 0.85).fillRect(0, 0, box.w, box.h);
g.lineStyle(2, 0xc8642a, 1).strokeRect(1, 1, box.w - 2, box.h - 2);
diff --git a/game/src/stage/boards.json b/game/src/stage/boards.json
index f7125f3..1e018b8 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", "rubber-chicken"]
+ "optional": ["it-props", "rubber-chicken", "arena-wall"]
}
diff --git a/references/reading-order.json b/references/reading-order.json
index 0303ff2..eae67b1 100644
--- a/references/reading-order.json
+++ b/references/reading-order.json
@@ -49,11 +49,11 @@
},
{
"path": "references/games/drova-forsaken-kin/README.md",
- "label": "Drova – Forsaken Kin"
+ "label": "Drova – Forsaken Kin"
},
{
"path": "references/games/drova-forsaken-kin/visual-reference.md",
- "label": "Drova – Forsaken Kin · gallery"
+ "label": "Drova – Forsaken Kin · gallery"
},
{
"path": "references/games/kingdom-two-crowns/README.md",
diff --git a/scripts/check_docs.py b/scripts/check_docs.py
index 6e8de02..6aa1a6b 100644
--- a/scripts/check_docs.py
+++ b/scripts/check_docs.py
@@ -6,6 +6,8 @@
- no byte-identical tracked files, no tracked file over 6 MB
- every committed board image belongs to a board in its config
- no photos, audio or keys tracked anywhere
+- no double-encoded text (UTF-8 read as Windows-1252 and saved again, so an en dash or a check mark turns into
+ two or three Latin-1 letters)
"""
from __future__ import annotations
@@ -29,6 +31,16 @@
capture_output=True, check=True).stdout.decode().split('\0') if p]
tracked = [p for p in tracked if p.exists()]
+# UTF-8 bytes read as Windows-1252 and written back as UTF-8: the lead bytes of "–", "“", "✅", "🟨", "ä" turn
+# into these pairs. A script that reads git's output in the console's code page does it to a whole file.
+_PAIRS = ((0xE2, 0x20AC), (0xE2, 0x153), (0xF0, 0x178)) # the pairs, by code point, so this file stays clean
+_AFTER_C3 = (0xA4, 0xB6, 0xBC, 0x178, 0x2013, 0x153, 0xA9)
+MOJIBAKE = re.compile('|'.join(''.join(map(chr, pair)) for pair in _PAIRS) + '|' + chr(0xC3) + '[' + ''.join(map(chr, _AFTER_C3)) + ']')
+for path in (p for p in tracked if p.suffix in ('.md', '.json', '.ts', '.py', '.csv')):
+ found = MOJIBAKE.search(path.read_text(encoding='utf-8-sig', errors='replace'))
+ if found:
+ errors.append(f'{path.relative_to(ROOT)}: double-encoded text ({found.group(0)!r}), re-save it from the UTF-8 source')
+
for path in (p for p in tracked if p.suffix == '.md'):
text = path.read_text(encoding='utf-8-sig')
for target in re.findall(r'\]\(([^)\s]+)', text):
diff --git a/scripts/make_boards.py b/scripts/make_boards.py
index bbdbac6..474658a 100644
--- a/scripts/make_boards.py
+++ b/scripts/make_boards.py
@@ -275,6 +275,8 @@ def crush(board: dict) -> None:
else:
small = image.resize(size, Image.Resampling.BOX).convert("RGBA")
alpha = small.getchannel("A").point(lambda a: 255 if a >= 128 else 0)
+ if board.get("despeckle"):
+ alpha = despeckle(alpha, board["despeckle"])
colours = small.convert("RGB").quantize(board.get("colours", COLOURS), method=Image.Quantize.MEDIANCUT,
dither=Image.Dither.NONE)
sprite = colours.convert("RGBA")
@@ -289,6 +291,36 @@ def crush(board: dict) -> None:
print(f"{board['id']}: {small.width}x{small.height} sprite + preview")
+def despeckle(alpha: Image.Image, smaller_than: int) -> Image.Image:
+ """Drop the islands of opaque pixels smaller than `smaller_than` (8-connected): a glow the model drew as a soft
+ halo crushes into scattered specks, which read as noise over any backdrop (backdrop_check.py). The game draws
+ glows itself."""
+ w, h = alpha.size
+ solid = [v > 0 for v in alpha.getdata()]
+ seen = [False] * (w * h)
+ out = alpha.copy()
+ for start in range(w * h):
+ if not solid[start] or seen[start]:
+ continue
+ island, todo = [start], [start]
+ seen[start] = True
+ while todo:
+ i = todo.pop()
+ x, y = i % w, i // w
+ for dx in (-1, 0, 1):
+ for dy in (-1, 0, 1):
+ nx, ny = x + dx, y + dy
+ j = ny * w + nx
+ if 0 <= nx < w and 0 <= ny < h and solid[j] and not seen[j]:
+ seen[j] = True
+ island.append(j)
+ todo.append(j)
+ if len(island) < smaller_than:
+ for i in island:
+ out.putpixel((i % w, i // w), 0)
+ return out
+
+
def describe(ref: str) -> str:
ref, _, box = ref.partition("@")
if box:
diff --git a/scripts/test_make_boards.py b/scripts/test_make_boards.py
index ae30821..9a39d1d 100644
--- a/scripts/test_make_boards.py
+++ b/scripts/test_make_boards.py
@@ -38,6 +38,20 @@ def test_no_hole_without_enclosed_magenta(self):
self.assertIsNone(mb.hole_mask(image).getbbox())
+class DespeckleTests(unittest.TestCase):
+ def test_small_islands_go_and_the_sprite_and_its_attached_glow_stay(self):
+ alpha = Image.new('L', (20, 20), 0)
+ alpha.paste(255, (5, 5, 15, 15)) # the sprite
+ alpha.putpixel((15, 4), 255) # glow touching it diagonally: part of it
+ alpha.putpixel((1, 1), 255) # a lone speck
+ alpha.paste(255, (18, 18, 20, 20)) # a 4-pixel island
+ out = mb.despeckle(alpha, 5)
+ self.assertEqual(out.getpixel((1, 1)), 0)
+ self.assertEqual(out.getpixel((19, 19)), 0)
+ self.assertEqual(out.getpixel((15, 4)), 255)
+ self.assertEqual(out.getpixel((10, 10)), 255)
+
+
class BoardPipelineTests(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()