diff --git a/art/sprites/README.md b/art/sprites/README.md
index b049170..64772fe 100644
--- a/art/sprites/README.md
+++ b/art/sprites/README.md
@@ -1,20 +1,22 @@
# Sprite boards
-Sprite reference boards: the hero sheet (approved) and the animation, enemy, item and boss boards that follow the [visual direction](../direction/README.md). Each board is drawn from real photos of the pose or object, keyed on magenta and crushed to a transparent 1x image. Frame lists and status are in [07 – Asset manifest](../../docs/07-ASSET-MANIFEST.md); the pipeline is in [09](../../docs/09-ART-PIPELINE.md). The [poncho photo board](source-boards/ponchos.png) is the source for the hero's poncho.
+Sprite reference boards: the hero sheet (approved) and the animation, enemy, item and boss boards that follow the [visual direction](../direction/README.md). Each board is drawn from real photos of the pose or object, keyed on magenta and crushed to a transparent 1x image. Frame lists and status are in [07 – Asset manifest](../../docs/07-ASSET-MANIFEST.md); the pipeline is in [09](../../docs/09-ART-PIPELINE.md). The [poncho photo board](source-boards/ponchos.png) is the source for the hero's poncho.
| | |
| --- | --- |
-| [](hero.md)
**[The student · hero design](hero.md)** | [](hero-move.md)
**[The student · walk](hero-move.md)** |
-| [](hero-idle.md)
**[The student · idle variants](hero-idle.md)** | [](hero-run.md)
**[The student · run](hero-run.md)** |
-| [](hero-hurt.md)
**[The student · hurt and interact](hero-hurt.md)** | [](hero-death.md)
**[The student · death](hero-death.md)** |
-| [](hero-air.md)
**[The student · jump, fall, climb](hero-air.md)** | [](hero-ladder.md)
**[The student · ladder top and bottom](hero-ladder.md)** |
-| [](chair-side.md)
**[Chair · side sweep](chair-side.md)** | [](chair-overhead.md)
**[Chair · overhead slam](chair-overhead.md)** |
-| [](chair-block.md)
**[Chair · block](chair-block.md)** | [](enemy-drone.md)
**[Enemy · drone](enemy-drone.md)** |
-| [](enemy-rover.md)
**[Enemy · rover](enemy-rover.md)** | [](enemy-crab.md)
**[Enemy · crab](enemy-crab.md)** |
-| [](fx.md)
**[FX · black fluid](fx.md)** | [](boss-jenny.md)
**[Boss · Jenny, attacks](boss-jenny.md)** |
-| [](boss-jenny-2.md)
**[Boss · Jenny, stunned and debris](boss-jenny-2.md)** | [](pocket-computer.md)
**[Item · pocket computer](pocket-computer.md)** |
-| [](mensa-food.md)
**[Mensa · food pickups](mensa-food.md)** | [](coffee-machine.md)
**[Prop · coffee machine (save point)](coffee-machine.md)** |
-| [](dexter.md)
**[Dexter · moves](dexter.md)** | [](dexter-acts.md)
**[Dexter · gestures](dexter-acts.md)** |
-| [](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)** | [](rubber-chicken.md)
**[Secret · the rubber chicken](rubber-chicken.md)** |
+| [](hero.md)
**[The student · hero design](hero.md)** | [](hero-move.md)
**[The student · walk](hero-move.md)** |
+| [](hero-idle.md)
**[The student · idle variants](hero-idle.md)** | [](hero-run.md)
**[The student · run](hero-run.md)** |
+| [](hero-hurt.md)
**[The student · hurt and interact](hero-hurt.md)** | [](hero-death.md)
**[The student · death](hero-death.md)** |
+| [](hero-air.md)
**[The student · jump, fall, climb](hero-air.md)** | [](hero-ladder.md)
**[The student · ladder top and bottom](hero-ladder.md)** |
+| [](chair-side.md)
**[Chair · side sweep](chair-side.md)** | [](chair-overhead.md)
**[Chair · overhead slam](chair-overhead.md)** |
+| [](chair-block.md)
**[Chair · block](chair-block.md)** | [](enemy-drone.md)
**[Enemy · drone](enemy-drone.md)** |
+| [](enemy-rover.md)
**[Enemy · rover](enemy-rover.md)** | [](enemy-crab.md)
**[Enemy · crab](enemy-crab.md)** |
+| [](fx.md)
**[FX · black fluid](fx.md)** | [](boss-jenny.md)
**[Boss · Jenny, attacks](boss-jenny.md)** |
+| [](boss-jenny-2.md)
**[Boss · Jenny, stunned and debris](boss-jenny-2.md)** | [](pocket-computer.md)
**[Item · pocket computer](pocket-computer.md)** |
+| [](mensa-food.md)
**[Mensa · food pickups](mensa-food.md)** | [](coffee-machine.md)
**[Prop · coffee machine (save point)](coffee-machine.md)** |
+| [](dexter.md)
**[Dexter · moves](dexter.md)** | [](dexter-acts.md)
**[Dexter · gestures](dexter-acts.md)** |
+| [](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)** | [](rubber-chicken.md)
**[Secret · the rubber chicken](rubber-chicken.md)** |
+| [](arena-wall.md)
**[Prop · Jenny's arena wall](arena-wall.md)** | [](arena-platform.md)
**[Prop · Jenny's arena platforms](arena-platform.md)** |
+| [](arena-chain.md)
**[Prop · the platforms' chains](arena-chain.md)** | |
diff --git a/art/sprites/arena-chain.md b/art/sprites/arena-chain.md
new file mode 100644
index 0000000..7ea5538
--- /dev/null
+++ b/art/sprites/arena-chain.md
@@ -0,0 +1,25 @@
+# Prop · the platforms' chains
+
+
+[← Prop · Jenny's arena platforms](arena-platform.md) · [Sprite boards](README.md) · **31 / 31** · [Sprite boards →](README.md)
+
+
+
+
+**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.
+
+## Brief
+
+On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground shadows), with NO text at all - no titles, no captions, no labels: Image 1 is an approved prop from this game, a rusty steel girder platform: draw in exactly its pixel-art look, pixel grain, palette and rusty orange-on-steel colours. Prop sprite sheet, 1 row, exactly 2 frames, side by side with a wide magenta gap: frame 1, ONE heavy rusted steel CHAIN hanging perfectly straight and vertical, about as long as the platform in image 1 is wide, made of many identical oval links alternating face-on and edge-on, the top and bottom links cut flat; frame 2, ONE heavy steel EYE-BOLT anchor: a small rusted base plate with two rivets and a thick eye ring on top, with a single rusted shackle through the ring - the piece that would sit on top of the platform where the chain is fixed. Both frames at the SAME pixel scale (the chain's links as thick as the shackle). No platform, no people, no scenery.
+
+## Inputs
+
+- [arena-platform-preview.png](images/arena-platform-preview.png)
+- [coffee-machine-preview.png](images/coffee-machine-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-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)
+
diff --git a/art/sprites/arena-platform.md b/art/sprites/arena-platform.md
new file mode 100644
index 0000000..b838b24
--- /dev/null
+++ b/art/sprites/arena-platform.md
@@ -0,0 +1,25 @@
+# Prop · Jenny's arena platforms
+
+
+[← Prop · Jenny's arena wall](arena-wall.md) · [Sprite boards](README.md) · **30 / 31** · [Prop · the platforms' chains →](arena-chain.md)
+
+
+
+
+**Review:** 🟨 Awaiting Alexander's review (25 Oct). Replaces the strip cut from the maze plate, which read flat and floated with no reason (Alexander). Drawn with volume from the coffee-machine sheet's look and the maze's own girder as the material reference: a riveted box girder with a grated underside, 202 × 25 px in the game. The prompt asked for eye-bolts and chain stubs, and the model drew the platform alone, so it is locked and the chains are their own board.
+
+## 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 in exactly that pixel-art look - solid three-dimensional objects drawn as detailed 2D pixel art with volume, the same pixel grain, palette and warm rim light. Image 2 shows the rusty steel girders of the level these props hang in: copy their material exactly - pale blue-grey painted steel eaten by orange rust, riveted flanges, a dark grated underside. Prop sprite sheet, 2 rows. Row 1, exactly 1 frame: ONE short girder PLATFORM segment hung like a stage rigging bar, seen from slightly below like image 2: a long flat box beam about FIVE times as long as it is tall, its front face with rivets, a lit top flange, a dark grated underside with depth, solid end caps showing its thickness; on top, near each end, a heavy steel eye-bolt with the lowest three links of a rusted chain rising straight up from it. Row 2, exactly 1 frame: ONE long straight vertical rusted steel CHAIN hanging perfectly straight up and down, many identical links, about as long as the platform in row 1, the same chain as on the platform. Both rows at the SAME pixel scale. Rows separated by a wide empty magenta band. No people, no scenery, no sky.
+
+## Inputs
+
+- [coffee-machine-preview.png](images/coffee-machine-preview.png)
+- [girder-reference.png](../stages/rusty-creek/arena/girder-reference.png)
+- [style-master-preview.png](../direction/images/style-master-preview.png)
+
+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`
+
+
+[← Prop · Jenny's arena wall](arena-wall.md) · [Sprite boards](README.md) · **30 / 31** · [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..9648b58
--- /dev/null
+++ b/art/sprites/arena-wall.md
@@ -0,0 +1,25 @@
+# Prop · Jenny's arena wall
+
+
+[← Secret · the rubber chicken](rubber-chicken.md) · [Sprite boards](README.md) · **29 / 31** · [Prop · Jenny's arena platforms →](arena-platform.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`
+
+
+[← Secret · the rubber chicken](rubber-chicken.md) · [Sprite boards](README.md) · **29 / 31** · [Prop · Jenny's arena platforms →](arena-platform.md)
+
diff --git a/art/sprites/boards.json b/art/sprites/boards.json
index 7acb33f..27b0a3a 100644
--- a/art/sprites/boards.json
+++ b/art/sprites/boards.json
@@ -1,6 +1,6 @@
{
"name": "Sprite boards",
- "readme": "Sprite reference boards: the hero sheet (approved) and the animation, enemy, item and boss boards that follow the [visual direction](../direction/README.md). Each board is drawn from real photos of the pose or object, keyed on magenta and crushed to a transparent 1x image. Frame lists and status are in [07 – Asset manifest](../../docs/07-ASSET-MANIFEST.md); the pipeline is in [09](../../docs/09-ART-PIPELINE.md). The [poncho photo board](source-boards/ponchos.png) is the source for the hero's poncho.",
+ "readme": "Sprite reference boards: the hero sheet (approved) and the animation, enemy, item and boss boards that follow the [visual direction](../direction/README.md). Each board is drawn from real photos of the pose or object, keyed on magenta and crushed to a transparent 1x image. Frame lists and status are in [07 – Asset manifest](../../docs/07-ASSET-MANIFEST.md); the pipeline is in [09](../../docs/09-ART-PIPELINE.md). The [poncho photo board](source-boards/ponchos.png) is the source for the hero's poncho.",
"model": "gpt-image-2.5-sunburst",
"size": "1536x1024",
"style": "Detailed modern HI-BIT PIXEL ART in the look of a modern detailed pixel-art side-scroller: crisp square pixels at a fine 2-pixel grain, rich naturalistic palette with warm autumn light, careful pixel shading and texture (rust, fabric weave, plastic sheen), no anti-aliasing blur, no smooth vector gradients, not chunky 8-bit, not chibi. This is a SPRITE SHEET, not an illustration: no landscape, no sky, no scenery, no panels, no UI frames. Rules: strict side view unless the brief says otherwise; every sprite on the same invisible ground line, same scale within the sheet, evenly spaced with clear empty space between sprites so each can be cut out; frames progress by small continuous changes so they animate smoothly. Solid flat pure magenta #FF00FF background everywhere, no floor, no cast shadows on the background, no frame borders. ABSOLUTELY NO TEXT: no titles, no frame numbers, no arrows, no captions, no labels, no logos - only sprites on magenta. Match the pixel grain, palette and warm rim light of the game's style master (the LAST image), but draw nothing of its scenery. WORLD SCALE: the hero is an adult 6 ft (183 cm) tall; Dexter (the Deggster panther plush) is 1.5 ft (45 cm); Dexter's final form is 8.5 ft (260 cm). Keep real-world proportions between characters and props (a chair seat is at the hero's knee height, a cafeteria tray is as wide as the hero's shoulders).",
@@ -8,7 +8,7 @@
"boards": [
{
"id": "hero",
- "title": "The student · hero design",
+ "title": "The student · hero design",
"factor": 2,
"inputs": [
"out:hero-design",
@@ -18,11 +18,11 @@
"prompt": "Keep the body, height (6 ft adult), proportions, pose, backpack, grey slim jeans and chunky white sneakers of the hero in the first image, and the poncho's CUT, tassel fringe and geometric zig-zag weave from the second image, but RECOLOUR and redesign the poncho as the university's own THD poncho: heather-grey base, deep THD blue and teal zig-zag bands, with a few thin colourful accent stripes (orange, cream, a little red). The poncho has its OWN deep hood, always up. The face is NEVER visible: inside the hood is only solid dark shadow - no eyes, no glasses, no hair, no chin. Gender must be impossible to tell: no visible hair, no visible face, loose silhouette. No hoodie, no scarf. Clean pixel-art CHARACTER SHEET on a flat magenta background, no scenery and no panels. Row 1, full-body turnaround, 5 figures at identical scale: front, three-quarter front, side facing right, three-quarter back, BACK - on the back the poncho has a bold woven THD crest patch in cream and blue with the letters THD clearly readable. Row 2: a close-up of the hood (pure shadow inside), a close-up of the THD back patch, then a scale lineup on one ground line: the hero, a 1.5 ft black panther plush mascot in a grey T-shirt at knee height (third image), and an 8.5 ft tall shadowy panther-humanoid silhouette with glowing blue eyes.",
"locked": true,
"note": "Approved 23 Oct 2026. Drawn from earlier iterations (design anchor outside Git); never redrawn - every other board uses its committed preview.",
- "review": "✅ Approved 23 Oct. Adult 6 ft, face never visible, THD poncho in grey / THD blue / teal with colourful accents, THD crest on the back, scale lineup with Dexter (1.5 ft) and his final form (8.5 ft). Fourth iteration: chibi proportions, a hoodie and a visible face were rejected along the way."
+ "review": "✅ Approved 23 Oct. Adult 6 ft, face never visible, THD poncho in grey / THD blue / teal with colourful accents, THD crest on the back, scale lineup with Dexter (1.5 ft) and his final form (8.5 ft). Fourth iteration: chibi proportions, a hoodie and a visible face were rejected along the way."
},
{
"id": "hero-move",
- "title": "The student · walk",
+ "title": "The student · walk",
"factor": 2,
"inputs": [
"repo:art/sprites/images/hero-preview.png",
@@ -52,11 +52,11 @@
]
]
},
- "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "hero-idle",
- "title": "The student · idle variants",
+ "title": "The student · idle variants",
"factor": 2,
"inputs": [
"repo:art/sprites/images/hero-preview.png",
@@ -92,11 +92,11 @@
]
]
},
- "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "hero-run",
- "title": "The student · run",
+ "title": "The student · run",
"factor": 2,
"inputs": [
"repo:art/sprites/images/hero-preview.png",
@@ -129,11 +129,11 @@
]
]
},
- "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "companion-robot",
- "title": "Campus robot · collectible companion",
+ "title": "Campus robot · collectible companion",
"factor": 2,
"inputs": [
"repo:art/sprites/images/hero-preview.png",
@@ -147,7 +147,7 @@
},
{
"id": "hero-hurt",
- "title": "The student · hurt and interact",
+ "title": "The student · hurt and interact",
"factor": 2,
"inputs": [
"repo:art/sprites/images/hero-preview.png",
@@ -192,11 +192,11 @@
]
]
},
- "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "hero-death",
- "title": "The student · death",
+ "title": "The student · death",
"factor": 2,
"inputs": [
"repo:art/sprites/images/hero-preview.png",
@@ -222,11 +222,11 @@
]
]
},
- "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "hero-air",
- "title": "The student · jump, fall, climb",
+ "title": "The student · jump, fall, climb",
"factor": 2,
"inputs": [
"repo:art/sprites/images/hero-preview.png",
@@ -266,11 +266,11 @@
],
"ref": "jump-08"
},
- "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "hero-ladder",
- "title": "The student · ladder top and bottom",
+ "title": "The student · ladder top and bottom",
"factor": 2,
"inputs": [
"repo:art/sprites/images/hero-preview.png",
@@ -300,7 +300,7 @@
},
{
"id": "chair-side",
- "title": "Chair · side sweep",
+ "title": "Chair · side sweep",
"factor": 2,
"inputs": [
"repo:art/sprites/images/hero-preview.png",
@@ -341,11 +341,11 @@
]
]
},
- "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "chair-overhead",
- "title": "Chair · overhead slam",
+ "title": "Chair · overhead slam",
"factor": 2,
"inputs": [
"repo:art/sprites/images/hero-preview.png",
@@ -385,11 +385,11 @@
]
]
},
- "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "chair-block",
- "title": "Chair · block",
+ "title": "Chair · block",
"factor": 2,
"inputs": [
"repo:art/sprites/images/hero-preview.png",
@@ -428,11 +428,11 @@
]
]
},
- "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "tray-throw",
- "title": "Tray · throw and return",
+ "title": "Tray · throw and return",
"factor": 2,
"inputs": [
"repo:art/sprites/images/hero-preview.png",
@@ -447,7 +447,7 @@
},
{
"id": "enemy-drone",
- "title": "Enemy · drone",
+ "title": "Enemy · drone",
"factor": 2,
"inputs": [
"ref2:20261023_135552.jpg",
@@ -491,11 +491,11 @@
]
]
},
- "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "enemy-rover",
- "title": "Enemy · rover",
+ "title": "Enemy · rover",
"factor": 2,
"inputs": [
"ref2:20261023_125948.jpg",
@@ -534,11 +534,11 @@
]
]
},
- "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "enemy-crab",
- "title": "Enemy · crab",
+ "title": "Enemy · crab",
"factor": 2,
"inputs": [
"ref2:20261023_125939.jpg",
@@ -579,11 +579,11 @@
]
]
},
- "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "fx",
- "title": "FX · black fluid",
+ "title": "FX · black fluid",
"factor": 2,
"inputs": [
"repo:art/direction/images/cast-preview.png",
@@ -602,11 +602,11 @@
]
]
},
- "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "boss-jenny",
- "title": "Boss · Jenny, attacks",
+ "title": "Boss · Jenny, attacks",
"factor": 2,
"inputs": [
"ref2:20261023_130003.jpg",
@@ -642,11 +642,11 @@
],
"ref": "rev-00"
},
- "review": "✅ Approved 24 Oct by Alexander. 270 px long like the approved direction (docs/04), one livery in both sheets; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 24 Oct by Alexander. 270 px long like the approved direction (docs/04), one livery in both sheets; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "boss-jenny-2",
- "title": "Boss · Jenny, stunned and debris",
+ "title": "Boss · Jenny, stunned and debris",
"factor": 2,
"inputs": [
"out:boss-jenny@0,110,1536,190",
@@ -695,11 +695,11 @@
],
"ref": "reboot-01"
},
- "review": "✅ Approved 24 Oct by Alexander. 270 px long like the approved direction (docs/04), one livery in both sheets; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 24 Oct by Alexander. 270 px long like the approved direction (docs/04), one livery in both sheets; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "pocket-computer",
- "title": "Item · pocket computer",
+ "title": "Item · pocket computer",
"factor": 2,
"inputs": [
"ref2:20261023_125955.jpg",
@@ -727,11 +727,11 @@
]
]
},
- "review": "✅ Approved 24 Oct by Alexander (#154); exported to game/public/assets/sprites/ with export_atlas.py. Closed case 19 × 10 px (≈ 45 cm at the hero's 76 px = 183 cm): the photographed suitcase, shrunk to carry; open with the green screen 22 px tall. The map screens are the approved UI atlas (#127), so this board has no screen mock-ups."
+ "review": "✅ Approved 24 Oct by Alexander (#154); exported to game/public/assets/sprites/ with export_atlas.py. Closed case 19 × 10 px (≈ 45 cm at the hero's 76 px = 183 cm): the photographed suitcase, shrunk to carry; open with the green screen 22 px tall. The map screens are the approved UI atlas (#127), so this board has no screen mock-ups."
},
{
"id": "mensa-food",
- "title": "Mensa · food pickups",
+ "title": "Mensa · food pickups",
"factor": 2,
"inputs": [
"repo:art/sprites/images/coffee-machine-preview.png",
@@ -765,11 +765,11 @@
]
]
},
- "review": "✅ Approved 24 Oct by Alexander (#154); exported to game/public/assets/sprites/ with export_atlas.py. Third draw: the first two copied only the muffin; anchoring on the approved coffee-machine sheet gave four distinct items. One frame each (the model ignored the sparkle frames; the game adds the sparkle). Muffin 16 × 14, cake 18 × 14, wafer 17 × 13, Leberkäse roll 18 × 18 px – around the documented 16 px."
+ "review": "✅ Approved 24 Oct by Alexander (#154); exported to game/public/assets/sprites/ with export_atlas.py. Third draw: the first two copied only the muffin; anchoring on the approved coffee-machine sheet gave four distinct items. One frame each (the model ignored the sparkle frames; the game adds the sparkle). Muffin 16 × 14, cake 18 × 14, wafer 17 × 13, Leberkäse roll 18 × 18 px – around the documented 16 px."
},
{
"id": "coffee-machine",
- "title": "Prop · coffee machine (save point)",
+ "title": "Prop · coffee machine (save point)",
"factor": 2,
"inputs": [
"ref2:20261023_125138.jpg",
@@ -795,11 +795,11 @@
]
]
},
- "review": "✅ Approved 24 Oct by Alexander (#154); exported to game/public/assets/sprites/ with export_atlas.py. 42 × 80 px in the game: fills the 40 × 80 coffee-1 box in Rusty Creek, a little taller than the 76 px hero on its base cabinet, as in the photos. Lineup in the stage: previews/coffee-visit-lineup.png."
+ "review": "✅ Approved 24 Oct by Alexander (#154); exported to game/public/assets/sprites/ with export_atlas.py. 42 × 80 px in the game: fills the 40 × 80 coffee-1 box in Rusty Creek, a little taller than the 76 px hero on its base cabinet, as in the photos. Lineup in the stage: previews/coffee-visit-lineup.png."
},
{
"id": "mensa-drinks",
- "title": "Mensa · drinks and coffee",
+ "title": "Mensa · drinks and coffee",
"factor": 2,
"inputs": [
"ref2:20261023_124941.jpg",
@@ -814,7 +814,7 @@
},
{
"id": "wayfinder",
- "title": "Wayfinder sign · prop and pole weapon",
+ "title": "Wayfinder sign · prop and pole weapon",
"factor": 2,
"inputs": [
"repo:art/sprites/images/hero-preview.png",
@@ -828,7 +828,7 @@
},
{
"id": "dexter",
- "title": "Dexter · moves",
+ "title": "Dexter · moves",
"factor": 2,
"inputs": [
"repo:art/direction/images/dexter-stages-preview.png@535,585,125,205",
@@ -873,11 +873,11 @@
]
]
},
- "review": "✅ Approved 24 Oct by Alexander (#141). 27 px = 0.35 of the hero as in the approved Dexter stages (docs/04, compare.py lineup); exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 24 Oct by Alexander (#141). 27 px = 0.35 of the hero as in the approved Dexter stages (docs/04, compare.py lineup); exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "dexter-acts",
- "title": "Dexter · gestures",
+ "title": "Dexter · gestures",
"factor": 2,
"inputs": [
"repo:art/direction/images/dexter-stages-preview.png@535,585,125,205",
@@ -922,11 +922,11 @@
]
]
},
- "review": "✅ Approved 24 Oct by Alexander (#141). 27 px = 0.35 of the hero as in the approved Dexter stages (docs/04, compare.py lineup); exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
+ "review": "✅ Approved 24 Oct by Alexander (#141). 27 px = 0.35 of the hero as in the approved Dexter stages (docs/04, compare.py lineup); exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size."
},
{
"id": "fx-leaves",
- "title": "FX · falling leaves",
+ "title": "FX · falling leaves",
"factor": 2,
"inputs": [
"repo:art/sprites/images/fx-preview.png",
@@ -965,11 +965,11 @@
]
]
},
- "review": "🟡 Drawn 24 Oct for the prologue (workstream 5); exported for the slice, awaiting Alexander's review. Second draw: the first take's thin outlines and fine veins melted into the orange trees at 8 px (6 of 8 test frames LOW in scripts/readability.py); thick dark outlines and lighter fills brought it to 2 of 8. 7–10 px in the game."
+ "review": "🟡 Drawn 24 Oct for the prologue (workstream 5); exported for the slice, awaiting Alexander's review. Second draw: the first take's thin outlines and fine veins melted into the orange trees at 8 px (6 of 8 test frames LOW in scripts/readability.py); thick dark outlines and lighter fills brought it to 2 of 8. 7–10 px in the game."
},
{
"id": "fx-ground",
- "title": "FX · footstep puffs",
+ "title": "FX · footstep puffs",
"factor": 2,
"inputs": [
"repo:art/sprites/images/fx-preview.png",
@@ -1016,11 +1016,11 @@
]
]
},
- "review": "🟡 Drawn 24 Oct for the prologue (workstream 5); exported for the slice, awaiting Alexander's review. Dust 21–30 × 5–12 px, grass and leaf kick ≈ 25 × 14, splash ≈ 25 × 16 – about one step wide next to the 76 px hero. Rows are `even` (they break into specks)."
+ "review": "🟡 Drawn 24 Oct for the prologue (workstream 5); exported for the slice, awaiting Alexander's review. Dust 21–30 × 5–12 px, grass and leaf kick ≈ 25 × 14, splash ≈ 25 × 16 – about one step wide next to the 76 px hero. Rows are `even` (they break into specks)."
},
{
"id": "fx-water",
- "title": "FX · pond ripples and lily pad",
+ "title": "FX · pond ripples and lily pad",
"factor": 2,
"inputs": [
"repo:art/sprites/images/fx-preview.png",
@@ -1061,11 +1061,11 @@
],
"_measured": "Lily pads in hg-4-0 (x 670, y 660) measure about 68 x 14-17 px; height 16 on lily-bob-01 gives 58 x 16. The sparkle is halved (scale 0.5) to a 12 px glint."
},
- "review": "🟡 Drawn 24 Oct for the prologue (workstream 5); exported for the slice, awaiting Alexander's review. Lily pad 58 × 14–16 px, measured against the painted pads in hg-4-0 (≈ 68 × 14–17); ripple 37–71 px wide; sparkle halved to a 12 px glint. The plate's pads are browner than this olive pad – tint in code if it stands out."
+ "review": "🟡 Drawn 24 Oct for the prologue (workstream 5); exported for the slice, awaiting Alexander's review. Lily pad 58 × 14–16 px, measured against the painted pads in hg-4-0 (≈ 68 × 14–17); ripple 37–71 px wide; sparkle halved to a 12 px glint. The plate's pads are browner than this olive pad – tint in code if it stands out."
},
{
"id": "dexter-prologue",
- "title": "Dexter · prologue additions",
+ "title": "Dexter · prologue additions",
"factor": 2,
"inputs": [
"repo:art/sprites/images/dexter-preview.png",
@@ -1099,11 +1099,11 @@
]
]
},
- "review": "✅ Approved 24 Oct by Alexander; exported to game/public/assets/sprites/ with export_atlas.py."
+ "review": "✅ Approved 24 Oct by Alexander; exported to game/public/assets/sprites/ with export_atlas.py."
},
{
"id": "menu-key",
- "title": "Menu · the Hidden Garden at first light",
+ "title": "Menu · the Hidden Garden at first light",
"factor": 2,
"keyed": false,
"size": "2560x1440",
@@ -1115,11 +1115,11 @@
"repo:art/direction/images/hidden-garden-preview.png@0,0,1536,560"
],
"prompt": "Key art for the game's title menu, a full 16:9 picture in exactly the hi-bit pixel-art look of the first image (the style master) - the magenta and sprite-sheet rules do NOT apply to this one image: it is a full scene. ABSOLUTELY NO TEXT, no logo, no title, no letters, no UI, no characters, no people, no animals. The scene: the Hidden Garden pond from images 2 and 3 at FIRST LIGHT, early morning - the low sun just rising behind the far conifer forest on the left, a pale gold-and-rose sky turning blue above, thin mist lying over the still pond. Strict side view like a game screen, everything parallel to the screen: the far hills and dark conifers, the band of autumn trees in yellow, orange and russet, the tall golden reed bed, the low stone wall and the small timber jetty at the water's edge, the empty park bench on the left. The pond fills the lower third and mirrors everything like a Kingdom Two Crowns reflection, with lily pads and a few floating maple leaves, and a dark blurred foreground of autumn leaves and grass along the bottom edge. Leave calm, simple sky in the upper middle (the game puts its title there). Cozy and beautiful, and only on a second look a little too still: in the reflection right below the jetty, one small patch reflects something that is not there - a faint rusty blue-green girder - very subtle.",
- "review": "🟡 Drawn 24 Oct for the prologue menu (workstream 5); exported to game/public/assets/ui/menu-key.png with scripts/export_key_art.py, awaiting Alexander's review."
+ "review": "🟡 Drawn 24 Oct for the prologue menu (workstream 5); exported to game/public/assets/ui/menu-key.png with scripts/export_key_art.py, awaiting Alexander's review."
},
{
"id": "intro-anomaly",
- "title": "Intro · the lake anomaly",
+ "title": "Intro · the lake anomaly",
"factor": 2,
"inputs": [
"repo:art/sprites/images/hero-preview.png",
@@ -1132,7 +1132,7 @@
},
{
"id": "rubber-chicken",
- "title": "Secret · the rubber chicken",
+ "title": "Secret · the rubber chicken",
"factor": 2,
"inputs": [
"repo:art/sprites/images/mensa-food-preview.png",
@@ -1155,7 +1155,79 @@
]
]
},
- "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."
+ "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."
+ },
+ {
+ "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\"."
+ },
+ {
+ "id": "arena-platform",
+ "title": "Prop · Jenny's arena platforms",
+ "factor": 2,
+ "inputs": [
+ "repo:art/sprites/images/coffee-machine-preview.png",
+ "repo:art/stages/rusty-creek/arena/girder-reference.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 in exactly that pixel-art look - solid three-dimensional objects drawn as detailed 2D pixel art with volume, the same pixel grain, palette and warm rim light. Image 2 shows the rusty steel girders of the level these props hang in: copy their material exactly - pale blue-grey painted steel eaten by orange rust, riveted flanges, a dark grated underside. Prop sprite sheet, 2 rows. Row 1, exactly 1 frame: ONE short girder PLATFORM segment hung like a stage rigging bar, seen from slightly below like image 2: a long flat box beam about FIVE times as long as it is tall, its front face with rivets, a lit top flange, a dark grated underside with depth, solid end caps showing its thickness; on top, near each end, a heavy steel eye-bolt with the lowest three links of a rusted chain rising straight up from it. Row 2, exactly 1 frame: ONE long straight vertical rusted steel CHAIN hanging perfectly straight up and down, many identical links, about as long as the platform in row 1, the same chain as on the platform. Both rows at the SAME pixel scale. Rows separated by a wide empty magenta band. No people, no scenery, no sky.",
+ "atlas": {
+ "height": 25,
+ "rows": [
+ [
+ {
+ "anim": "platform",
+ "frames": 1
+ }
+ ]
+ ]
+ },
+ "locked": true,
+ "review": "🟨 Awaiting Alexander's review (25 Oct). Replaces the strip cut from the maze plate, which read flat and floated with no reason (Alexander). Drawn with volume from the coffee-machine sheet's look and the maze's own girder as the material reference: a riveted box girder with a grated underside, 202 × 25 px in the game. The prompt asked for eye-bolts and chain stubs, and the model drew the platform alone, so it is locked and the chains are their own board."
+ },
+ {
+ "id": "arena-chain",
+ "title": "Prop · the platforms' chains",
+ "factor": 2,
+ "inputs": [
+ "repo:art/sprites/images/arena-platform-preview.png",
+ "repo:art/sprites/images/coffee-machine-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), with NO text at all - no titles, no captions, no labels: Image 1 is an approved prop from this game, a rusty steel girder platform: draw in exactly its pixel-art look, pixel grain, palette and rusty orange-on-steel colours. Prop sprite sheet, 1 row, exactly 2 frames, side by side with a wide magenta gap: frame 1, ONE heavy rusted steel CHAIN hanging perfectly straight and vertical, about as long as the platform in image 1 is wide, made of many identical oval links alternating face-on and edge-on, the top and bottom links cut flat; frame 2, ONE heavy steel EYE-BOLT anchor: a small rusted base plate with two rivets and a thick eye ring on top, with a single rusted shackle through the ring - the piece that would sit on top of the platform where the chain is fixed. Both frames at the SAME pixel scale (the chain's links as thick as the shackle). No platform, no people, no scenery.",
+ "atlas": {
+ "height": 120,
+ "rows": [
+ [
+ {
+ "anim": "chain",
+ "frames": 2
+ }
+ ]
+ ]
+ },
+ "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."
}
]
}
diff --git a/art/sprites/boss-jenny-2.md b/art/sprites/boss-jenny-2.md
index f0693e3..5334b22 100644
--- a/art/sprites/boss-jenny-2.md
+++ b/art/sprites/boss-jenny-2.md
@@ -1,12 +1,12 @@
-# Boss · Jenny, stunned and debris
+# 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 / 31** · [Item · pocket computer →](pocket-computer.md)
-
+
-**Review:** ✅ Approved 24 Oct by Alexander. 270 px long like the approved direction (docs/04), one livery in both sheets; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 24 Oct by Alexander. 270 px long like the approved direction (docs/04), one livery in both sheets; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [Item · pocket computer →](pocket-computer.md)
diff --git a/art/sprites/boss-jenny.md b/art/sprites/boss-jenny.md
index ea99c18..75e189f 100644
--- a/art/sprites/boss-jenny.md
+++ b/art/sprites/boss-jenny.md
@@ -1,12 +1,12 @@
-# Boss · Jenny, attacks
+# 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 / 31** · [Boss · Jenny, stunned and debris →](boss-jenny-2.md)
-
+
-**Review:** ✅ Approved 24 Oct by Alexander. 270 px long like the approved direction (docs/04), one livery in both sheets; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 24 Oct by Alexander. 270 px long like the approved direction (docs/04), one livery in both sheets; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [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..867fd45 100644
--- a/art/sprites/chair-block.md
+++ b/art/sprites/chair-block.md
@@ -1,12 +1,12 @@
-# Chair · block
+# 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 / 31** · [Enemy · drone →](enemy-drone.md)
-
+
-**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [Enemy · drone →](enemy-drone.md)
diff --git a/art/sprites/chair-overhead.md b/art/sprites/chair-overhead.md
index 04c3eb2..8b0a1c6 100644
--- a/art/sprites/chair-overhead.md
+++ b/art/sprites/chair-overhead.md
@@ -1,12 +1,12 @@
-# Chair · overhead slam
+# 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 / 31** · [Chair · block →](chair-block.md)
-
+
-**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [Chair · block →](chair-block.md)
diff --git a/art/sprites/chair-side.md b/art/sprites/chair-side.md
index 1ddcd98..8f21b8a 100644
--- a/art/sprites/chair-side.md
+++ b/art/sprites/chair-side.md
@@ -1,12 +1,12 @@
-# Chair · side sweep
+# 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 / 31** · [Chair · overhead slam →](chair-overhead.md)
-
+
-**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [Chair · overhead slam →](chair-overhead.md)
diff --git a/art/sprites/coffee-machine.md b/art/sprites/coffee-machine.md
index 6c766d5..7ce6f68 100644
--- a/art/sprites/coffee-machine.md
+++ b/art/sprites/coffee-machine.md
@@ -1,12 +1,12 @@
-# Prop · coffee machine (save point)
+# 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 / 31** · [Dexter · moves →](dexter.md)
-
+
-**Review:** ✅ Approved 24 Oct by Alexander (#154); exported to game/public/assets/sprites/ with export_atlas.py. 42 × 80 px in the game: fills the 40 × 80 coffee-1 box in Rusty Creek, a little taller than the 76 px hero on its base cabinet, as in the photos. Lineup in the stage: previews/coffee-visit-lineup.png.
+**Review:** ✅ Approved 24 Oct by Alexander (#154); exported to game/public/assets/sprites/ with export_atlas.py. 42 × 80 px in the game: fills the 40 × 80 coffee-1 box in Rusty Creek, a little taller than the 76 px hero on its base cabinet, as in the photos. Lineup in the stage: previews/coffee-visit-lineup.png.
## In motion
@@ -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 / 31** · [Dexter · moves →](dexter.md)
diff --git a/art/sprites/dexter-acts.md b/art/sprites/dexter-acts.md
index 2d07461..1833da5 100644
--- a/art/sprites/dexter-acts.md
+++ b/art/sprites/dexter-acts.md
@@ -1,12 +1,12 @@
-# Dexter · gestures
+# 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 / 31** · [FX · falling leaves →](fx-leaves.md)
-
+
-**Review:** ✅ Approved 24 Oct by Alexander (#141). 27 px = 0.35 of the hero as in the approved Dexter stages (docs/04, compare.py lineup); exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 24 Oct by Alexander (#141). 27 px = 0.35 of the hero as in the approved Dexter stages (docs/04, compare.py lineup); exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [FX · falling leaves →](fx-leaves.md)
diff --git a/art/sprites/dexter-prologue.md b/art/sprites/dexter-prologue.md
index ab4b37f..2e72984 100644
--- a/art/sprites/dexter-prologue.md
+++ b/art/sprites/dexter-prologue.md
@@ -1,12 +1,12 @@
-# Dexter · prologue additions
+# 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 / 31** · [Menu · the Hidden Garden at first light →](menu-key.md)
-
+
-**Review:** ✅ Approved 24 Oct by Alexander; exported to game/public/assets/sprites/ with export_atlas.py.
+**Review:** ✅ Approved 24 Oct by Alexander; exported to game/public/assets/sprites/ with export_atlas.py.
## In motion
@@ -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 / 31** · [Menu · the Hidden Garden at first light →](menu-key.md)
diff --git a/art/sprites/dexter.md b/art/sprites/dexter.md
index 39e0854..57c38f6 100644
--- a/art/sprites/dexter.md
+++ b/art/sprites/dexter.md
@@ -1,12 +1,12 @@
-# Dexter · moves
+# 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 / 31** · [Dexter · gestures →](dexter-acts.md)
-
+
-**Review:** ✅ Approved 24 Oct by Alexander (#141). 27 px = 0.35 of the hero as in the approved Dexter stages (docs/04, compare.py lineup); exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 24 Oct by Alexander (#141). 27 px = 0.35 of the hero as in the approved Dexter stages (docs/04, compare.py lineup); exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [Dexter · gestures →](dexter-acts.md)
diff --git a/art/sprites/enemy-crab.md b/art/sprites/enemy-crab.md
index 8487f8a..b54db9c 100644
--- a/art/sprites/enemy-crab.md
+++ b/art/sprites/enemy-crab.md
@@ -1,12 +1,12 @@
-# Enemy · crab
+# 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 / 31** · [FX · black fluid →](fx.md)
-
+
-**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [FX · black fluid →](fx.md)
diff --git a/art/sprites/enemy-drone.md b/art/sprites/enemy-drone.md
index 404cf3b..b97ce59 100644
--- a/art/sprites/enemy-drone.md
+++ b/art/sprites/enemy-drone.md
@@ -1,12 +1,12 @@
-# Enemy · drone
+# 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 / 31** · [Enemy · rover →](enemy-rover.md)
-
+
-**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [Enemy · rover →](enemy-rover.md)
diff --git a/art/sprites/enemy-rover.md b/art/sprites/enemy-rover.md
index dea7a5e..c6aa592 100644
--- a/art/sprites/enemy-rover.md
+++ b/art/sprites/enemy-rover.md
@@ -1,12 +1,12 @@
-# Enemy · rover
+# 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 / 31** · [Enemy · crab →](enemy-crab.md)
-
+
-**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [Enemy · crab →](enemy-crab.md)
diff --git a/art/sprites/fx-ground.md b/art/sprites/fx-ground.md
index 0b10d87..758c4aa 100644
--- a/art/sprites/fx-ground.md
+++ b/art/sprites/fx-ground.md
@@ -1,12 +1,12 @@
-# FX · footstep puffs
+# 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 / 31** · [FX · pond ripples and lily pad →](fx-water.md)
-
+
-**Review:** 🟡 Drawn 24 Oct for the prologue (workstream 5); exported for the slice, awaiting Alexander's review. Dust 21–30 × 5–12 px, grass and leaf kick ≈ 25 × 14, splash ≈ 25 × 16 – about one step wide next to the 76 px hero. Rows are `even` (they break into specks).
+**Review:** 🟡 Drawn 24 Oct for the prologue (workstream 5); exported for the slice, awaiting Alexander's review. Dust 21–30 × 5–12 px, grass and leaf kick ≈ 25 × 14, splash ≈ 25 × 16 – about one step wide next to the 76 px hero. Rows are `even` (they break into specks).
## In motion
@@ -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 / 31** · [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..77b6db3 100644
--- a/art/sprites/fx-leaves.md
+++ b/art/sprites/fx-leaves.md
@@ -1,12 +1,12 @@
-# FX · falling leaves
+# 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 / 31** · [FX · footstep puffs →](fx-ground.md)
-
+
-**Review:** 🟡 Drawn 24 Oct for the prologue (workstream 5); exported for the slice, awaiting Alexander's review. Second draw: the first take's thin outlines and fine veins melted into the orange trees at 8 px (6 of 8 test frames LOW in scripts/readability.py); thick dark outlines and lighter fills brought it to 2 of 8. 7–10 px in the game.
+**Review:** 🟡 Drawn 24 Oct for the prologue (workstream 5); exported for the slice, awaiting Alexander's review. Second draw: the first take's thin outlines and fine veins melted into the orange trees at 8 px (6 of 8 test frames LOW in scripts/readability.py); thick dark outlines and lighter fills brought it to 2 of 8. 7–10 px in the game.
## In motion
@@ -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 / 31** · [FX · footstep puffs →](fx-ground.md)
diff --git a/art/sprites/fx-water.md b/art/sprites/fx-water.md
index d768b26..d30384b 100644
--- a/art/sprites/fx-water.md
+++ b/art/sprites/fx-water.md
@@ -1,12 +1,12 @@
-# FX · pond ripples and lily pad
+# 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 / 31** · [Dexter · prologue additions →](dexter-prologue.md)
-
+
-**Review:** 🟡 Drawn 24 Oct for the prologue (workstream 5); exported for the slice, awaiting Alexander's review. Lily pad 58 × 14–16 px, measured against the painted pads in hg-4-0 (≈ 68 × 14–17); ripple 37–71 px wide; sparkle halved to a 12 px glint. The plate's pads are browner than this olive pad – tint in code if it stands out.
+**Review:** 🟡 Drawn 24 Oct for the prologue (workstream 5); exported for the slice, awaiting Alexander's review. Lily pad 58 × 14–16 px, measured against the painted pads in hg-4-0 (≈ 68 × 14–17); ripple 37–71 px wide; sparkle halved to a 12 px glint. The plate's pads are browner than this olive pad – tint in code if it stands out.
## In motion
@@ -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 / 31** · [Dexter · prologue additions →](dexter-prologue.md)
diff --git a/art/sprites/fx.md b/art/sprites/fx.md
index 0d222fb..71f0388 100644
--- a/art/sprites/fx.md
+++ b/art/sprites/fx.md
@@ -1,12 +1,12 @@
-# FX · black fluid
+# 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 / 31** · [Boss · Jenny, attacks →](boss-jenny.md)
-
+
-**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [Boss · Jenny, attacks →](boss-jenny.md)
diff --git a/art/sprites/hero-air.md b/art/sprites/hero-air.md
index 641c4a8..421be17 100644
--- a/art/sprites/hero-air.md
+++ b/art/sprites/hero-air.md
@@ -1,12 +1,12 @@
-# The student · jump, fall, climb
+# 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 / 31** · [The student · ladder top and bottom →](hero-ladder.md)
-
+
-**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [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..62fc554 100644
--- a/art/sprites/hero-death.md
+++ b/art/sprites/hero-death.md
@@ -1,12 +1,12 @@
-# The student · death
+# 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 / 31** · [The student · jump, fall, climb →](hero-air.md)
-
+
-**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [The student · jump, fall, climb →](hero-air.md)
diff --git a/art/sprites/hero-hurt.md b/art/sprites/hero-hurt.md
index 544a68a..1f5b205 100644
--- a/art/sprites/hero-hurt.md
+++ b/art/sprites/hero-hurt.md
@@ -1,12 +1,12 @@
-# The student · hurt and interact
+# 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 / 31** · [The student · death →](hero-death.md)
-
+
-**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [The student · death →](hero-death.md)
diff --git a/art/sprites/hero-idle.md b/art/sprites/hero-idle.md
index abcd584..4e627c0 100644
--- a/art/sprites/hero-idle.md
+++ b/art/sprites/hero-idle.md
@@ -1,12 +1,12 @@
-# The student · idle variants
+# 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 / 31** · [The student · run →](hero-run.md)
-
+
-**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [The student · run →](hero-run.md)
diff --git a/art/sprites/hero-ladder.md b/art/sprites/hero-ladder.md
index 35fffed..92fab68 100644
--- a/art/sprites/hero-ladder.md
+++ b/art/sprites/hero-ladder.md
@@ -1,10 +1,10 @@
-# The student · ladder top and bottom
+# 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 / 31** · [Chair · side sweep →](chair-side.md)
-
+
**Review:** Not reviewed yet.
@@ -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 / 31** · [Chair · side sweep →](chair-side.md)
diff --git a/art/sprites/hero-move.md b/art/sprites/hero-move.md
index cd0e74d..e0b5e21 100644
--- a/art/sprites/hero-move.md
+++ b/art/sprites/hero-move.md
@@ -1,12 +1,12 @@
-# The student · walk
+# 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 / 31** · [The student · idle variants →](hero-idle.md)
-
+
-**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [The student · idle variants →](hero-idle.md)
diff --git a/art/sprites/hero-run.md b/art/sprites/hero-run.md
index e361ba3..b0e9f94 100644
--- a/art/sprites/hero-run.md
+++ b/art/sprites/hero-run.md
@@ -1,12 +1,12 @@
-# The student · run
+# 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 / 31** · [The student · hurt and interact →](hero-hurt.md)
-
+
-**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
+**Review:** ✅ Approved 23 Oct by Alexander. Counts match docs/07; exported to game/public/assets/sprites/ with export_atlas.py. The looping previews below are at twice game size.
## In motion
@@ -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 / 31** · [The student · hurt and interact →](hero-hurt.md)
diff --git a/art/sprites/hero.md b/art/sprites/hero.md
index d63239e..9f41122 100644
--- a/art/sprites/hero.md
+++ b/art/sprites/hero.md
@@ -1,12 +1,12 @@
-# The student · hero design
+# 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 / 31** · [The student · walk →](hero-move.md)
-
+
-**Review:** ✅ Approved 23 Oct. Adult 6 ft, face never visible, THD poncho in grey / THD blue / teal with colourful accents, THD crest on the back, scale lineup with Dexter (1.5 ft) and his final form (8.5 ft). Fourth iteration: chibi proportions, a hoodie and a visible face were rejected along the way.
+**Review:** ✅ Approved 23 Oct. Adult 6 ft, face never visible, THD poncho in grey / THD blue / teal with colourful accents, THD crest on the back, scale lineup with Dexter (1.5 ft) and his final form (8.5 ft). Fourth iteration: chibi proportions, a hoodie and a visible face were rejected along the way.
## Brief
@@ -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 / 31** · [The student · walk →](hero-move.md)
diff --git a/art/sprites/images/arena-chain-preview.png b/art/sprites/images/arena-chain-preview.png
new file mode 100644
index 0000000..4b8738c
Binary files /dev/null and b/art/sprites/images/arena-chain-preview.png differ
diff --git a/art/sprites/images/arena-chain.png b/art/sprites/images/arena-chain.png
new file mode 100644
index 0000000..ce40a58
Binary files /dev/null and b/art/sprites/images/arena-chain.png differ
diff --git a/art/sprites/images/arena-platform-preview.png b/art/sprites/images/arena-platform-preview.png
new file mode 100644
index 0000000..535de77
Binary files /dev/null and b/art/sprites/images/arena-platform-preview.png differ
diff --git a/art/sprites/images/arena-platform.png b/art/sprites/images/arena-platform.png
new file mode 100644
index 0000000..52a5859
Binary files /dev/null and b/art/sprites/images/arena-platform.png differ
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/mensa-food.md b/art/sprites/mensa-food.md
index ba4f631..1838a1b 100644
--- a/art/sprites/mensa-food.md
+++ b/art/sprites/mensa-food.md
@@ -1,12 +1,12 @@
-# Mensa · food pickups
+# 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 / 31** · [Prop · coffee machine (save point) →](coffee-machine.md)
-
+
-**Review:** ✅ Approved 24 Oct by Alexander (#154); exported to game/public/assets/sprites/ with export_atlas.py. Third draw: the first two copied only the muffin; anchoring on the approved coffee-machine sheet gave four distinct items. One frame each (the model ignored the sparkle frames; the game adds the sparkle). Muffin 16 × 14, cake 18 × 14, wafer 17 × 13, Leberkäse roll 18 × 18 px – around the documented 16 px.
+**Review:** ✅ Approved 24 Oct by Alexander (#154); exported to game/public/assets/sprites/ with export_atlas.py. Third draw: the first two copied only the muffin; anchoring on the approved coffee-machine sheet gave four distinct items. One frame each (the model ignored the sparkle frames; the game adds the sparkle). Muffin 16 × 14, cake 18 × 14, wafer 17 × 13, Leberkäse roll 18 × 18 px – around the documented 16 px.
## In motion
@@ -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 / 31** · [Prop · coffee machine (save point) →](coffee-machine.md)
diff --git a/art/sprites/menu-key.md b/art/sprites/menu-key.md
index 45aa6b7..02d547f 100644
--- a/art/sprites/menu-key.md
+++ b/art/sprites/menu-key.md
@@ -1,12 +1,12 @@
-# Menu · the Hidden Garden at first light
+# Menu · the Hidden Garden at first light
-[← Dexter · prologue additions](dexter-prologue.md) · [Sprite boards](README.md) · **27 / 28** · [Secret · the rubber chicken →](rubber-chicken.md)
+[← Dexter · prologue additions](dexter-prologue.md) · [Sprite boards](README.md) · **27 / 31** · [Secret · the rubber chicken →](rubber-chicken.md)
-
+
-**Review:** 🟡 Drawn 24 Oct for the prologue menu (workstream 5); exported to game/public/assets/ui/menu-key.png with scripts/export_key_art.py, awaiting Alexander's review.
+**Review:** 🟡 Drawn 24 Oct for the prologue menu (workstream 5); exported to game/public/assets/ui/menu-key.png with scripts/export_key_art.py, awaiting Alexander's review.
## Brief
@@ -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** · [Secret · the rubber chicken →](rubber-chicken.md)
+[← Dexter · prologue additions](dexter-prologue.md) · [Sprite boards](README.md) · **27 / 31** · [Secret · the rubber chicken →](rubber-chicken.md)
diff --git a/art/sprites/pocket-computer.md b/art/sprites/pocket-computer.md
index 2958bb4..8af85ac 100644
--- a/art/sprites/pocket-computer.md
+++ b/art/sprites/pocket-computer.md
@@ -1,12 +1,12 @@
-# Item · pocket computer
+# 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 / 31** · [Mensa · food pickups →](mensa-food.md)
-
+
-**Review:** ✅ Approved 24 Oct by Alexander (#154); exported to game/public/assets/sprites/ with export_atlas.py. Closed case 19 × 10 px (≈ 45 cm at the hero's 76 px = 183 cm): the photographed suitcase, shrunk to carry; open with the green screen 22 px tall. The map screens are the approved UI atlas (#127), so this board has no screen mock-ups.
+**Review:** ✅ Approved 24 Oct by Alexander (#154); exported to game/public/assets/sprites/ with export_atlas.py. Closed case 19 × 10 px (≈ 45 cm at the hero's 76 px = 183 cm): the photographed suitcase, shrunk to carry; open with the green screen 22 px tall. The map screens are the approved UI atlas (#127), so this board has no screen mock-ups.
## In motion
@@ -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 / 31** · [Mensa · food pickups →](mensa-food.md)
diff --git a/art/sprites/rubber-chicken.md b/art/sprites/rubber-chicken.md
index 6f0003b..b13b1a5 100644
--- a/art/sprites/rubber-chicken.md
+++ b/art/sprites/rubber-chicken.md
@@ -1,12 +1,12 @@
-# Secret · the rubber chicken
+# Secret · the rubber chicken
-[← 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 / 31** · [Prop · Jenny's arena wall →](arena-wall.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.
+**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
@@ -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`
-[← 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 / 31** · [Prop · Jenny's arena wall →](arena-wall.md)
diff --git a/art/stages/rusty-creek/arena/girder-reference.png b/art/stages/rusty-creek/arena/girder-reference.png
new file mode 100644
index 0000000..644ee24
Binary files /dev/null and b/art/stages/rusty-creek/arena/girder-reference.png differ
diff --git a/art/stages/rusty-creek/arena/right.png b/art/stages/rusty-creek/arena/right.png
index dfb8568..3c125c0 100644
Binary files a/art/stages/rusty-creek/arena/right.png and b/art/stages/rusty-creek/arena/right.png differ
diff --git a/art/stages/rusty-creek/previews/arena-platforms.png b/art/stages/rusty-creek/previews/arena-platforms.png
index d74d282..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/art/stages/rusty-creek/rc-2-0.anomaly.collision.json b/art/stages/rusty-creek/rc-2-0.anomaly.collision.json
index 82a34e7..4f2cbf6 100644
--- a/art/stages/rusty-creek/rc-2-0.anomaly.collision.json
+++ b/art/stages/rusty-creek/rc-2-0.anomaly.collision.json
@@ -225,21 +225,21 @@
],
[
[
- 1220,
+ 1180,
476
],
[
- 1370,
+ 1379,
476
]
],
[
[
- 1460,
+ 1430,
476
],
[
- 1610,
+ 1629,
476
]
]
diff --git a/docs/meetings/playtest-24-oct.md b/docs/meetings/playtest-24-oct.md
index 5dee97c..5bcc25d 100644
--- a/docs/meetings/playtest-24-oct.md
+++ b/docs/meetings/playtest-24-oct.md
@@ -25,7 +25,7 @@ Vatsal and Lex played the live slice on 24 Oct and recorded what to fix ([A15](t
| J3 | One Jenny hit leaves you nearly dead (3 of 5) → at most half the health | 🔧 |
| J4 | Make it obvious she must crash into something to become hittable – the satisfaction of figuring it out | ✅ crash shake, sound and smoke; stun-ending warning; reboot sparks (fights branch) |
| J5 | Scraps should hurt but must be clearly visible | 🔧 |
-| J6 | An invisible wall in the arena; the arena is "awful to fight in" with limited movement – add platforms in the middle, maybe a slightly higher jump to clear her | ✅ two girder platforms over her floor (`art/stages/rusty-creek/arena/`, an overlay layer): reachable with a full jump, and she drives under them – stand on one and let her pass; the walls are drawn whenever they are closed (#186) |
+| J6 | An invisible wall in the arena; the arena is "awful to fight in" with limited movement – add platforms in the middle, maybe a slightly higher jump to clear her | ✅ two girder platforms hung on chains from the girder over her arena (`scripts/arena_platforms.py`, boards `arena-platform` and `arena-chain`, an overlay layer): reachable with a full jump, and she drives under them – stand on one and let her pass; the walls are drawn whenever they are closed (#186) |
| J7 | After Jenny: a wall at the right end that says "you can't pass until you beat her", not just the camera stopping | ✅ both walls are drawn as rusted hazard-striped barriers whenever they are closed, and the fight (and the walls) start as soon as you are inside the arena (#186); they fade out when she is beaten |
| J8 | Landing on top of an enemy hurts the hero | Intended – no change |
| J9 | Double jump | Rejected (Lex: Metroidvanias give it last or never) |
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/arena.test.ts b/game/src/enemies/arena.test.ts
index 82e9f8b..4b80e0b 100644
--- a/game/src/enemies/arena.test.ts
+++ b/game/src/enemies/arena.test.ts
@@ -3,8 +3,8 @@ import { JENNY_BOX } from './jenny';
import { GRAVITY, JUMP_SPEED } from '../player/jump';
import arena from '../../../art/stages/rusty-creek/rc-2-0.anomaly.collision.json';
-/** The platforms' plate (art/stages/rusty-creek/arena/right.png): beams of the maze's girder, this thick. */
-const BEAM = 32;
+/** The platforms (arena-platform board, scripts/arena_platforms.py): girders this thick, hung on chains. */
+const BEAM = 25;
/** The walk line's height at x (the lowest line under it). */
function floorAt(x: number): number {
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 d93c801..c9ca2c2 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": ["rubber-chicken"]
+ "optional": ["rubber-chicken", "arena-wall"]
}
diff --git a/scripts/arena_platforms.py b/scripts/arena_platforms.py
new file mode 100644
index 0000000..053494a
--- /dev/null
+++ b/scripts/arena_platforms.py
@@ -0,0 +1,97 @@
+"""Jenny's arena platforms (playtest J6): two girder platforms hung on chains from the girder over her arena.
+
+ python scripts/arena_platforms.py # writes art/stages/rusty-creek/arena/right.png and the one-way lines
+
+The art is two sprite boards in the game's look (art/sprites boards.json): `arena-platform`, a short box girder in
+the maze's own material, and `arena-chain`, a rusted chain. Exported at game size (export_atlas.py, into tmp/), they
+are composed into one overlay plate for the right chunk's anomaly state (export.json `05-arena`, "overlay"):
+each platform at PLATFORMS, and a chain from each end of it straight up to the underside of the girder above,
+measured from the traversal plate – so nothing floats. The same numbers write the platforms' one-way lines into
+rc-2-0.anomaly.collision.json (any earlier lines over the arena floor are replaced).
+
+Heights (arena.test.ts checks them): the tops 84–89 px over the walk line – a full jump is 96 px – and the bottoms
+clear Jenny's 56 px body, so she drives under them.
+"""
+
+from __future__ import annotations
+
+import json
+import subprocess
+import sys
+from pathlib import Path
+
+import numpy as np
+from PIL import Image
+
+ROOT = Path(__file__).resolve().parents[1]
+REGION = ROOT / "art" / "stages" / "rusty-creek"
+ATLAS = ROOT / "tmp" / "arena-atlas"
+PLATFORMS = [(1180, 476), (1430, 476)] # left edge, top (chunk px); 202 px wide
+CHAIN_INSET = 18 # chain centre from each platform end (px)
+ARENA = (1126, 1700) # the arena floor's x range: one-way lines in it are the platforms'
+
+
+def frame(board: str, name: str) -> Image.Image:
+ sheet = Image.open(ATLAS / f"{board}.png").convert("RGBA")
+ f = json.loads((ATLAS / f"{board}.json").read_text(encoding="utf-8"))["frames"][name]["frame"]
+ cell = sheet.crop((f["x"], f["y"], f["x"] + f["w"], f["y"] + f["h"]))
+ return cell.crop(cell.getbbox())
+
+
+def period(chain: Image.Image) -> int:
+ """The chain's repeat (two links), from the autocorrelation of its rows' opacity."""
+ rows = (np.asarray(chain.getchannel("A")) > 0).sum(1).astype(float)
+ rows -= rows.mean()
+ scores = {p: float((rows[:-p] * rows[p:]).mean()) for p in range(6, len(rows) // 2)}
+ return max(scores, key=scores.get)
+
+
+def chain_strip(chain: Image.Image, length: int) -> Image.Image:
+ """A straight chain `length` px long: whole periods of the drawn chain, from its middle, stacked."""
+ p = period(chain)
+ top = (chain.height // 2) // p * p
+ unit = chain.crop((0, top, chain.width, top + p))
+ strip = Image.new("RGBA", (chain.width, length), (0, 0, 0, 0))
+ for y in range(0, length, p):
+ strip.alpha_composite(unit, (0, y)) if y + p <= length else strip.alpha_composite(unit.crop((0, 0, chain.width, length - y)), (0, y))
+ return strip
+
+
+def underside(traversal: Image.Image, x: int, below: int) -> int:
+ """The lowest opaque row of the structure above `below` at column x: where the chain is fixed."""
+ column = np.asarray(traversal.getchannel("A"))[:below, x] > 128
+ rows = np.nonzero(column)[0]
+ if not len(rows):
+ raise ValueError(f"nothing to hang a chain from above x {x}")
+ return int(rows[-1]) + 1
+
+
+def build() -> tuple[Image.Image, list]:
+ platform = frame("arena-platform", "platform-00")
+ chain = frame("arena-chain", "chain-00")
+ traversal = Image.open(REGION / "images" / "05-traversal-right.png").convert("RGBA")
+ plate = Image.new("RGBA", traversal.size, (0, 0, 0, 0))
+ lines = []
+ for x, top in PLATFORMS:
+ for cx in (x + CHAIN_INSET, x + platform.width - CHAIN_INSET):
+ hook = underside(traversal, cx, top)
+ plate.alpha_composite(chain_strip(chain, top - hook + 4), (cx - chain.width // 2, hook))
+ plate.alpha_composite(platform, (x, top))
+ lines.append([[x, top], [x + platform.width, top]])
+ return plate, lines
+
+
+def main() -> None:
+ subprocess.run([sys.executable, str(ROOT / "scripts" / "export_atlas.py"), "arena-platform", "arena-chain",
+ "--out", str(ATLAS)], check=True, capture_output=True)
+ plate, lines = build()
+ plate.save(REGION / "arena" / "right.png", optimize=True)
+ path = REGION / "rc-2-0.anomaly.collision.json"
+ data = json.loads(path.read_text(encoding="utf-8"))
+ data["oneWay"] = [l for l in data["oneWay"] if not (ARENA[0] <= l[0][0] <= ARENA[1] and l[0][1] > 400)] + lines
+ path.write_text(json.dumps(data, indent=1, ensure_ascii=False) + "\n", encoding="utf-8")
+ print(f"arena platforms: {len(lines)} on chains -> {plate.size}, one-way lines {lines}")
+
+
+if __name__ == "__main__":
+ main()
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()