fuzzyworld

explodorama / commit

Outer wall as a bordered stone frame

d05d464d581aa8fa10491fd07a2b8673b0de4c2c · Pavel Safronov · 2026-10-22 09:43:03 -0700 · parents beb9f2b7ca3f

Claude Code usage: this commit ~48.6K out / ~4.8M cached (first commit of its session: everything since it began) · cost unknown (no per-model trailer) · session 2 tally 134 in / 48.6K out / 4.8M cached · usage by commit and session

Outer wall as a bordered stone frame

The outer wall uses the platformer pack's bordered terrain_stone pieces
(strips and rounded corners), so a dark outline runs along both sides of
the ring; the inner corners, which the pack has no piece for, get a small
square in the outline colour. Floors are drawn before walls, which removes
thin light seams that ran through the stone.

Usage: 134 in / 48.6K out / 4.8M cached (session tally)
rtk:   1.8M saved, 25.7% over 2706 cmds (global/lifetime)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
 PLAN.md                                            |   2 +-
 assets/kenney/terrain_stone_block_bottom_left.png  | Bin 0 -> 1178 bytes
 assets/kenney/terrain_stone_block_bottom_right.png | Bin 0 -> 1167 bytes
 assets/kenney/terrain_stone_block_center.png       | Bin 920 -> 0 bytes
 assets/kenney/terrain_stone_block_top_left.png     | Bin 0 -> 1320 bytes
 assets/kenney/terrain_stone_block_top_right.png    | Bin 0 -> 1331 bytes
 assets/kenney/terrain_stone_horizontal_middle.png  | Bin 0 -> 978 bytes
 assets/kenney/terrain_stone_vertical_middle.png    | Bin 0 -> 938 bytes
 decisions.md                                       |  15 +++++++++++++
 docs/screenshots/auto-move.png                     | Bin 249023 -> 249559 bytes
 docs/screenshots/bomb-picked.png                   | Bin 248985 -> 249435 bytes
 docs/screenshots/charges.png                       | Bin 276870 -> 277032 bytes
 docs/screenshots/countdown.png                     | Bin 253578 -> 253982 bytes
 docs/screenshots/death.png                         | Bin 198263 -> 199246 bytes
 docs/screenshots/desktop.png                       | Bin 593333 -> 598296 bytes
 docs/screenshots/dev.png                           | Bin 266198 -> 267516 bytes
 docs/screenshots/enemies.png                       | Bin 252627 -> 252762 bytes
 docs/screenshots/info-enemy.png                    | Bin 192237 -> 191188 bytes
 docs/screenshots/info-hud.png                      | Bin 182314 -> 181634 bytes
 docs/screenshots/lingering.png                     | Bin 257820 -> 258424 bytes
 docs/screenshots/long-press.png                    | Bin 251937 -> 252346 bytes
 docs/screenshots/menu.png                          | Bin 190391 -> 189637 bytes
 docs/screenshots/new-run.png                       | Bin 194300 -> 193766 bytes
 docs/screenshots/pierce-decoy.png                  | Bin 284653 -> 284698 bytes
 docs/screenshots/power-up.png                      | Bin 261831 -> 261868 bytes
 docs/screenshots/run.png                           | Bin 237216 -> 239526 bytes
 docs/screenshots/scrolled.png                      | Bin 261157 -> 259762 bytes
 docs/screenshots/small-desktop.png                 | Bin 279278 -> 280692 bytes
 docs/screenshots/small.png                         | Bin 189686 -> 194117 bytes
 docs/screenshots/start.png                         | Bin 247835 -> 248312 bytes
 src/render/board.ts                                |  18 +++++++++++++++-
 src/render/sprites.ts                              |  24 +++++++++++++++++----
 src/ui/info.ts                                     |   4 ++--
 33 files changed, 55 insertions(+), 8 deletions(-)

diff --git a/PLAN.md b/PLAN.md
index 7f7f023..07a5083 100644
--- a/PLAN.md
+++ b/PLAN.md
@@ -479,7 +479,7 @@ Sprite assignments (✅ = drawn now; the rest are copied and waiting for their f
 | Thing | Sprite(s) in `assets/kenney/` |
 |---|---|
 | ✅ player | `character_green_idle`; `character_green_hit` when dead; mirrored when facing left. Other green frames (walk, jump, duck, climb, front) are there for animation. |
-| ✅ outer wall | `terrain_stone_block_center` |
+| ✅ outer wall | a bordered stone frame (2026-10-22): `terrain_stone_horizontal_middle` top and bottom, `terrain_stone_vertical_middle` left and right, `terrain_stone_block_top_left` / `_top_right` / `_bottom_left` / `_bottom_right` corners; the four inner corners, which the pack has no piece for, get a small square in the outline colour |
 | ✅ pillars | `block_blue`, `block_green`, `block_red`, `block_yellow`, cycling by level |
 | ✅ soft block | `block_planks` (wooden crate) |
 | ✅ bomb | `bomb`; flashes to `bomb_active` on its last turn; timer drawn in `hud_character_0`–`9` |
diff --git a/assets/kenney/terrain_stone_block_bottom_left.png b/assets/kenney/terrain_stone_block_bottom_left.png
new file mode 100644
index 0000000..8f5f500
Binary files /dev/null and b/assets/kenney/terrain_stone_block_bottom_left.png differ
diff --git a/assets/kenney/terrain_stone_block_bottom_right.png b/assets/kenney/terrain_stone_block_bottom_right.png
new file mode 100644
index 0000000..a714beb
Binary files /dev/null and b/assets/kenney/terrain_stone_block_bottom_right.png differ
diff --git a/assets/kenney/terrain_stone_block_center.png b/assets/kenney/terrain_stone_block_center.png
deleted file mode 100644
index 23ef36a..0000000
Binary files a/assets/kenney/terrain_stone_block_center.png and /dev/null differ
diff --git a/assets/kenney/terrain_stone_block_top_left.png b/assets/kenney/terrain_stone_block_top_left.png
new file mode 100644
index 0000000..1992d2c
Binary files /dev/null and b/assets/kenney/terrain_stone_block_top_left.png differ
diff --git a/assets/kenney/terrain_stone_block_top_right.png b/assets/kenney/terrain_stone_block_top_right.png
new file mode 100644
index 0000000..a609052
Binary files /dev/null and b/assets/kenney/terrain_stone_block_top_right.png differ
diff --git a/assets/kenney/terrain_stone_horizontal_middle.png b/assets/kenney/terrain_stone_horizontal_middle.png
new file mode 100644
index 0000000..41178b1
Binary files /dev/null and b/assets/kenney/terrain_stone_horizontal_middle.png differ
diff --git a/assets/kenney/terrain_stone_vertical_middle.png b/assets/kenney/terrain_stone_vertical_middle.png
new file mode 100644
index 0000000..937f1e4
Binary files /dev/null and b/assets/kenney/terrain_stone_vertical_middle.png differ
diff --git a/decisions.md b/decisions.md
index de8f1ba..b9f0115 100644
--- a/decisions.md
+++ b/decisions.md
@@ -237,3 +237,18 @@ Made by me:
   each other (from memory, not verified). Listed as open question 10 in PLAN.md §14.
 - **Exclusive power-ups** are written up as an idea with its conflicts (PLAN.md §5b), not built.
 
+### 2026-10-22 — The outer wall gets borders
+
+The user found the walls too uniform and asked for the pack's bordered wall tiles.
+
+- **The outer wall is a stone frame**: the edge, corner and strip pieces of the pack's
+  `terrain_stone` set, so a dark outline runs along both sides of the ring (towards the board and
+  towards the screen edge) with rounded outer corners. Top and bottom strips show the pack's dark
+  ledge on their upper side, as in the pack. `terrain_stone_block_center` is no longer used and
+  was removed from `assets/kenney/`.
+- **Inner corners are drawn squares**: the pack has no inner-corner piece, so where two sides of
+  the frame meet the outline would have a gap; a square in the outline colour fills it.
+- **Pillars are unchanged**: the coloured blocks already have their own outline.
+- **Floors are drawn before walls** (render only): each tile is drawn a hair oversized to hide
+  seams, and a floor drawn after its neighbouring wall painted over that wall's edge, leaving
+  thin light lines through the stone.
diff --git a/docs/screenshots/auto-move.png b/docs/screenshots/auto-move.png
index ce373cf..8ce407a 100644
Binary files a/docs/screenshots/auto-move.png and b/docs/screenshots/auto-move.png differ
diff --git a/docs/screenshots/bomb-picked.png b/docs/screenshots/bomb-picked.png
index 2781573..496cb17 100644
Binary files a/docs/screenshots/bomb-picked.png and b/docs/screenshots/bomb-picked.png differ
diff --git a/docs/screenshots/charges.png b/docs/screenshots/charges.png
index 70222b6..4ed832f 100644
Binary files a/docs/screenshots/charges.png and b/docs/screenshots/charges.png differ
diff --git a/docs/screenshots/countdown.png b/docs/screenshots/countdown.png
index 06b899d..c6ec468 100644
Binary files a/docs/screenshots/countdown.png and b/docs/screenshots/countdown.png differ
diff --git a/docs/screenshots/death.png b/docs/screenshots/death.png
index 44fc872..ce02f25 100644
Binary files a/docs/screenshots/death.png and b/docs/screenshots/death.png differ
diff --git a/docs/screenshots/desktop.png b/docs/screenshots/desktop.png
index 47affca..c226b4c 100644
Binary files a/docs/screenshots/desktop.png and b/docs/screenshots/desktop.png differ
diff --git a/docs/screenshots/dev.png b/docs/screenshots/dev.png
index d6007a1..2eb22cf 100644
Binary files a/docs/screenshots/dev.png and b/docs/screenshots/dev.png differ
diff --git a/docs/screenshots/enemies.png b/docs/screenshots/enemies.png
index 74e9193..c2b3abb 100644
Binary files a/docs/screenshots/enemies.png and b/docs/screenshots/enemies.png differ
diff --git a/docs/screenshots/info-enemy.png b/docs/screenshots/info-enemy.png
index 7b18125..edf0b8a 100644
Binary files a/docs/screenshots/info-enemy.png and b/docs/screenshots/info-enemy.png differ
diff --git a/docs/screenshots/info-hud.png b/docs/screenshots/info-hud.png
index 189e32c..f8fff59 100644
Binary files a/docs/screenshots/info-hud.png and b/docs/screenshots/info-hud.png differ
diff --git a/docs/screenshots/lingering.png b/docs/screenshots/lingering.png
index 1ca8059..c13fb65 100644
Binary files a/docs/screenshots/lingering.png and b/docs/screenshots/lingering.png differ
diff --git a/docs/screenshots/long-press.png b/docs/screenshots/long-press.png
index dd650c1..9c838db 100644
Binary files a/docs/screenshots/long-press.png and b/docs/screenshots/long-press.png differ
diff --git a/docs/screenshots/menu.png b/docs/screenshots/menu.png
index fbf0b1d..1c26558 100644
Binary files a/docs/screenshots/menu.png and b/docs/screenshots/menu.png differ
diff --git a/docs/screenshots/new-run.png b/docs/screenshots/new-run.png
index 4b25e7b..68ada1b 100644
Binary files a/docs/screenshots/new-run.png and b/docs/screenshots/new-run.png differ
diff --git a/docs/screenshots/pierce-decoy.png b/docs/screenshots/pierce-decoy.png
index 3103de5..05879da 100644
Binary files a/docs/screenshots/pierce-decoy.png and b/docs/screenshots/pierce-decoy.png differ
diff --git a/docs/screenshots/power-up.png b/docs/screenshots/power-up.png
index 538df8c..de6ca69 100644
Binary files a/docs/screenshots/power-up.png and b/docs/screenshots/power-up.png differ
diff --git a/docs/screenshots/run.png b/docs/screenshots/run.png
index b48073b..c7a8277 100644
Binary files a/docs/screenshots/run.png and b/docs/screenshots/run.png differ
diff --git a/docs/screenshots/scrolled.png b/docs/screenshots/scrolled.png
index 5df6f6d..aa02540 100644
Binary files a/docs/screenshots/scrolled.png and b/docs/screenshots/scrolled.png differ
diff --git a/docs/screenshots/small-desktop.png b/docs/screenshots/small-desktop.png
index 1ea247b..d290ee0 100644
Binary files a/docs/screenshots/small-desktop.png and b/docs/screenshots/small-desktop.png differ
diff --git a/docs/screenshots/small.png b/docs/screenshots/small.png
index 90c6a19..526d025 100644
Binary files a/docs/screenshots/small.png and b/docs/screenshots/small.png differ
diff --git a/docs/screenshots/start.png b/docs/screenshots/start.png
index 7473295..0e08f97 100644
Binary files a/docs/screenshots/start.png and b/docs/screenshots/start.png differ
diff --git a/src/render/board.ts b/src/render/board.ts
index dff6db7..b35da5d 100644
--- a/src/render/board.ts
+++ b/src/render/board.ts
@@ -84,8 +84,12 @@ const C = {
   powerUpGlow: rgb(1, 0.95, 0.55, 0.8),
   star: rgb(1, 0.85, 0.2, 0.9),
   blocked: rgb(0.85, 0.2, 0.2, 0.35),
+  stoneOutline: rgb(53 / 255, 53 / 255, 65 / 255),
 }
 
+/** The stone frame's outline: 8 of its sprites' 128 pixels. */
+const STONE_OUTLINE = 8 / 128
+
 export const BACKGROUND: Color = C.sky
 
 
@@ -96,12 +100,15 @@ export function drawBoard(view: View): void {
   const tileSize = vec2(1)
   const crumbling = new Map(s.crumbling.map((c) => [c.cell, c.turns]))
 
+  // Floors first: drawn a hair oversized too, a later one would paint over the last wall's edge.
+  for (let y = 0; y < s.height; y++) {
+    for (let x = 0; x < s.width; x++) drawRect(toWorld(s, { x, y }), wallSize, (x + y) % 2 ? C.floorAlt : C.floor)
+  }
   for (let y = 0; y < s.height; y++) {
     for (let x = 0; x < s.width; x++) {
       const p = { x, y }
       const pos = toWorld(s, p)
       const cell = s.cells[idx(s, p)]
-      drawRect(pos, wallSize, (x + y) % 2 ? C.floorAlt : C.floor)
       if (cell === 'wall') drawTile(pos, wallSize, sprite(wallSprite(s.level, s.width, s.height, p)))
       else if (cell === 'block') {
         // A crumbling crate shakes, harder and fainter the closer it is to gone.
@@ -114,6 +121,15 @@ export function drawBoard(view: View): void {
       }
     }
   }
+  // The stone frame has no inner-corner piece, so its outline gets a square where the sides meet,
+  // lined up with the oversized wall sprites' outline.
+  const outline = wallSize.x * STONE_OUTLINE
+  const reach = 0.5 - (wallSize.x - 1) / 2 + outline / 2
+  for (const [x, dx] of [[1, -1], [s.width - 2, 1]] as const) {
+    for (const [y, dy] of [[1, 1], [s.height - 2, -1]] as const) {
+      drawRect(toWorld(s, { x, y }).add(vec2(dx * reach, dy * reach)), vec2(outline), C.stoneOutline)
+    }
+  }
   if (cellAt(s, s.exit) === 'floor') drawExit(toWorld(s, s.exit), exitOpen(s))
   // The level's power-up, once its crate is gone: on a soft glow, so it stands out from drops.
   if (s.powerUp && cellAt(s, s.powerUp.pos) === 'floor') {
diff --git a/src/render/sprites.ts b/src/render/sprites.ts
index bf43d52..b2e8a1d 100644
--- a/src/render/sprites.ts
+++ b/src/render/sprites.ts
@@ -16,7 +16,12 @@ export function spriteUrl(name: string): string {
 
 /** The sprites drawn on the canvas. Their order is their LittleJS texture index. */
 const BOARD = [
-  'terrain_stone_block_center',
+  'terrain_stone_horizontal_middle',
+  'terrain_stone_vertical_middle',
+  'terrain_stone_block_top_left',
+  'terrain_stone_block_top_right',
+  'terrain_stone_block_bottom_left',
+  'terrain_stone_block_bottom_right',
   'block_blue',
   'block_green',
   'block_red',
@@ -104,10 +109,21 @@ export function digitSprite(d: number): TileInfo {
 /** Pillars change colour from level to level; the outer wall is always stone. */
 const PILLARS: BoardSprite[] = ['block_blue', 'block_green', 'block_red', 'block_yellow']
 
-/** The sprite of a wall tile: stone on the level's edge, otherwise the level's pillar colour. */
+export function isOuterWall(width: number, height: number, p: { x: number; y: number }): boolean {
+  return p.x === 0 || p.y === 0 || p.x === width - 1 || p.y === height - 1
+}
+
+/**
+ * The sprite of a wall tile: the level's pillar colour inside; on the edge, the piece of a stone
+ * frame whose dark outline runs along both sides of the ring, with rounded outer corners.
+ */
 export function wallSprite(level: number, width: number, height: number, p: { x: number; y: number }): BoardSprite {
-  const edge = p.x === 0 || p.y === 0 || p.x === width - 1 || p.y === height - 1
-  return edge ? 'terrain_stone_block_center' : PILLARS[(level - 1) % PILLARS.length]!
+  if (!isOuterWall(width, height, p)) return PILLARS[(level - 1) % PILLARS.length]!
+  const top = p.y === 0
+  const bottom = p.y === height - 1
+  if (p.x === 0 && (top || bottom)) return top ? 'terrain_stone_block_top_left' : 'terrain_stone_block_bottom_left'
+  if (p.x === width - 1 && (top || bottom)) return top ? 'terrain_stone_block_top_right' : 'terrain_stone_block_bottom_right'
+  return top || bottom ? 'terrain_stone_horizontal_middle' : 'terrain_stone_vertical_middle'
 }
 
 /** The icon for each action charge, on the board (as a drop) and on its button. */
diff --git a/src/ui/info.ts b/src/ui/info.ts
index 34a7044..ca6b3e2 100644
--- a/src/ui/info.ts
+++ b/src/ui/info.ts
@@ -4,7 +4,7 @@ import { bombAt, cellAt, eq, exitOpen, isBurning, PAR_BONUS } from '../sim/state
 import { COIN_BAG, EXIT_SPAWN, STAR_TURNS } from '../sim/turn'
 import type { ChargeId, Dir, EnemyKind, GameState, Powers, PowerUpKind, Vec } from '../sim/types'
 import { dirBetween } from '../sim/enemies'
-import { CHARGE_ICONS, coinSprite, ENEMY_SPRITES, POWER_UP_ICONS, spriteUrl, wallSprite } from '../render/sprites'
+import { CHARGE_ICONS, coinSprite, ENEMY_SPRITES, isOuterWall, POWER_UP_ICONS, spriteUrl, wallSprite } from '../render/sprites'
 
 /**
  * What the game says about each thing on the board and in the top bar, for the info dialog
@@ -183,7 +183,7 @@ export function tileInfo(s: GameState, p: Vec): Info | null {
   const cell = cellAt(s, p)
   if (cell === 'wall') {
     const icon = wallSprite(s.level, s.width, s.height, p)
-    const pillar = icon !== 'terrain_stone_block_center'
+    const pillar = !isOuterWall(s.width, s.height, p)
     return {
       icon,
       title: pillar ? 'Pillar' : 'Outer wall',