diffs / #1

feat(#1): moon and skyline

SHIPPED commit cd97989 +39 −0 request “a moon and a skyline” SEED · built by us pre-launch

Requested-by: SEED (built by us pre-launch)
+ BUILD+ UNIT TESTS+ LINT+ DIFF GATE+ PAGE CLASSIFIER+ PLAYWRIGHT
src/features/moon.jsNEW+18 −0
1+// Seed #1: the night sky. Stars twinkle slowly; the moon hangs top left.
2+import { PAL } from "../palette.js";
3+let stars = [];
4+export default {
5+ id: "moon",
6+ order: 0,
7+ layer: "sky",
8+ init(w) { stars = Array.from({ length: 46 }, () => ({ x: Math.floor(w.rand() * 320), y: Math.floor(w.rand() * 110), p: w.rand() * 6 })); },
9+ draw(g, w) {
10+ g.fillStyle = PAL.skyDeep; g.fillRect(0, 0, 320, 14);
11+ for (let y = 14; y < 26; y += 2 + Math.floor((y - 14) / 4)) g.fillRect(0, y, 320, 1);
12+ for (const s of stars) { g.fillStyle = (Math.sin(w.t * 1.3 + s.p) > 0.6 && !w.reduced) ? PAL.light : PAL.playerShade; g.fillRect(s.x, s.y, 1, 1); }
13+ const cx = 34, cy = 34;
14+ g.fillStyle = PAL.player;
15+ for (let y = -9; y <= 9; y++) { const hw = Math.floor(Math.sqrt(81 - y * y)); g.fillRect(cx - hw, cy + y, hw * 2 + 1, 1); }
16+ g.fillStyle = PAL.playerShade; g.fillRect(cx - 4, cy - 3, 3, 2); g.fillRect(cx + 2, cy + 3, 2, 2); g.fillRect(cx - 1, cy + 5, 2, 1);
17+ },
18+};
1+// Seed #1: the night sky. Stars twinkle slowly; the moon hangs top left.
2+import { PAL } from "../palette.js";
3+let stars = [];
4+export default {
5+ id: "moon",
6+ order: 0,
7+ layer: "sky",
8+ init(w) { stars = Array.from({ length: 46 }, () => ({ x: Math.floor(w.rand() * 320), y: Math.floor(w.rand() * 110), p: w.rand() * 6 })); },
9+ draw(g, w) {
10+ g.fillStyle = PAL.skyDeep; g.fillRect(0, 0, 320, 14);
11+ for (let y = 14; y < 26; y += 2 + Math.floor((y - 14) / 4)) g.fillRect(0, y, 320, 1);
12+ for (const s of stars) { g.fillStyle = (Math.sin(w.t * 1.3 + s.p) > 0.6 && !w.reduced) ? PAL.light : PAL.playerShade; g.fillRect(s.x, s.y, 1, 1); }
13+ const cx = 34, cy = 34;
14+ g.fillStyle = PAL.player;
15+ for (let y = -9; y <= 9; y++) { const hw = Math.floor(Math.sqrt(81 - y * y)); g.fillRect(cx - hw, cy + y, hw * 2 + 1, 1); }
16+ g.fillStyle = PAL.playerShade; g.fillRect(cx - 4, cy - 3, 3, 2); g.fillRect(cx + 2, cy + 3, 2, 2); g.fillRect(cx - 1, cy + 5, 2, 1);
17+ },
18+};
src/features/skyline.jsNEW+21 −0
1+// Seed #1: the far skyline. Two silhouette layers with a few lit windows.
2+import { PAL } from "../palette.js";
3+let far = [], mid = [];
4+export default {
5+ id: "skyline",
6+ order: 1,
7+ layer: "bg",
8+ rooms: ["rooftop", "void"],
9+ init(w) {
10+ let x = 0; far = []; while (x < 320) { const bw = 10 + Math.floor(w.rand() * 18); far.push({ x, w: bw, h: 34 + Math.floor(w.rand() * 40) }); x += bw; }
11+ x = -6; mid = []; while (x < 320) { const bw = 14 + Math.floor(w.rand() * 22); const h = 46 + Math.floor(w.rand() * 50); mid.push({ x, w: bw, h, lit: Array.from({ length: 12 }, () => w.rand() < 0.22) }); x += bw + 2; }
12+ },
13+ draw(g) {
14+ g.fillStyle = PAL.far; for (const b of far) g.fillRect(b.x, 150 - b.h, b.w, b.h + 30);
15+ for (const b of mid) {
16+ g.fillStyle = PAL.wall; g.fillRect(b.x, 160 - b.h, b.w, b.h + 20);
17+ let i = 0;
18+ for (let y = 166 - b.h; y < 150; y += 8) for (let x = b.x + 3; x < b.x + b.w - 3; x += 5) { if (b.lit[i++ % 12]) { g.fillStyle = PAL.light; g.fillRect(x, y, 2, 3); } }
19+ }
20+ },
21+};
1+// Seed #1: the far skyline. Two silhouette layers with a few lit windows.
2+import { PAL } from "../palette.js";
3+let far = [], mid = [];
4+export default {
5+ id: "skyline",
6+ order: 1,
7+ layer: "bg",
8+ rooms: ["rooftop", "void"],
9+ init(w) {
10+ let x = 0; far = []; while (x < 320) { const bw = 10 + Math.floor(w.rand() * 18); far.push({ x, w: bw, h: 34 + Math.floor(w.rand() * 40) }); x += bw; }
11+ x = -6; mid = []; while (x < 320) { const bw = 14 + Math.floor(w.rand() * 22); const h = 46 + Math.floor(w.rand() * 50); mid.push({ x, w: bw, h, lit: Array.from({ length: 12 }, () => w.rand() < 0.22) }); x += bw + 2; }
12+ },
13+ draw(g) {
14+ g.fillStyle = PAL.far; for (const b of far) g.fillRect(b.x, 150 - b.h, b.w, b.h + 30);
15+ for (const b of mid) {
16+ g.fillStyle = PAL.wall; g.fillRect(b.x, 160 - b.h, b.w, b.h + 20);
17+ let i = 0;
18+ for (let y = 166 - b.h; y < 150; y += 8) for (let x = b.x + 3; x < b.x + b.w - 3; x += 5) { if (b.lit[i++ % 12]) { g.fillStyle = PAL.light; g.fillRect(x, y, 2, 3); } }
19+ }
20+ },
21+};
Before
BEFORE
After
AFTER
CI log tail
$ ci:build  (329 ms, exit 0)

$ ci:test  (102 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.708937
  type: 'test'
  ...
# Subtest: sprites: rectangular-ish rows of known keys
ok 2 - sprites: rectangular-ish rows of known keys
  ---
  duration_ms: 0.957252
  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: 0.899564
  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.69408
  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: 1.119215
  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: 0.790791
  type: 'test'
  ...
1..6
# tests 6
# suites 0
# pass 6
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 60.087102

$ ci:lint  (342 ms, exit 0)

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