diffs / #45

feat(#45): neon signs on the arcade

SHIPPED commit b0dc275 +19 −0 demo replay

Requested-by: demo replay (synthetic wallet)
+ BUILD+ UNIT TESTS+ LINT+ DIFF GATE+ PAGE CLASSIFIER+ PLAYWRIGHT
src/features/r45-neon-signs.jsNEW+19 −0
1+// #45: neon signs on the arcade building: an OPEN sign and a pizza slice sign on the rooftop walls.
2+import { PAL } from "../palette.js";
3+const OPEN = ["0110111011101001", "1001101010001101", "1001111011101011", "1001100010001001", "0110100011101001"];
4+export default {
5+ id: "neon-signs-45",
6+ order: 26,
7+ layer: "mid",
8+ rooms: ["rooftop", "alley"],
9+ draw(g, w) {
10+ const on = w.reduced || Math.sin(w.t * 1.1 + 1) > -0.95;
11+ const ox = w.room === "alley" ? 150 : 158, oy = w.room === "alley" ? 60 : 120;
12+ g.fillStyle = PAL.ink; g.fillRect(ox - 3, oy - 3, 38, 16);
13+ for (let y = 0; y < 5; y++) for (let x = 0; x < 16; x++) if (OPEN[y][x] === "1") { g.fillStyle = on ? PAL.boss : PAL.winOff; g.fillRect(ox + x * 2, oy + y * 2, 2, 2); }
14+ const px = w.room === "alley" ? 210 : 60, py = w.room === "alley" ? 58 : 108;
15+ g.fillStyle = on ? PAL.pizza : PAL.winOff;
16+ for (let r = 0; r < 8; r++) g.fillRect(px + r, py + r, 12 - r * 2 + r, 1);
17+ g.fillStyle = on ? PAL.boss : PAL.winOff; g.fillRect(px + 4, py + 2, 2, 2); g.fillRect(px + 7, py + 4, 2, 2);
18+ },
19+};
1+// #45: neon signs on the arcade building: an OPEN sign and a pizza slice sign on the rooftop walls.
2+import { PAL } from "../palette.js";
3+const OPEN = ["0110111011101001", "1001101010001101", "1001111011101011", "1001100010001001", "0110100011101001"];
4+export default {
5+ id: "neon-signs-45",
6+ order: 26,
7+ layer: "mid",
8+ rooms: ["rooftop", "alley"],
9+ draw(g, w) {
10+ const on = w.reduced || Math.sin(w.t * 1.1 + 1) > -0.95;
11+ const ox = w.room === "alley" ? 150 : 158, oy = w.room === "alley" ? 60 : 120;
12+ g.fillStyle = PAL.ink; g.fillRect(ox - 3, oy - 3, 38, 16);
13+ for (let y = 0; y < 5; y++) for (let x = 0; x < 16; x++) if (OPEN[y][x] === "1") { g.fillStyle = on ? PAL.boss : PAL.winOff; g.fillRect(ox + x * 2, oy + y * 2, 2, 2); }
14+ const px = w.room === "alley" ? 210 : 60, py = w.room === "alley" ? 58 : 108;
15+ g.fillStyle = on ? PAL.pizza : PAL.winOff;
16+ for (let r = 0; r < 8; r++) g.fillRect(px + r, py + r, 12 - r * 2 + r, 1);
17+ g.fillStyle = on ? PAL.boss : PAL.winOff; g.fillRect(px + 4, py + 2, 2, 2); g.fillRect(px + 7, py + 4, 2, 2);
18+ },
19+};
Before
BEFORE
After
AFTER
CI log tail
$ ci:build  (745 ms, exit 0)

$ ci:test  (204 ms, exit 0)
TAP version 13
# Subtest: palette: valid hex, at most 40 colours
ok 1 - palette: valid hex, at most 40 colours
  ---
  duration_ms: 0.978051
  type: 'test'
  ...
# Subtest: sprites: rectangular-ish rows of known keys
ok 2 - sprites: rectangular-ish rows of known keys
  ---
  duration_ms: 10.239181
  type: 'test'
  ...
# Subtest: levels: every room has a name, platforms and a draw function
ok 3 - levels: every room has a name, platforms and a draw function
  ---
  duration_ms: 4.214441
  type: 'test'
  ...
# Subtest: bosses: every file exports a class with update/draw
ok 4 - bosses: every file exports a class with update/draw
  ---
  duration_ms: 0.920674
  type: 'test'
  ...
# Subtest: features: default export with a unique id and only known hooks
ok 5 - features: default export with a unique id and only known hooks
  ---
  duration_ms: 3.787962
  type: 'test'
  ...
# Subtest: no network, storage or parent access anywhere in src
ok 6 - no network, storage or parent access anywhere in src
  ---
  duration_ms: 4.163165
  type: 'test'
  ...
1..6
# tests 6
# suites 0
# pass 6
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 128.576979

$ ci:lint  (769 ms, exit 0)

$ playwright 960x540  fps 60  long tasks 0  console errors 0