diff --git a/art/sprites/README.md b/art/sprites/README.md index f65ebf4..895376a 100644 --- a/art/sprites/README.md +++ b/art/sprites/README.md @@ -18,4 +18,5 @@ Sprite reference boards: the hero sheet (approved) and the animation, enemy, ite | [![FX · falling leaves](images/fx-leaves-preview.png)](fx-leaves.md)
**[FX · falling leaves](fx-leaves.md)** | [![FX · footstep puffs](images/fx-ground-preview.png)](fx-ground.md)
**[FX · footstep puffs](fx-ground.md)** | | [![FX · pond ripples and lily pad](images/fx-water-preview.png)](fx-water.md)
**[FX · pond ripples and lily pad](fx-water.md)** | [![Dexter · prologue additions](images/dexter-prologue-preview.png)](dexter-prologue.md)
**[Dexter · prologue additions](dexter-prologue.md)** | | [![Menu · the Hidden Garden at first light](images/menu-key-preview.png)](menu-key.md)
**[Menu · the Hidden Garden at first light](menu-key.md)** | [![Props · the storm drain's IT details](images/it-props-preview.png)](it-props.md)
**[Props · the storm drain's IT details](it-props.md)** | -| [![Secret · the rubber chicken](images/rubber-chicken-preview.png)](rubber-chicken.md)
**[Secret · the rubber chicken](rubber-chicken.md)** | | +| [![Secret · the rubber chicken](images/rubber-chicken-preview.png)](rubber-chicken.md)
**[Secret · the rubber chicken](rubber-chicken.md)** | [![Prop · Jenny's arena platforms](images/arena-platform-preview.png)](arena-platform.md)
**[Prop · Jenny's arena platforms](arena-platform.md)** | +| [![Prop · the platforms' chains](images/arena-chain-preview.png)](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..cc7b401 --- /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) + + +![Prop · the platforms' chains](images/arena-chain-preview.png) + +**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..4fd3f85 --- /dev/null +++ b/art/sprites/arena-platform.md @@ -0,0 +1,25 @@ +# Prop · Jenny's arena platforms + + +[← Secret · the rubber chicken](rubber-chicken.md) · [Sprite boards](README.md) · **30 / 31** · [Prop · the platforms' chains →](arena-chain.md) + + +![Prop · Jenny's arena platforms](images/arena-platform-preview.png) + +**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` + + +[← Secret · the rubber chicken](rubber-chicken.md) · [Sprite boards](README.md) · **30 / 31** · [Prop · the platforms' chains →](arena-chain.md) + diff --git a/art/sprites/boards.json b/art/sprites/boards.json index 8b22525..714e018 100644 --- a/art/sprites/boards.json +++ b/art/sprites/boards.json @@ -1197,6 +1197,53 @@ ] }, "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-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 521ba86..a8320af 100644 --- a/art/sprites/boss-jenny-2.md +++ b/art/sprites/boss-jenny-2.md @@ -1,7 +1,7 @@ # Boss · Jenny, stunned and debris -[← Boss · Jenny, attacks](boss-jenny.md) · [Sprite boards](README.md) · **17 / 29** · [Item · pocket computer →](pocket-computer.md) +[← Boss · Jenny, attacks](boss-jenny.md) · [Sprite boards](README.md) · **17 / 31** · [Item · pocket computer →](pocket-computer.md) ![Boss · Jenny, stunned and debris](images/boss-jenny-2-preview.png) @@ -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 / 29** · [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 f03ded6..84e15dc 100644 --- a/art/sprites/boss-jenny.md +++ b/art/sprites/boss-jenny.md @@ -1,7 +1,7 @@ # Boss · Jenny, attacks -[← FX · black fluid](fx.md) · [Sprite boards](README.md) · **16 / 29** · [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) ![Boss · Jenny, attacks](images/boss-jenny-preview.png) @@ -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 / 29** · [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 f3fdbc1..523a2ab 100644 --- a/art/sprites/chair-block.md +++ b/art/sprites/chair-block.md @@ -1,7 +1,7 @@ # Chair · block -[← Chair · overhead slam](chair-overhead.md) · [Sprite boards](README.md) · **11 / 29** · [Enemy · drone →](enemy-drone.md) +[← Chair · overhead slam](chair-overhead.md) · [Sprite boards](README.md) · **11 / 31** · [Enemy · drone →](enemy-drone.md) ![Chair · block](images/chair-block-preview.png) @@ -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 / 29** · [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 4b9385c..b26b124 100644 --- a/art/sprites/chair-overhead.md +++ b/art/sprites/chair-overhead.md @@ -1,7 +1,7 @@ # Chair · overhead slam -[← Chair · side sweep](chair-side.md) · [Sprite boards](README.md) · **10 / 29** · [Chair · block →](chair-block.md) +[← Chair · side sweep](chair-side.md) · [Sprite boards](README.md) · **10 / 31** · [Chair · block →](chair-block.md) ![Chair · overhead slam](images/chair-overhead-preview.png) @@ -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 / 29** · [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 83e3229..22e0bd8 100644 --- a/art/sprites/chair-side.md +++ b/art/sprites/chair-side.md @@ -1,7 +1,7 @@ # Chair · side sweep -[← The student · ladder top and bottom](hero-ladder.md) · [Sprite boards](README.md) · **9 / 29** · [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) ![Chair · side sweep](images/chair-side-preview.png) @@ -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 / 29** · [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 576d057..ce15838 100644 --- a/art/sprites/coffee-machine.md +++ b/art/sprites/coffee-machine.md @@ -1,7 +1,7 @@ # Prop · coffee machine (save point) -[← Mensa · food pickups](mensa-food.md) · [Sprite boards](README.md) · **20 / 29** · [Dexter · moves →](dexter.md) +[← Mensa · food pickups](mensa-food.md) · [Sprite boards](README.md) · **20 / 31** · [Dexter · moves →](dexter.md) ![Prop · coffee machine (save point)](images/coffee-machine-preview.png) @@ -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 / 29** · [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 dbba39d..4434bb1 100644 --- a/art/sprites/dexter-acts.md +++ b/art/sprites/dexter-acts.md @@ -1,7 +1,7 @@ # Dexter · gestures -[← Dexter · moves](dexter.md) · [Sprite boards](README.md) · **22 / 29** · [FX · falling leaves →](fx-leaves.md) +[← Dexter · moves](dexter.md) · [Sprite boards](README.md) · **22 / 31** · [FX · falling leaves →](fx-leaves.md) ![Dexter · gestures](images/dexter-acts-preview.png) @@ -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 / 29** · [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 3feda47..2505f77 100644 --- a/art/sprites/dexter-prologue.md +++ b/art/sprites/dexter-prologue.md @@ -1,7 +1,7 @@ # Dexter · prologue additions -[← FX · pond ripples and lily pad](fx-water.md) · [Sprite boards](README.md) · **26 / 29** · [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) ![Dexter · prologue additions](images/dexter-prologue-preview.png) @@ -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 / 29** · [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 46db0a2..dd76632 100644 --- a/art/sprites/dexter.md +++ b/art/sprites/dexter.md @@ -1,7 +1,7 @@ # Dexter · moves -[← Prop · coffee machine (save point)](coffee-machine.md) · [Sprite boards](README.md) · **21 / 29** · [Dexter · gestures →](dexter-acts.md) +[← Prop · coffee machine (save point)](coffee-machine.md) · [Sprite boards](README.md) · **21 / 31** · [Dexter · gestures →](dexter-acts.md) ![Dexter · moves](images/dexter-preview.png) @@ -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 / 29** · [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 38906db..fdd1ee2 100644 --- a/art/sprites/enemy-crab.md +++ b/art/sprites/enemy-crab.md @@ -1,7 +1,7 @@ # Enemy · crab -[← Enemy · rover](enemy-rover.md) · [Sprite boards](README.md) · **14 / 29** · [FX · black fluid →](fx.md) +[← Enemy · rover](enemy-rover.md) · [Sprite boards](README.md) · **14 / 31** · [FX · black fluid →](fx.md) ![Enemy · crab](images/enemy-crab-preview.png) @@ -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 / 29** · [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 4d2b4b1..6f94a3f 100644 --- a/art/sprites/enemy-drone.md +++ b/art/sprites/enemy-drone.md @@ -1,7 +1,7 @@ # Enemy · drone -[← Chair · block](chair-block.md) · [Sprite boards](README.md) · **12 / 29** · [Enemy · rover →](enemy-rover.md) +[← Chair · block](chair-block.md) · [Sprite boards](README.md) · **12 / 31** · [Enemy · rover →](enemy-rover.md) ![Enemy · drone](images/enemy-drone-preview.png) @@ -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 / 29** · [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 8976818..ce72f0f 100644 --- a/art/sprites/enemy-rover.md +++ b/art/sprites/enemy-rover.md @@ -1,7 +1,7 @@ # Enemy · rover -[← Enemy · drone](enemy-drone.md) · [Sprite boards](README.md) · **13 / 29** · [Enemy · crab →](enemy-crab.md) +[← Enemy · drone](enemy-drone.md) · [Sprite boards](README.md) · **13 / 31** · [Enemy · crab →](enemy-crab.md) ![Enemy · rover](images/enemy-rover-preview.png) @@ -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 / 29** · [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 717f2cb..6f1fbc8 100644 --- a/art/sprites/fx-ground.md +++ b/art/sprites/fx-ground.md @@ -1,7 +1,7 @@ # FX · footstep puffs -[← FX · falling leaves](fx-leaves.md) · [Sprite boards](README.md) · **24 / 29** · [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) ![FX · footstep puffs](images/fx-ground-preview.png) @@ -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 / 29** · [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 f6b0423..a09a308 100644 --- a/art/sprites/fx-leaves.md +++ b/art/sprites/fx-leaves.md @@ -1,7 +1,7 @@ # FX · falling leaves -[← Dexter · gestures](dexter-acts.md) · [Sprite boards](README.md) · **23 / 29** · [FX · footstep puffs →](fx-ground.md) +[← Dexter · gestures](dexter-acts.md) · [Sprite boards](README.md) · **23 / 31** · [FX · footstep puffs →](fx-ground.md) ![FX · falling leaves](images/fx-leaves-preview.png) @@ -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 / 29** · [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 0955222..718436e 100644 --- a/art/sprites/fx-water.md +++ b/art/sprites/fx-water.md @@ -1,7 +1,7 @@ # FX · pond ripples and lily pad -[← FX · footstep puffs](fx-ground.md) · [Sprite boards](README.md) · **25 / 29** · [Dexter · prologue additions →](dexter-prologue.md) +[← FX · footstep puffs](fx-ground.md) · [Sprite boards](README.md) · **25 / 31** · [Dexter · prologue additions →](dexter-prologue.md) ![FX · pond ripples and lily pad](images/fx-water-preview.png) @@ -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 / 29** · [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 c445175..d129282 100644 --- a/art/sprites/fx.md +++ b/art/sprites/fx.md @@ -1,7 +1,7 @@ # FX · black fluid -[← Enemy · crab](enemy-crab.md) · [Sprite boards](README.md) · **15 / 29** · [Boss · Jenny, attacks →](boss-jenny.md) +[← Enemy · crab](enemy-crab.md) · [Sprite boards](README.md) · **15 / 31** · [Boss · Jenny, attacks →](boss-jenny.md) ![FX · black fluid](images/fx-preview.png) @@ -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 / 29** · [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 cd605b5..a79eca9 100644 --- a/art/sprites/hero-air.md +++ b/art/sprites/hero-air.md @@ -1,7 +1,7 @@ # The student · jump, fall, climb -[← The student · death](hero-death.md) · [Sprite boards](README.md) · **7 / 29** · [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) ![The student · jump, fall, climb](images/hero-air-preview.png) @@ -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 / 29** · [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 50b1c8e..b9bcb3f 100644 --- a/art/sprites/hero-death.md +++ b/art/sprites/hero-death.md @@ -1,7 +1,7 @@ # The student · death -[← The student · hurt and interact](hero-hurt.md) · [Sprite boards](README.md) · **6 / 29** · [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) ![The student · death](images/hero-death-preview.png) @@ -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 / 29** · [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 6204a09..9199c1b 100644 --- a/art/sprites/hero-hurt.md +++ b/art/sprites/hero-hurt.md @@ -1,7 +1,7 @@ # The student · hurt and interact -[← The student · run](hero-run.md) · [Sprite boards](README.md) · **5 / 29** · [The student · death →](hero-death.md) +[← The student · run](hero-run.md) · [Sprite boards](README.md) · **5 / 31** · [The student · death →](hero-death.md) ![The student · hurt and interact](images/hero-hurt-preview.png) @@ -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 / 29** · [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 e67d68e..b58e4f7 100644 --- a/art/sprites/hero-idle.md +++ b/art/sprites/hero-idle.md @@ -1,7 +1,7 @@ # The student · idle variants -[← The student · walk](hero-move.md) · [Sprite boards](README.md) · **3 / 29** · [The student · run →](hero-run.md) +[← The student · walk](hero-move.md) · [Sprite boards](README.md) · **3 / 31** · [The student · run →](hero-run.md) ![The student · idle variants](images/hero-idle-preview.png) @@ -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 / 29** · [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 7f5fa05..2616b9c 100644 --- a/art/sprites/hero-ladder.md +++ b/art/sprites/hero-ladder.md @@ -1,7 +1,7 @@ # The student · ladder top and bottom -[← The student · jump, fall, climb](hero-air.md) · [Sprite boards](README.md) · **8 / 29** · [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) ![The student · ladder top and bottom](images/hero-ladder-preview.png) @@ -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 / 29** · [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 b710510..fd96aa5 100644 --- a/art/sprites/hero-move.md +++ b/art/sprites/hero-move.md @@ -1,7 +1,7 @@ # The student · walk -[← The student · hero design](hero.md) · [Sprite boards](README.md) · **2 / 29** · [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) ![The student · walk](images/hero-move-preview.png) @@ -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 / 29** · [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 8987cd1..6c8a070 100644 --- a/art/sprites/hero-run.md +++ b/art/sprites/hero-run.md @@ -1,7 +1,7 @@ # The student · run -[← The student · idle variants](hero-idle.md) · [Sprite boards](README.md) · **4 / 29** · [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) ![The student · run](images/hero-run-preview.png) @@ -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 / 29** · [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 2580a90..687c795 100644 --- a/art/sprites/hero.md +++ b/art/sprites/hero.md @@ -1,7 +1,7 @@ # The student · hero design -[← Sprite boards](README.md) · [Sprite boards](README.md) · **1 / 29** · [The student · walk →](hero-move.md) +[← Sprite boards](README.md) · [Sprite boards](README.md) · **1 / 31** · [The student · walk →](hero-move.md) ![The student · hero design](images/hero-preview.png) @@ -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 / 29** · [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/it-props.md b/art/sprites/it-props.md index ce3b0a7..858c6c3 100644 --- a/art/sprites/it-props.md +++ b/art/sprites/it-props.md @@ -1,7 +1,7 @@ # Props · the storm drain's IT details -[← Menu · the Hidden Garden at first light](menu-key.md) · [Sprite boards](README.md) · **28 / 29** · [Secret · the rubber chicken →](rubber-chicken.md) +[← Menu · the Hidden Garden at first light](menu-key.md) · [Sprite boards](README.md) · **28 / 31** · [Secret · the rubber chicken →](rubber-chicken.md) ![Props · the storm drain's IT details](images/it-props-preview.png) @@ -27,5 +27,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/it-props.png` · rebuild: `python scripts/make_boards.py draw it-props --redo --config art/sprites/boards.json` -[← Menu · the Hidden Garden at first light](menu-key.md) · [Sprite boards](README.md) · **28 / 29** · [Secret · the rubber chicken →](rubber-chicken.md) +[← Menu · the Hidden Garden at first light](menu-key.md) · [Sprite boards](README.md) · **28 / 31** · [Secret · the rubber chicken →](rubber-chicken.md) diff --git a/art/sprites/mensa-food.md b/art/sprites/mensa-food.md index adb77e1..9be2e1e 100644 --- a/art/sprites/mensa-food.md +++ b/art/sprites/mensa-food.md @@ -1,7 +1,7 @@ # Mensa · food pickups -[← Item · pocket computer](pocket-computer.md) · [Sprite boards](README.md) · **19 / 29** · [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) ![Mensa · food pickups](images/mensa-food-preview.png) @@ -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 / 29** · [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 15974b5..31c7b9a 100644 --- a/art/sprites/menu-key.md +++ b/art/sprites/menu-key.md @@ -1,7 +1,7 @@ # Menu · the Hidden Garden at first light -[← Dexter · prologue additions](dexter-prologue.md) · [Sprite boards](README.md) · **27 / 29** · [Props · the storm drain's IT details →](it-props.md) +[← Dexter · prologue additions](dexter-prologue.md) · [Sprite boards](README.md) · **27 / 31** · [Props · the storm drain's IT details →](it-props.md) ![Menu · the Hidden Garden at first light](images/menu-key-preview.png) @@ -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 / 29** · [Props · the storm drain's IT details →](it-props.md) +[← Dexter · prologue additions](dexter-prologue.md) · [Sprite boards](README.md) · **27 / 31** · [Props · the storm drain's IT details →](it-props.md) diff --git a/art/sprites/pocket-computer.md b/art/sprites/pocket-computer.md index c77a836..2395a6f 100644 --- a/art/sprites/pocket-computer.md +++ b/art/sprites/pocket-computer.md @@ -1,7 +1,7 @@ # Item · pocket computer -[← Boss · Jenny, stunned and debris](boss-jenny-2.md) · [Sprite boards](README.md) · **18 / 29** · [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) ![Item · pocket computer](images/pocket-computer-preview.png) @@ -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 / 29** · [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 79a705d..cc998a9 100644 --- a/art/sprites/rubber-chicken.md +++ b/art/sprites/rubber-chicken.md @@ -1,7 +1,7 @@ # Secret · the rubber chicken -[← Props · the storm drain's IT details](it-props.md) · [Sprite boards](README.md) · **29 / 29** · [Sprite boards →](README.md) +[← Props · the storm drain's IT details](it-props.md) · [Sprite boards](README.md) · **29 / 31** · [Prop · Jenny's arena platforms →](arena-platform.md) ![Secret · the rubber chicken](images/rubber-chicken-preview.png) @@ -20,5 +20,5 @@ On a flat, solid, pure magenta #FF00FF background (no paper colour, no ground sh Model `gpt-image-2.5-sunburst` · pixel factor 2 · source image `images/rubber-chicken.png` · rebuild: `python scripts/make_boards.py draw rubber-chicken --redo --config art/sprites/boards.json` -[← Props · the storm drain's IT details](it-props.md) · [Sprite boards](README.md) · **29 / 29** · [Sprite boards →](README.md) +[← Props · the storm drain's IT details](it-props.md) · [Sprite boards](README.md) · **29 / 31** · [Prop · Jenny's arena platforms →](arena-platform.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 new file mode 100644 index 0000000..3c125c0 Binary files /dev/null and b/art/stages/rusty-creek/arena/right.png differ diff --git a/art/stages/rusty-creek/export.json b/art/stages/rusty-creek/export.json index 2290e5a..b4258d2 100644 --- a/art/stages/rusty-creek/export.json +++ b/art/stages/rusty-creek/export.json @@ -65,6 +65,14 @@ "file": "images/04-water-{name}.png", "z": 4, "scroll": 1.0 + }, + "05-arena": { + "file": "arena/{name}.png", + "z": 5, + "scroll": 1.0, + "optional": true, + "why": "Jenny's arena platforms (playtest J6): two beams of the maze's own girder, 84 px over her floor – reachable with a full jump, high enough that she drives under them. An overlay: drawn in the game, left out of the masters (creek_layers.py pins them) and of --verify.", + "overlay": true } }, "states": { @@ -73,6 +81,7 @@ "03-bank", "02-supports", "05-traversal", + "05-arena", "04-water" ], "clean": [ diff --git a/art/stages/rusty-creek/previews/arena-platforms.png b/art/stages/rusty-creek/previews/arena-platforms.png new file mode 100644 index 0000000..2223980 Binary files /dev/null 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 bde4c10..4f2cbf6 100644 --- a/art/stages/rusty-creek/rc-2-0.anomaly.collision.json +++ b/art/stages/rusty-creek/rc-2-0.anomaly.collision.json @@ -222,6 +222,26 @@ 1852, 129 ] + ], + [ + [ + 1180, + 476 + ], + [ + 1379, + 476 + ] + ], + [ + [ + 1430, + 476 + ], + [ + 1629, + 476 + ] ] ], "climb": [ diff --git a/docs/meetings/playtest-24-oct.md b/docs/meetings/playtest-24-oct.md index e8ebb02..fd3a507 100644 --- a/docs/meetings/playtest-24-oct.md +++ b/docs/meetings/playtest-24-oct.md @@ -25,8 +25,8 @@ 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 | 🔧 hitboxes; platforms are stage data ⏭ | -| J7 | After Jenny: a wall at the right end that says "you can't pass until you beat her", not just the camera stopping | ✅ a far wall closes the arena during the fight (fights branch); a visible barrier ⏭ | +| 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/src/enemies/arena.test.ts b/game/src/enemies/arena.test.ts new file mode 100644 index 0000000..4b80e0b --- /dev/null +++ b/game/src/enemies/arena.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; +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 (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 { + let best = -Infinity; + for (const line of arena.walk) for (let i = 1; i < line.length; i++) { + const [[ax, ay], [bx, by]] = [line[i - 1], line[i]]; + if (x >= ax && x <= bx) best = Math.max(best, ay + (by - ay) * (x - ax) / Math.max(1, bx - ax)); + } + return best; +} + +describe("Jenny's arena platforms (playtest J6)", () => { + const gate = arena.interact.find(b => b.kind === 'boss-gate')!; + const jenny = arena.spawn.find(s => s.kind === 'jenny')!; + const platforms = arena.oneWay.filter(line => line[0][0] > gate.x && line[0][1] > jenny.y - 120); + + it('has two platforms over her floor', () => { + expect(platforms).toHaveLength(2); + }); + + it('are reachable with a full jump, and high enough that she drives under them', () => { + const jump = (JUMP_SPEED * JUMP_SPEED) / (2 * GRAVITY); + for (const [[x0, y], [x1]] of platforms) { + // The hero jumps from the walk line under the platform; she drives flat at her spawn's height. + const ground = Math.min(floorAt(x0), floorAt(x1)); + expect(ground - y, `platform at x ${x0}`).toBeLessThanOrEqual(jump - 4); + expect(y + BEAM, `platform at x ${x0}`).toBeLessThanOrEqual(jenny.y - JENNY_BOX.h); + } + }); +}); 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/backdrop_check.py b/scripts/backdrop_check.py new file mode 100644 index 0000000..801d270 --- /dev/null +++ b/scripts/backdrop_check.py @@ -0,0 +1,76 @@ +"""Check transparent art over the debug backdrops: purple, then white (docs/09, "every layer is whole, checked on purple"). + + python scripts/backdrop_check.py [...] [--out tmp/backdrop] [--holes] + +For every RGBA image: a sheet with the image over the game's debug purple (#9b30ff) and over white, side by side, +in --out, and a line of findings: +- key: pixels left of the magenta key (#ff00ff and its near shades) – they show as pink specks on any backdrop; +- soft: semi-transparent pixels (alpha 1–254) – a hard-edged pixel sprite has none; a glow or a blend edge does; +- holes (--holes, for plates): fully transparent pixels enclosed by opaque ones in the same row and column – + the cut-outs that show as soon as a layer moves. +Exit 1 when any image has key pixels (or holes, with --holes); soft pixels are reported, not failed. +""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +import numpy as np +from PIL import Image + +PURPLE = (0x9B, 0x30, 0xFF) +WHITE = (255, 255, 255) + + +def findings(image: Image.Image, holes: bool = False) -> dict[str, int]: + rgba = np.asarray(image.convert("RGBA")).astype(int) + r, g, b, a = (rgba[..., i] for i in range(4)) + out = { + "key": int(((a > 0) & (r > 200) & (b > 200) & (g < 90)).sum()), + "soft": int(((a > 0) & (a < 255)).sum()), + } + if holes: + solid = a > 0 + # enclosed: opaque somewhere left and right in its row, and above and below in its column + left = np.maximum.accumulate(solid, axis=1) + right = np.maximum.accumulate(solid[:, ::-1], axis=1)[:, ::-1] + up = np.maximum.accumulate(solid, axis=0) + down = np.maximum.accumulate(solid[::-1], axis=0)[::-1] + out["holes"] = int((~solid & left & right & up & down).sum()) + return out + + +def sheet(image: Image.Image, scale: int = 1) -> Image.Image: + rgba = image.convert("RGBA") + w, h = rgba.size + out = Image.new("RGB", (w * 2 + 8, h), (40, 40, 40)) + for i, colour in enumerate((PURPLE, WHITE)): + bg = Image.new("RGBA", (w, h), colour + (255,)) + bg.alpha_composite(rgba) + out.paste(bg.convert("RGB"), (i * (w + 8), 0)) + return out.resize((out.width * scale, out.height * scale), Image.Resampling.NEAREST) if scale > 1 else out + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("images", nargs="+", type=Path) + parser.add_argument("--out", type=Path, default=Path("tmp/backdrop")) + parser.add_argument("--holes", action="store_true", help="also count enclosed transparent pixels (plates)") + args = parser.parse_args() + args.out.mkdir(parents=True, exist_ok=True) + failed = False + for path in args.images: + image = Image.open(path) + found = findings(image, args.holes) + scale = 4 if max(image.size) <= 200 else 2 if max(image.size) <= 600 else 1 + sheet(image, scale).save(args.out / f"{path.stem}.png") + bad = found["key"] or found.get("holes", 0) + failed |= bool(bad) + print(f"{'FAIL' if bad else 'ok '} {path}: " + ", ".join(f"{k} {v}" for k, v in found.items())) + sys.exit(1 if failed else 0) + + +if __name__ == "__main__": + main() diff --git a/scripts/creek_layers.py b/scripts/creek_layers.py index 1a2afc4..f338a1a 100644 --- a/scripts/creek_layers.py +++ b/scripts/creek_layers.py @@ -106,6 +106,8 @@ def compose(plates, architecture=None): config = read(BASE / 'export.json') out = blank() for role in config['states']['clean' if architecture is not None else 'anomaly']: + if config['layers'][role].get('overlay'): + continue # drawn over the art in the game (Jenny's arena platforms), not part of the masters layer = architecture if role == 'clean-architecture' else plates[role] dy = config['layers'][role].get('dy', 0) if dy: # a plate painted off its ground, moved down onto it diff --git a/scripts/export_stage.py b/scripts/export_stage.py index b02841b..ab8fa0f 100644 --- a/scripts/export_stage.py +++ b/scripts/export_stage.py @@ -18,6 +18,7 @@ # overlap or leave gaps), cut into chunk-wide tiles: "file" takes {tile}, chunk j carries tile j; # "rest": the chunk's own plate it was quilted from, which --verify rebuilds the chunk with # "optional": true – a chunk without the file just doesn't draw that layer (the anomaly's tears) + # "overlay": true – drawn in the game over the art, not part of the composed picture --verify compares "aliases": "media-sources.json"} # optional: shared plates Usage: @@ -167,8 +168,10 @@ def rest(pattern: str, name: str) -> Path: canvas = Image.new("RGBA", tuple(stage["chunkSize"]), (0, 0, 0, 0)) for layer_id in config["states"][state]: layer = config["layers"][layer_id] - if layer.get("region") and chunk["y"] != 0 or layer_id not in exported and not layer.get("region"): - continue # not drawn for this chunk (a row away from the level, or a layer it has no pixels in) + if (layer.get("overlay") or layer.get("region") and chunk["y"] != 0 + or layer_id not in exported and not layer.get("region")): + continue # not drawn for this chunk (a row away from the level, a layer it has no pixels in), + # or drawn over the art in the game, not part of the composed picture path = rests[layer_id] if layer.get("region") else exported[layer_id] plate = Image.open(path).convert("RGBA") if layer.get("dy"): diff --git a/scripts/test_backdrop_check.py b/scripts/test_backdrop_check.py new file mode 100644 index 0000000..ec01656 --- /dev/null +++ b/scripts/test_backdrop_check.py @@ -0,0 +1,31 @@ +"""Offline checks for backdrop_check.py: key specks, soft pixels and enclosed holes are counted.""" +import unittest + +from PIL import Image + +import backdrop_check as bc + + +class BackdropCheckTests(unittest.TestCase): + def test_a_clean_hard_sprite_has_nothing(self): + im = Image.new('RGBA', (10, 10), (0, 0, 0, 0)) + im.paste((40, 90, 160, 255), (2, 2, 8, 8)) + self.assertEqual(bc.findings(im, holes=True), {'key': 0, 'soft': 0, 'holes': 0}) + + def test_key_specks_soft_edges_and_holes_are_counted(self): + im = Image.new('RGBA', (10, 10), (0, 0, 0, 0)) + im.paste((40, 90, 160, 255), (1, 1, 9, 9)) + im.putpixel((4, 4), (0, 0, 0, 0)) # a hole inside + im.putpixel((1, 1), (250, 10, 250, 255)) # a magenta speck + im.putpixel((8, 8), (40, 90, 160, 128)) # a soft pixel + self.assertEqual(bc.findings(im, holes=True), {'key': 1, 'soft': 1, 'holes': 1}) + + def test_the_sheet_shows_purple_then_white(self): + im = Image.new('RGBA', (4, 4), (0, 0, 0, 0)) + out = bc.sheet(im) + self.assertEqual(out.getpixel((0, 0)), bc.PURPLE) + self.assertEqual(out.getpixel((4 + 8, 0)), bc.WHITE) + + +if __name__ == '__main__': + unittest.main()