pixel-daylight-switch-gsvegetable 1.3.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +484 -174
  2. package/app.js +594 -258
  3. package/package.json +19 -4
package/app.js CHANGED
@@ -96,6 +96,42 @@ const MATS = [
96
96
  ['MARK', I0, I0, C3, C3],
97
97
  ['ZZZ', U3, U3, U3, U3],
98
98
  ['WING', A2, A2, A1, I2],
99
+ ['SNOW', C3, A3, U2, I3],
100
+ ['SNOW_SH', U2, U1, U0, I2],
101
+ ['SNOW_DK', U1, U0, I3, I1],
102
+ ['SEA', U1, U0, I3, I1],
103
+ ['SEA_DK', U0, I3, I2, I0],
104
+ ['SEA_LT', U2, A1, U0, I2],
105
+ ['SEA_HI', U3, A3, U2, I3],
106
+ ['FOAM', C3, A3, U2, I3],
107
+ ['ROCK', U0, I3, I1, I0],
108
+ ['LANTERN', A1, A2, A3, A3],
109
+ ['LANTERN_HI', A2, A3, A3, A3],
110
+ ['BAND', A1, A1, I3, I1],
111
+ ['PLASTER', C2, A2, U1, I2],
112
+ ['PLASTER_SH', C1, A1, U0, I1],
113
+ ['CONCRETE', U2, U1, I3, I1],
114
+ ['CONCRETE_SH', U1, U0, I2, I0],
115
+ ['ASPHALT', U0, I3, I2, I1],
116
+ ['ASPHALT_LT', U1, U0, I3, I1],
117
+ ['ASPHALT_DK', I3, I2, I1, I0],
118
+ ['LINE', C3, A3, U2, I3],
119
+ ['GLASS_SKY', U2, A2, I3, I1],
120
+ ['GLASS_SKY_HI', U3, A3, U1, I2],
121
+ ['NOREN', I3, I2, I1, I0],
122
+ ['CAT', A2, A1, I3, I1],
123
+ ['CAT_DK', A0, A0, I2, I0],
124
+ ['AUR_HI', G3, G3, G3, G3],
125
+ ['AUR_MID', G2, G2, G2, G2],
126
+ ['AUR_LO', G1, G1, G1, G1],
127
+ ['AUR_ICE', U3, U3, U3, U3],
128
+ ['SAND_LT', C2, A2, U1, I3],
129
+ ['SOFA', G1, G0, I2, I0],
130
+ ['SOFA_LT', G2, G1, I3, I1],
131
+ ['SOFA_DK', G0, G0, I1, I0],
132
+ ['RUG', U1, U0, I3, I1],
133
+ ['RUG_LT', U2, U1, I3, I2],
134
+ ['SHALLOW', G3, A2, U1, I2],
99
135
  ];
100
136
  const M = Object.fromEntries(MATS.map((row, i) => [row[0], i]));
101
137
  const KEYS = [
@@ -148,7 +184,7 @@ function labTone(lab, L) {
148
184
  }
149
185
  return oklabToLinear(L, 0, 0).map((v) => Math.round(toGamma(clamp(v)) * 255));
150
186
  }
151
- const CHARS = { H: 'HAIR', S: 'SKIN', E: 'HAIR', T: 'SHIRT', D: 'SHIRT_DK', P: 'PANTS', B: 'BOOT', W: 'BOOK', K: 'BOOK_DK' };
187
+ const CHARS = { H: 'HAIR', S: 'SKIN', E: 'HAIR', T: 'SHIRT', D: 'SHIRT_DK', P: 'PANTS', B: 'BOOT', W: 'BOOK', K: 'BOOK_DK', C: 'CAT', Q: 'CAT_DK', L: 'LANTERN', F: 'FOAM' };
152
188
  const makeSprite = (rows) => { const list = []; rows.forEach((row, y) => { [...row].forEach((ch, x) => { if (ch !== '.') list.push([x, y, M[CHARS[ch]], ch === 'E']); }); }); return { list, w: rows[0].length, h: rows.length }; };
153
189
  const HEAD = ['..HHH..', '.HHHHH.', '.HHSSS.', '.HSSES.', '..SSSS.'];
154
190
  const HEAD_BLINK = ['..HHH..', '.HHHHH.', '.HHSSS.', '.HSSSS.', '..SSSS.'];
@@ -315,267 +351,567 @@ function mount(root) {
315
351
  rect(156, 87, 5, 2, M.STONE); rect(157, 86, 3, 1, M.STONE_LT); rect(124, 95, 4, 2, M.STONE); rect(125, 94, 2, 1, M.STONE_LT);
316
352
  };
317
353
 
318
- // Additional handcrafted pixel environments. Coordinates share the 192×112 world grid.
319
- const paintJapaneseStreet = () => {
320
- rect(0, 0, CORE_W, 74, 0);
321
- rect(0, 74, CORE_W, 8, M.STONE); rect(0, 82, CORE_W, 30, M.PATH_DK);
322
- for (let y=76;y<82;y+=2) rect(0,y,CORE_W,1,M.STONE_LT);
323
- // shopfronts and narrow Japanese buildings
324
- rect(0,24,53,51,M.WALL); rect(0,20,55,5,M.ROOF);
325
- rect(4,30,45,3,M.WOOD_DK); rect(5,34,43,23,M.GLASS);
326
- rect(4,58,46,17,M.WOOD_DK); rect(7,60,40,13,M.WOOD);
327
- for(let x=10;x<47;x+=8){rect(x,60,1,13,M.WOOD_DK);}
328
- rect(8,27,39,5,M.ROOF_HI); rect(8,36,39,2,M.WOOD_DK);
329
- // noren curtain
330
- for(let x=10;x<44;x+=7){rect(x,38,5,9,M.CLOUD);rect(x+1,45,3,2,M.CLOUD_SH);}
331
- rect(68,16,42,59,M.WALL_SH); rect(70,19,38,5,M.ROOF);
332
- for(let x=74;x<106;x+=12){rect(x,30,8,13,M.GLASS);rect(x+1,31,6,2,M.GLASS_HI);rect(x+3,30,1,13,M.WOOD_DK);}
333
- rect(72,51,34,22,M.WOOD_DK); rect(75,54,28,19,M.WOOD);
334
- rect(78,57,22,9,M.GLASS); rect(79,58,20,2,M.GLASS_HI);
335
- // right-side ramen shop
336
- rect(132,29,60,46,M.WALL); rect(130,24,62,6,M.ROOF);
337
- rect(135,34,54,4,M.WOOD_DK); rect(138,39,48,3,M.WOOD_LT);
338
- rect(139,44,44,19,M.GLASS); rect(141,46,40,14,M.WOOD_DK);
339
- rect(143,48,36,10,M.GLASS); rect(144,49,34,2,M.GLASS_HI);
340
- rect(135,63,54,12,M.WOOD_DK); rect(139,65,46,8,M.WOOD);
341
- // lanterns, signs, street poles and wires
342
- rect(15,18,2,8,M.WOOD_DK); rect(12,17,8,1,M.WOOD_DK); rect(13,18,6,6,M.LIT); rect(14,19,4,4,M.LIT_HI);
343
- rect(150,15,2,9,M.WOOD_DK); rect(146,14,10,1,M.WOOD_DK); rect(147,15,8,7,M.LIT); rect(148,16,6,4,M.LIT_HI);
344
- rect(117,10,2,70,M.WOOD_DK); rect(108,12,22,2,M.WOOD_DK); rect(111,14,1,5,M.WOOD_DK);
345
- for(let x=0;x<CORE_W;x+=2){ if(x%4===0) put(x,12+Math.round(Math.sin(x*.035)*2),M.WOOD_DK); }
346
- rect(118,78,2,8,M.WOOD_DK); rect(115,76,8,2,M.WOOD_LT);
347
- // road markings and drain
348
- for(let x=14;x<CORE_W;x+=36){rect(x,94,14,2,M.STONE_LT);rect(x+4,95,7,1,M.PATH);}
349
- rect(24,80,12,2,M.WOOD_DK); for(let x=26;x<35;x+=3) put(x,80,M.STONE_LT);
350
- // vending machine and bicycle silhouette
351
- rect(57,51,8,24,M.WOOD_DK); rect(58,52,6,18,M.CLOUD); rect(59,54,4,6,M.GLASS_HI); rect(59,63,4,2,M.LIT);
352
- for(let x of [84,101]){for(let a=0;a<360;a+=30){const rad=a*Math.PI/180;put(Math.round(x+4*Math.cos(rad)),Math.round(78+4*Math.sin(rad)),M.WOOD_DK);} }
353
- rect(88,75,10,1,M.WOOD_DK); rect(91,72,4,4,M.WOOD_DK); rect(95,72,4,1,M.WOOD_DK);
354
- // Second-floor balconies, tiled roof edges, shop sign typography and alley depth
355
- rect(2,13,49,5,M.WOOD_DK); rect(4,14,45,2,M.ROOF_LT);
356
- for(let x=6;x<49;x+=7){rect(x,8,4,5,M.WALL_SH);rect(x+1,9,2,3,M.GLASS);put(x+2,10,M.GLASS_HI);}
357
- rect(3,47,48,2,M.WOOD_DK); for(let x=5;x<50;x+=5) rect(x,48,1,5,M.WOOD_LT);
358
- rect(6,53,44,2,M.WOOD_DK); rect(10,24,34,7,M.WOOD_DK);
359
- for(let x=12;x<43;x+=4){put(x,26,M.LIT_HI);put(x+1,27,M.LIT);}
360
- // Ramen shop noren, menu board and noren tassels
361
- rect(143,30,43,7,M.WOOD_DK); rect(146,31,37,4,M.LIT_DK);
362
- for(let x=148;x<181;x+=6){rect(x,39,4,7,M.CLOUD);put(x+1,46,M.CLOUD_SH);}
363
- rect(184,38,5,18,M.WOOD_DK); for(let y=40;y<54;y+=3) put(186,y,M.LIT_HI);
364
- // Rooftop water tank, satellite dish and dense utility cables
365
- rect(82,7,13,7,M.STONE); rect(84,5,9,2,M.STONE_LT); rect(85,9,7,3,M.WOOD_DK);
366
- rect(101,9,2,5,M.WOOD_DK); rect(98,8,8,1,M.WOOD_DK); rect(100,6,4,2,M.STONE_LT);
367
- for(let x=0;x<CORE_W;x+=1){const sag=Math.round(10+Math.sin(x/CORE_W*Math.PI)*5);if(x%3===0)put(x,sag,M.WOOD_DK);if(x%11===0)put(x,sag+1,M.WOOD_DK);}
368
- // Street utility boxes, bollards, storm drain and manhole
369
- rect(4,76,8,8,M.WOOD_DK);rect(5,77,6,5,M.STONE);for(let y=78;y<82;y+=2)rect(6,y,4,1,M.WOOD_DK);
370
- for(let x=4;x<CORE_W;x+=48){rect(x,83,2,8,M.STONE);put(x,83,M.STONE_LT);}
371
- rect(148,91,16,5,M.WOOD_DK);rect(150,92,12,2,M.STONE);for(let x=151;x<162;x+=3)put(x,93,M.WOOD_DK);
372
- // More road perspective and wet-pavement glints
373
- for(let y=99;y<111;y+=4){for(let x=(y*3)%17;x<CORE_W;x+=29)rect(x,y,5+(y%3),1,M.STONE);}
374
- for(let x=18;x<CORE_W;x+=31){put(x,88,M.CLOUD_SH);put(x+1,89,M.CLOUD_SH);}
375
- // Planters and small street tree
376
- rect(26,69,13,5,M.WOOD_DK);rect(28,67,9,2,M.STONE);for(let x=29;x<37;x+=2){put(x,65-(x%3),M.LEAF2);put(x+1,67,M.LEAF1);}
377
- rect(187,64,5,11,M.WOOD_DK);rect(188,61,3,4,M.LEAF1);put(187,62,M.LEAF2);put(191,63,M.LEAF2);
378
- };
379
- const paintApartment = () => {
380
- rect(0,0,CORE_W,78,M.WALL); rect(0,78,CORE_W,34,M.PLANK);
381
- for(let y=4;y<76;y+=12) rect(0,y,CORE_W,1,M.WALL_SH);
382
- // large city window
383
- rect(12,12,68,52,M.WOOD_DK); rect(15,15,62,46,M.GLASS);
384
- rect(44,15,2,46,M.WOOD_DK); rect(15,37,62,2,M.WOOD_DK);
385
- // distant city skyline through glass
386
- rect(17,42,58,18,M.CLOUD_SH);
387
- rect(20,32,8,28,M.STONE); rect(31,25,10,35,M.STONE_LT); rect(49,35,9,25,M.STONE); rect(63,29,10,31,M.STONE_LT);
388
- for(let x=21;x<28;x+=3) for(let y=36;y<57;y+=6) put(x,y,M.LIT);
389
- for(let x=33;x<40;x+=3) for(let y=29;y<57;y+=6) put(x,y,M.LIT);
390
- for(let x=51;x<57;x+=3) for(let y=39;y<57;y+=6) put(x,y,M.LIT);
391
- for(let x=65;x<72;x+=3) for(let y=33;y<57;y+=6) put(x,y,M.LIT);
392
- // curtains
393
- rect(9,10,5,57,M.WOOD); rect(78,10,5,57,M.WOOD);
394
- for(let y=12;y<65;y+=4){put(10,y,M.WOOD_LT);put(80,y,M.WOOD_LT);}
395
- // sofa and coffee table
396
- rect(91,59,63,8,M.WOOD_DK); rect(94,54,57,12,M.SHIRT_DK); rect(97,51,50,10,M.SHIRT);
397
- rect(89,63,6,12,M.WOOD_DK); rect(151,63,6,12,M.WOOD_DK);
398
- rect(105,76,42,4,M.WOOD_DK); rect(108,80,3,8,M.WOOD); rect(141,80,3,8,M.WOOD);
399
- rect(112,72,27,4,M.WOOD_LT); rect(119,69,12,3,M.BOOK);
400
- // floor lamp, framed art and indoor plant
401
- rect(169,25,2,47,M.WOOD_DK); rect(164,21,12,5,M.LIT); rect(166,22,8,2,M.LIT_HI);
402
- rect(160,72,20,2,M.WOOD_DK); rect(162,68,16,4,M.WOOD);
403
- rect(23,69,19,2,M.WOOD_DK); rect(25,53,15,14,M.WOOD_DK); rect(27,55,11,10,M.CLOUD);
404
- rect(26,84,11,10,M.WOOD_DK); rect(24,92,15,3,M.WOOD);
405
- for(let i=0;i<5;i++){put(31+i*2,83-i*2,M.LEAF2);put(29-i,85-i*2,M.LEAF1);}
406
- // rug and small details
407
- rect(78,91,89,10,M.SHIRT_DK); rect(82,93,81,6,M.SHIRT);
408
- rect(12,103,168,1,M.WOOD_DK);
409
- // Architectural trim and ceiling coving
410
- rect(0,5,CORE_W,2,M.WOOD_DK);rect(0,7,CORE_W,1,M.WOOD_LT);
411
- rect(86,12,2,47,M.WOOD_DK);rect(88,12,1,47,M.WOOD_LT);
412
- // Gallery wall: framed print, clock and floating shelf
413
- rect(96,12,22,20,M.WOOD_DK);rect(98,14,18,16,M.CLOUD_SH);
414
- rect(100,16,14,12,M.HILL_FAR);rect(100,24,14,4,M.HILL_NEAR);
415
- rect(103,18,3,5,M.LEAF1);put(106,17,M.LEAF2);put(109,19,M.LEAF2);
416
- rect(126,16,12,12,M.WOOD_DK);rect(128,18,8,8,M.CLOUD);
417
- put(132,21,M.WOOD_DK);put(132,23,M.WOOD_DK);
418
- rect(94,34,48,2,M.WOOD_DK);rect(99,36,4,2,M.WOOD_LT);rect(132,36,7,2,M.WOOD_LT);
419
- // Media console and television
420
- rect(91,43,42,8,M.WOOD_DK);rect(94,45,36,5,M.WOOD);
421
- rect(103,29,25,14,M.WOOD_DK);rect(105,31,21,10,M.GLASS);rect(107,33,17,6,M.HILL_FAR);
422
- put(115,36,M.LIT_HI);rect(108,43,15,1,M.WOOD_LT);
423
- // Cushions, seams, throw blanket and side table on sofa
424
- rect(99,54,11,7,M.SHIRT_DK);rect(101,55,7,4,M.SHIRT);
425
- rect(134,54,12,7,M.SHIRT_DK);rect(136,55,8,4,M.SHIRT);
426
- for(let x=99;x<149;x+=7)put(x,64,M.SHIRT_DK);
427
- rect(154,60,12,12,M.WOOD_DK);rect(156,62,8,7,M.WOOD);
428
- rect(158,59,4,2,M.CLOUD);put(159,58,M.LIT_HI);
429
- // Bookshelf with books and a small speaker
430
- rect(4,28,19,45,M.WOOD_DK);rect(6,31,15,2,M.WOOD_LT);rect(6,48,15,2,M.WOOD_LT);
431
- for(let x=7;x<20;x+=4){rect(x,34,2,12,M.BOOK);rect(x+2,36,1,10,M.BOOK_DK);}
432
- rect(7,52,5,10,M.WOOD);rect(13,54,6,8,M.BOOK);
433
- rect(17,66,5,7,M.WOOD_DK);rect(18,67,3,4,M.GLASS);
434
- // Kitchen counter, backsplash and hanging pendant
435
- rect(4,78,53,3,M.WOOD_DK);rect(7,81,47,10,M.WOOD);
436
- rect(9,73,44,5,M.STONE);rect(12,74,12,3,M.CLOUD);
437
- rect(39,75,10,3,M.WOOD_DK);rect(42,71,4,4,M.GLASS);
438
- rect(34,10,2,9,M.WOOD_DK);rect(30,18,10,3,M.LIT);rect(32,19,6,2,M.LIT_HI);
439
- // Floorboards, rug border and woven rug pattern
440
- for(let y=84;y<111;y+=5)rect(0,y,CORE_W,1,M.WOOD_DK);
441
- for(let x=10;x<CORE_W;x+=24)rect(x,84,1,28,M.WOOD_DK);
442
- rect(78,89,89,2,M.WOOD_DK);rect(78,101,89,2,M.WOOD_DK);
443
- for(let x=86;x<161;x+=8){put(x,95,M.SHIRT_DK);put(x+3,97,M.SHIRT_DK);}
444
- // Plant leaves, pot highlights and lamp pull chain
445
- rect(26,94,11,2,M.BRICK);rect(28,96,7,3,M.BRICK_LT);
446
- for(let i=0;i<7;i++){put(30+i*2,82-i%3*2,M.LEAF3);put(28+i*2,86-i%4*2,M.LEAF2);}
447
- rect(170,27,1,28,M.WOOD_LT);put(171,55,M.WOOD_DK);
354
+
355
+ // ===== Scene toolkit: shared by the four non-countryside scenes =====
356
+ // Every painter fills the WHOLE world buffer (-ox..bw-ox, -oy..bh-oy), not just the 192x112 core,
357
+ // so wide windows never show raw sky at the edges. Dynamic things (lights, people, water, smoke) are
358
+ // redrawn each frame on top of the static base in drawSceneLive().
359
+ const hash2 = (a, b) => { let t = Math.imul(a | 0, 374761393) ^ Math.imul(b | 0, 668265263); t = Math.imul(t ^ (t >>> 13), 1274126177); return ((t ^ (t >>> 16)) >>> 0) / 4294967296; };
360
+ const dth = (x, y, t) => t * 16 > bayerAt(x, y);
361
+ const strip = (y0, y1, m) => { for (let y = y0; y < y1; y++) for (let x = -ox; x < bw - ox; x++) put(x, y, m); };
362
+ const disc = (cx, cy, r, m) => { for (let y = Math.floor(cy - r); y <= cy + r; y++) for (let x = Math.floor(cx - r); x <= cx + r; x++) if ((x - cx) * (x - cx) + (y - cy) * (y - cy) <= r * r + 0.3) put(x, y, m); };
363
+ const line = (x0, y0, x1, y1, m) => { const n = Math.max(Math.abs(x1 - x0), Math.abs(y1 - y0), 1); for (let i = 0; i <= n; i++) put(Math.round(mix(x0, x1, i / n)), Math.round(mix(y0, y1, i / n)), m); };
364
+ const sunPos = (h) => { if (h <= 5.2 || h >= 19) return null; const a = ((h - 6) / 12) * Math.PI; return { x: Math.round(96 - Math.cos(a) * 102), y: Math.round(HORIZON - 2 - Math.sin(a) * 70) }; };
365
+ const moonPos = (h) => { const mh = h >= 17.6 ? h : h + 24; if (mh <= 17.6 || mh >= 30.4) return null; const a = ((mh - 18) / 12) * Math.PI; return { x: Math.round(96 - Math.cos(a) * 102), y: Math.round(HORIZON - 2 - Math.sin(a) * 66) }; };
366
+ const nightF = (h) => clamp(smoothstep(18.2, 20.2, h) + (1 - smoothstep(4.6, 6.2, h)));
367
+ const lampAmt = (h) => (h >= 12 ? smoothstep(17.4, 19.0, h) : 1 - smoothstep(5.2, 6.8, h));
368
+ const winFrac = (h) => { if (h >= 12) return 0.9 * smoothstep(17.2, 20.4, h) * (1 - 0.35 * smoothstep(23, 24, h)); if (h < 5) return 0.58 - h * 0.1; return Math.max(0, (0.08 + 0.32 * smoothstep(5, 6.3, h)) * (1 - smoothstep(6.8, 8.3, h))); };
369
+ // windows that light up one by one as evening falls
370
+ const wins = [];
371
+ const addWin = (x, y, w, h, o = {}) => wins.push({ x, y, w, h, seed: o.seed !== undefined ? o.seed : hash2(x * 7 + 3, y * 13 + 1), mull: o.mull || 0, always: !!o.always, tiny: !!o.tiny });
372
+ const drawWins = (h, level, boost = 1) => {
373
+ for (const w of wins) {
374
+ const on = w.always ? lampAmt(h) > 0.4 : w.seed < level; if (!on) continue;
375
+ if (w.tiny) { put(w.x, w.y, M.LIT); emit(w.x, w.y, 70 * boost, 2); continue; }
376
+ const flick = calm ? 1 : 0.93 + 0.07 * Math.sin(anim * 2.1 + w.seed * 40);
377
+ for (let j = 0; j < w.h; j++) for (let i = 0; i < w.w; i++) {
378
+ const ring = i === 0 || j === 0 || i === w.w - 1 || j === w.h - 1; const low = j === w.h - 1 && w.h > 3;
379
+ let m = low ? M.LIT_DK : ring ? M.LIT : M.LIT_HI; if (w.mull && i === (w.w >> 1) && w.w > 4) m = M.WOOD_DK;
380
+ put(w.x + i, w.y + j, m);
381
+ if (!ring && ((i + j) & 1) === 0) emit(w.x + i, w.y + j, 62 * boost * flick, 2);
382
+ }
383
+ }
448
384
  };
385
+ // warm light pool: switches nearby non-sky pixels to the "lit" tone table
386
+ const pool = (cx, cy, rx, ry, L) => { if (L <= 0.01) return; for (let y = Math.max(Math.floor(cy - ry), -oy); y <= cy + ry; y++) for (let x = Math.floor(cx - rx); x <= cx + rx; x++) { const px = x + ox; const py = y + oy; if (px < 0 || py < 0 || px >= bw || py >= bh) continue; const i = py * bw + px; const m = matBuf[i]; if (m === 0 || m === M.SIL || m === M.LIT || m === M.LIT_HI || m === M.LIT_DK || m === M.LANTERN || m === M.LANTERN_HI) continue; const d = Math.hypot((x - cx) / rx, (y - cy) / ry); if ((1 - d) * L * 1.5 * 16 > bayerAt(x, y)) varBuf[i] = 2; } };
387
+ // shell glow without bloom (the shell reads this as "the room is lit")
388
+ const homeLight = (x, y, weight) => { if (weight <= 0) return; const c = sources[0]; const px = x + ox; const py = y + oy; c.w += weight; c.x += px * weight; c.y += py * weight; };
389
+ const isSeaMat = (m) => m === M.SEA || m === M.SEA_DK || m === M.SEA_LT || m === M.SEA_HI;
390
+ const fx = { smoke: [], t: 0 };
391
+ // ===== Scene: seaside (lighthouse + pier + village across the bay) =====
392
+ const SEAL = { YH: 60, SH: 98, LX: 182, T: 38, BASE: 76, DECK: 90 };
449
393
  const paintSeaside = () => {
450
- // ocean band and distant islands
451
- rect(0,48,CORE_W,36,M.CLOUD); rect(0,82,CORE_W,30,M.PATH);
452
- for(let y=52;y<82;y+=5){for(let x=(y%2)*7;x<CORE_W;x+=24){rect(x,y,8+(x%3),1,M.CLOUD_SH);}}
453
- rect(0,46,48,5,M.HILL_FAR); rect(142,43,50,7,M.HILL_NEAR);
454
- // sandy beach and shoreline
455
- rect(0,83,CORE_W,29,M.PATH); for(let y=84;y<112;y+=5) for(let x=(y%3)*4;x<CORE_W;x+=19) put(x,y,M.STONE_LT);
456
- for(let x=0;x<CORE_W;x++){const shore=82+Math.round(Math.sin(x*.055)*2);put(x,shore,M.CLOUD_SH);if(x%3===0)put(x,shore+1,M.STONE_LT);}
457
- // lighthouse on rocky point
458
- rect(145,34,31,9,M.STONE); rect(151,16,18,27,M.WALL); rect(149,14,22,4,M.WOOD_DK);
459
- rect(153,19,14,3,M.WOOD_LT); rect(155,23,10,17,M.WALL_SH);
460
- rect(155,27,10,7,M.GLASS); rect(157,28,6,4,M.LIT_HI);
461
- rect(154,10,12,4,M.ROOF); rect(157,7,6,3,M.LIT); rect(159,3,2,4,M.LIT_HI);
462
- rect(144,41,34,3,M.STONE_LT); rect(148,44,28,2,M.STONE);
463
- // sailboat
464
- rect(49,56,2,18,M.WOOD_DK); rect(51,58,13,12,M.CLOUD); rect(39,70,31,3,M.WOOD_DK);
465
- rect(42,73,25,2,M.WOOD); rect(52,60,1,9,M.WOOD_DK);
466
- // beach umbrella, towel and rocks
467
- rect(25,77,2,18,M.WOOD_DK); rect(17,73,18,4,M.ROOF); rect(20,70,12,3,M.ROOF_LT);
468
- rect(8,96,28,5,M.SHIRT); rect(11,98,22,1,M.SHIRT_DK);
469
- paintBush(101,91,10,5,440); paintBush(186,87,12,6,441);
470
- rect(115,101,19,5,M.STONE); rect(117,99,15,2,M.STONE_LT);
471
- // small palm at edge
472
- for(let y=52;y<82;y++){const x=16+Math.round((y-52)*.18);put(x,y,M.BARK);}
473
- for(const [dx,dy] of [[-13,-4],[-9,-9],[-3,-12],[5,-11],[12,-6],[15,-1]]) for(let i=0;i<7;i++) put(16+dx+Math.round(i*dx*.08),52+dy+Math.round(i*.35),M.LEAF2);
474
- // Palm fronds with layered highlights and a textured trunk
475
- for(let y=55;y<82;y+=3){put(16+Math.round((y-52)*.18)-1,y,M.BARK_LT);put(16+Math.round((y-52)*.18)+1,y,M.BARK);}
476
- for(let x=0;x<CORE_W;x+=1){const wave=83+Math.round(Math.sin(x*.11)*2);if(x%2===0)put(x,wave,M.CLOUD);if(x%7===0)put(x,wave+2,M.CLOUD_SH);}
477
- // Multiple ocean bands, foam streaks and distant wave crests
478
- for(let y=53;y<80;y+=3){for(let x=(y*7)%21;x<CORE_W;x+=27){rect(x,y,5+(x%5),1,M.CLOUD_SH);if(y%2===0)put(x+2,y-1,M.CLOUD);}}
479
- for(let x=5;x<CORE_W;x+=17){put(x,61+(x%5),M.CLOUD);put(x+1,62+(x%5),M.CLOUD_SH);}
480
- // Lighthouse masonry bands, windows, balcony rail and lantern room
481
- for(let y=20;y<41;y+=5){rect(152,y,16,1,M.WOOD_LT);for(let x=153+(y%3);x<168;x+=6)put(x,y+1,M.STONE_LT);}
482
- rect(150,12,20,2,M.WOOD_DK);rect(152,9,16,3,M.GLASS);rect(154,10,12,1,M.LIT_HI);
483
- rect(149,7,22,2,M.WOOD_DK);rect(151,5,18,2,M.ROOF_LT);
484
- rect(150,17,20,1,M.WOOD_DK);for(let x=151;x<170;x+=3)put(x,16,M.WOOD_LT);
485
- rect(154,29,12,7,M.GLASS);rect(156,30,8,5,M.LIT);put(159,31,M.LIT_HI);
486
- rect(153,38,14,2,M.WOOD_DK);rect(155,40,10,3,M.STONE);
487
- // Rocky headland and a winding footpath to the lighthouse
488
- rect(137,43,43,4,M.STONE);rect(141,47,34,3,M.STONE_LT);
489
- for(let y=49;y<84;y+=3){const x=147-Math.floor((y-49)*.35);rect(x,y,26,2,y%2?M.STONE:M.HILL_NEAR);}
490
- for(let i=0;i<8;i++){put(139+i*4,47+i%3,M.CLOUD_SH);put(175-i*3,50+i%4,M.STONE_LT);}
491
- // Wooden pier, posts, rope and mooring rings
492
- rect(63,82,58,3,M.WOOD_DK);rect(66,83,52,1,M.WOOD_LT);
493
- for(let x=68;x<119;x+=8){rect(x,85,2,10,M.WOOD_DK);put(x,86,M.WOOD_LT);}
494
- rect(64,80,2,2,M.WOOD_DK);rect(118,80,2,2,M.WOOD_DK);
495
- // Boat cabin, rigging, mast flag and hull planks
496
- rect(43,68,26,2,M.WOOD_DK);rect(46,65,19,3,M.WOOD);rect(49,62,12,3,M.WOOD_DK);
497
- rect(51,58,2,13,M.WOOD_DK);rect(53,58,1,12,M.WOOD_LT);
498
- for(let x=45;x<67;x+=5)put(x,71,M.WOOD_LT);
499
- rect(53,55,7,2,M.CLOUD);put(60,55,M.ROOF);
500
- // Beach details: deck chair, shell, driftwood and dune grass
501
- rect(6,87,18,2,M.WOOD_DK);rect(8,89,2,7,M.WOOD_DK);rect(21,89,2,7,M.WOOD_DK);
502
- rect(9,88,12,3,M.SHIRT);rect(12,91,7,2,M.SHIRT_DK);
503
- put(42,98,M.CLOUD);put(43,97,M.CLOUD_SH);put(44,98,M.CLOUD);
504
- rect(128,102,15,2,M.WOOD_DK);put(130,100,M.WOOD_LT);put(137,99,M.WOOD_LT);
505
- for(let x=0;x<CORE_W;x+=13){put(x,87+(x%4),M.LEAF1);put(x+2,85+(x%5),M.LEAF2);}
506
- // Reflected sunlight on wet sand
507
- for(let y=88;y<109;y+=6){for(let x=(y*5)%23;x<CORE_W;x+=31)rect(x,y,7,1,M.LIT_DK);}
394
+ wins.length = 0; const { YH, SH, LX, T, BASE, DECK } = SEAL; const xa = -ox; const xb = bw - ox;
395
+ // sea: pale haze at the horizon, deeper water in the middle, lighter shallows near the shore
396
+ for (let y = YH; y <= SH + 10; y++) {
397
+ const t = clamp((y - YH) / (SH - YH));
398
+ for (let x = xa; x < xb; x++) {
399
+ const n = noiseA(x * 0.05 + 9, y * 0.45 + 2);
400
+ let m;
401
+ if (t < 0.16) m = dth(x, y, (0.16 - t) * 6.5) ? M.SEA_LT : M.SEA;
402
+ else if (t < 0.6) m = dth(x, y, (n - 0.3) * 1.7 + t * 0.35) ? M.SEA_DK : M.SEA;
403
+ else m = dth(x, y, (t - 0.6) * 1.9 + (n - 0.5) * 0.4) ? M.SEA_LT : M.SEA;
404
+ if (t > 0.78 && dth(x, y, (t - 0.78) * 3.8 + (n - 0.5) * 0.3)) m = M.SHALLOW;
405
+ put(x, y, m);
406
+ }
407
+ }
408
+ for (let x = xa; x < xb; x += 2) put(x, YH, M.SEA_HI);
409
+ // far hills + village across the bay (left) and a low island (centre)
410
+ const farTop = (x) => Math.round(YH - 10 + noiseA(x * 0.05 + 2, 3.5) * 3 + Math.pow(clamp((x - 6) / 64), 1.6) * 10);
411
+ for (let x = xa; x < 76; x++) { const top = farTop(x); for (let y = top; y < YH; y++) put(x, y, y === top || dth(x, y, 0.3) ? M.HILL_NEAR : M.HILL_FAR); }
412
+ for (const cx of [4, 22, 40]) for (let i = 0; i < 3; i++) {
413
+ const x = cx + i * 4 + ((hash2(cx, i) * 2) | 0); const top = farTop(x) + 2; const hh = 2 + ((hash2(x, 9) * 2) | 0);
414
+ rect(x, top - hh, 3, hh, M.CONCRETE_SH); put(x, top - hh - 1, M.ROOF); put(x + 1, top - hh - 2, M.ROOF); put(x + 1, top - hh - 1, M.ROOF); put(x + 2, top - hh - 1, M.ROOF); if (hash2(x, 4) < 0.85) addWin(x + 1, top - 1, 1, 1, { tiny: true });
415
+ }
416
+ for (let x = 98; x < 134; x++) { const top = YH - 1 - Math.round(3.4 * Math.sin(clamp((x - 98) / 36) * Math.PI) + noiseA(x * 0.1, 8) * 1.2); for (let y = top; y < YH; y++) put(x, y, y === top ? M.HILL_NEAR : M.HILL_FAR); }
417
+ // rocky headland with the lighthouse (right): left-lit, dithered three-tone rock with strata
418
+ const cliffTop = (x) => Math.round((x < 150 ? 98 - 22 * smoothstep(118, 150, x) : BASE) + (x > 204 ? (x - 204) * 0.4 : 0) + (noiseB(x * 0.2, 1) - 0.5) * 2.4);
419
+ for (let x = 118; x < xb; x++) {
420
+ const top = cliffTop(x);
421
+ for (let y = top; y <= SH + 8; y++) {
422
+ const depth = y - top; const strata = Math.sin((y + noiseA(x * 0.05, 3) * 7) * 1.25) * 0.09;
423
+ const v = 0.62 - (x - 118) * 0.0045 - depth * 0.014 + (noiseB(x * 0.17 + 4, y * 0.3) - 0.5) * 0.5 + strata + (bayerAt(x, y) / 16 - 0.5) * 0.2 + (x < 150 ? (150 - x) * 0.004 : 0);
424
+ let m = v > 0.6 ? M.STONE_LT : v > 0.3 ? M.STONE : M.ROCK;
425
+ if (depth < 2 && x >= 150) m = (x + y) % 3 === 0 ? M.GRASS_LT : M.GRASS;
426
+ else if (depth >= 2 && depth < 4 && x >= 150 && dth(x, y, 0.45)) m = M.GRASS_DK;
427
+ else if (depth < 1) m = M.STONE_LT;
428
+ put(x, y, m);
429
+ }
430
+ }
431
+ for (let i = 0; i < 18; i++) { const x = 152 + ((hash2(i, 1) * 46) | 0); put(x, BASE - 1, M.GRASS_LT); put(x, BASE - 2, M.GRASS_LT); if (i % 4 === 0) put(x + 1, BASE - 3, M.FLOWER_C); }
432
+ // keeper's cottage
433
+ const CX0 = 153; const CB = BASE;
434
+ rect(CX0, CB - 9, 17, 9, M.WALL); rect(CX0 + 14, CB - 9, 3, 9, M.WALL_SH); rect(CX0, CB - 9, 17, 1, M.WALL_SH);
435
+ for (let y = CB - 17; y < CB - 9; y++) { const hw = Math.round(9.8 - (CB - 10 - y) * 1.1); for (let x = CX0 + 8 - hw; x <= CX0 + 8 + hw; x++) put(x, y, (y + x) % 3 === 0 ? M.ROOF_LT : M.ROOF); }
436
+ rect(CX0 - 2, CB - 10, 21, 1, M.ROOF_LT); rect(CX0 + 11, CB - 22, 3, 7, M.BRICK); rect(CX0 + 10, CB - 23, 5, 1, M.STONE_LT);
437
+ rect(CX0 + 3, CB - 6, 4, 6, M.WOOD_DK); put(CX0 + 6, CB - 3, M.WOOD_LT); rect(CX0 + 9, CB - 7, 4, 3, M.GLASS); put(CX0 + 9, CB - 7, M.GLASS_HI); addWin(CX0 + 9, CB - 7, 4, 3, {});
438
+ // lighthouse tower
439
+ const half = (y) => 5.5 + ((y - T) / (BASE - T)) * 3;
440
+ for (let y = T; y < BASE; y++) {
441
+ const hw = Math.round(half(y));
442
+ for (let x = LX - hw; x <= LX + hw; x++) {
443
+ const t = (x - (LX - hw)) / (2 * hw); let m = M.WALL;
444
+ if (t > 0.78 || (t > 0.58 && dth(x, y, 0.55))) m = M.WALL_SH;
445
+ if ((y >= T + 9 && y < T + 15) || (y >= T + 25 && y < T + 31)) m = t > 0.62 ? M.WOOD : M.BAND;
446
+ put(x, y, m);
447
+ }
448
+ }
449
+ rect(LX - 2, BASE - 7, 5, 7, M.WOOD_DK); put(LX + 1, BASE - 4, M.WOOD_LT); rect(LX - 1, T + 19, 3, 4, M.GLASS); put(LX - 1, T + 19, M.GLASS_HI); rect(LX - 1, T + 3, 3, 3, M.GLASS); put(LX - 1, T + 3, M.GLASS_HI);
450
+ rect(LX - 9, T - 3, 19, 3, M.WOOD_DK); rect(LX - 9, T - 3, 19, 1, M.WOOD_LT); for (let x = LX - 9; x <= LX + 9; x += 3) rect(x, T - 6, 1, 3, M.WOOD_DK); rect(LX - 9, T - 7, 19, 1, M.WOOD_DK);
451
+ rect(LX - 4, T - 15, 9, 8, M.GLASS); put(LX - 3, T - 14, M.GLASS_HI); put(LX - 3, T - 13, M.GLASS_HI); for (const dx of [-4, 0, 4]) rect(LX + dx, T - 15, 1, 8, M.WOOD_DK); rect(LX - 5, T - 8, 11, 1, M.WOOD_DK); rect(LX - 5, T - 16, 11, 1, M.WOOD_DK);
452
+ for (let y = T - 22; y < T - 16; y++) { const hw = Math.round(1.6 + (y - (T - 22)) * 1.1); for (let x = LX - hw; x <= LX + hw; x++) put(x, y, x < LX - hw + 2 && y > T - 22 ? M.ROOF_HI : M.ROOF); }
453
+ put(LX, T - 24, M.WOOD_DK); put(LX, T - 23, M.WOOD_DK); put(LX, T - 25, M.LANTERN_HI);
454
+ // pier (left): plank deck, piles standing in the water, a lantern post at the end
455
+ for (let x = xa; x < 106; x++) { put(x, DECK, (x % 6 === 0) ? M.WOOD : M.WOOD_LT); put(x, DECK + 1, M.WOOD); put(x, DECK + 2, M.WOOD_DK); }
456
+ for (let x = xa + 3; x < 104; x += 12) { rect(x, DECK + 3, 2, 7, M.WOOD_DK); put(x, DECK + 3, M.WOOD); rect(x - 1, DECK + 9, 4, 1, M.SEA_HI); }
457
+ for (let x = xa + 9; x < 100; x += 12) line(x, DECK + 3, x + 5, DECK + 7, M.WOOD_DK);
458
+ rect(103, DECK - 9, 2, 9, M.WOOD_DK); rect(102, DECK - 12, 4, 3, M.LANTERN); put(102, DECK - 13, M.WOOD_DK); put(105, DECK - 13, M.WOOD_DK); rect(102, DECK - 14, 4, 1, M.WOOD_DK);
459
+ // beach: tan sand, a thin wet band at the water, a few light grains
460
+ for (let y = SH + 2; y < bh - oy; y++) {
461
+ for (let x = xa; x < xb; x++) {
462
+ const d = y - (SH + 2); const n = noiseA(x * 0.08 + 3, y * 0.25) + noiseB(x * 0.4, y * 0.6) * 0.3;
463
+ let m = M.PATH; if (d < 3) m = dth(x, y, 0.8 - d * 0.25) ? M.PATH_DK : M.PATH; else if (n > 0.98) m = M.SAND_LT; else if (n < 0.3 && dth(x, y, 0.2)) m = M.PATH_DK;
464
+ put(x, y, m);
465
+ }
466
+ }
467
+ const rr = seeded(612); for (let i = 0; i < 26; i++) { const x = xa + ((rr() * bw) | 0); const y = SH + 6 + ((rr() * (bh - oy - SH - 6)) | 0); put(x, y, rr() < 0.6 ? M.SAND_LT : M.STONE_LT); }
468
+ // row boat on the sand
469
+ for (let y = 103; y < 110; y++) { const t = (y - 103) / 6; const l = Math.round(22 + t * 5); const r = Math.round(58 - t * 6); for (let x = l; x <= r; x++) put(x, y, y === 103 ? M.WOOD_LT : x < l + 2 || x > r - 2 ? M.WOOD_DK : (y + x) % 5 === 0 ? M.WOOD_LT : M.WOOD); }
470
+ rect(26, 101, 28, 1, M.WOOD_DK); put(26, 100, M.WOOD_DK); put(53, 100, M.WOOD_DK); rect(36, 104, 8, 1, M.WOOD_LT); rect(50, 98, 1, 7, M.WOOD_LT); rect(49, 97, 3, 1, M.WOOD_LT);
471
+ paintBush(8, 112, 9, 3, 951); paintBush(196, 113, 12, 3, 952); paintBush(112, 115, 8, 2, 953);
472
+ for (let x = xa; x < xb; x += 9) { const y = 106 + (hash2(x, 5) * 7 | 0); put(x, y, M.GRASS_DK); put(x, y - 1, M.GRASS); put(x + 1, y - 2, M.GRASS_LT); put(x - 1, y - 2, M.GRASS_LT); }
473
+ };
474
+ const seaPerson = { x: 84, face: 1, step: 0, bite: 0, nextBite: 6, bob: 0 };
475
+ const walkTo = (p, want, speed, dt) => { const d = want - p.x; if (Math.abs(d) < 0.6) { p.x = want; p.step = 0; return false; } const mv = Math.min(Math.abs(d), speed * dt); p.x += Math.sign(d) * mv; p.face = Math.sign(d); p.step += mv * 0.28; return true; };
476
+ const liveSeaside = (h, dt) => {
477
+ const { YH, SH, LX, T, DECK } = SEAL; const LY = T - 11; const xa = -ox; const xb = bw - ox; const lamp = lampAmt(h); const t = anim;
478
+ // wave crests
479
+ for (let y = YH + 2; y < SH - 1; y++) {
480
+ if (y & 1) continue; const tt = (y - YH) / (SH - YH); const cnt = 3 + Math.round(tt * 4);
481
+ for (let i = 0; i < cnt; i++) {
482
+ const sp = (0.7 + tt * 4.5) * (hash2(y, i + 70) > 0.5 ? 1 : -1); const span = bw + 30;
483
+ const x = Math.round((((hash2(y, i + 50) * span + t * sp) % span) + span) % span) + xa - 15; const len = 2 + Math.floor(tt * 4) + ((hash2(y, i + 9) * 3) | 0);
484
+ if (Math.sin(t * 0.8 + hash2(y, i + 3) * 6.28) < -0.25) continue;
485
+ for (let k2 = 0; k2 < len; k2++) { const xx = x + k2; if (isSeaMat(get(xx, y))) put(xx, y, k2 === 0 || k2 === len - 1 ? M.SEA_LT : M.SEA_HI); }
486
+ }
487
+ }
488
+ // sun / moon glitter on the water
489
+ const sp = sunPos(h); const mp = moonPos(h); const lit = sp && sp.y < YH - 3 ? { x: sp.x, m: M.LIT_HI } : mp && mp.y < YH - 3 ? { x: mp.x, m: M.STAR } : null;
490
+ if (lit) for (let y = YH + 2; y < SH - 1; y++) { const tt = (y - YH) / (SH - YH); const spread = 1.5 + tt * 8; const n = 1 + Math.round(tt * 3); for (let i = 0; i < n; i++) { const r = hash2(y * 5 + i, Math.floor(t * 2.5 + i * 3)); const x = lit.x + Math.round((r - 0.5) * 2 * spread); if (r > 0.25 && isSeaMat(get(x, y))) { put(x, y, lit.m); if (r > 0.7 && isSeaMat(get(x + 1, y))) put(x + 1, y, lit.m); } } }
491
+ // distant village lights
492
+ drawWins(h, winFrac(h) * 1.1);
493
+ for (const w of wins) if (w.tiny && w.seed < winFrac(h) * 1.1 && ((Math.floor(t * 1.5 + w.seed * 9) & 1) === 0)) put(w.x, YH + 1 + (w.seed > 0.5 ? 1 : 0), M.SEA_HI);
494
+ // sailing boat
495
+ const span = 330; const bx = Math.round((((t * 2.4 + 60) % span) + span) % span) - 70 + xa + 10; const by = YH + 5 + Math.round(Math.sin(t * 1.2));
496
+ if (bx > xa - 14 && bx < xb + 14) {
497
+ rect(bx, by, 9, 1, M.WOOD_DK); rect(bx + 1, by + 1, 7, 1, M.WOOD_DK); rect(bx + 1, by, 7, 1, M.WOOD_LT); line(bx + 4, by - 1, bx + 4, by - 11, M.WOOD_DK);
498
+ for (let j = 0; j < 9; j++) { const w2 = Math.round(j * 0.45); for (let i = 1; i <= w2 + 1; i++) put(bx + 4 - i, by - 2 - j, M.CLOUD); }
499
+ for (let j = 0; j < 6; j++) { const w2 = Math.round(j * 0.35); for (let i = 1; i <= w2 + 1; i++) put(bx + 5 + i - 1, by - 2 - j, j > 3 ? M.CLOUD : M.CLOUD_SH); }
500
+ if (lamp > 0.1) { put(bx + 4, by - 12, M.LANTERN_HI); emit(bx + 4, by - 12, 110 * lamp, 2); put(bx + 4, by + 3, M.SEA_HI); }
501
+ }
502
+ // fisherman on the pier
503
+ const away = h >= 22.6 || h < 7.4; const P = seaPerson;
504
+ const moving = calm ? ((P.x = away ? -40 : 84), false) : walkTo(P, away ? -40 : 84, 14, dt);
505
+ if (P.x > xa - 8) {
506
+ if (moving) blit(SPR.walk[Math.floor(P.step) % 4], Math.round(P.x) - 3, DECK - 12, P.face < 0);
507
+ else {
508
+ P.nextBite -= calm ? 0 : dt; if (P.nextBite <= 0) { P.bite = 1.1; P.nextBite = 7 + hash2(Math.floor(t), 4) * 6; } P.bite = Math.max(0, P.bite - dt);
509
+ const spr = P.bite > 0.5 ? SPR.seatUp : (!calm && Math.sin(t * 0.9) > 0.97 ? SPR.seatBlink : SPR.seat); const sx = 90; const sy = DECK - 9;
510
+ blit(spr, sx, sy, true);
511
+ const hx = sx + 6; const hy = sy + 5; const tipx = hx + 12; const tipy = hy - 8 + (P.bite > 0.5 ? 2 : 0);
512
+ line(hx, hy, tipx, tipy, M.WOOD_LT);
513
+ const bobY = DECK + 4 + Math.round(calm ? 0 : Math.sin(t * 1.6)) + (P.bite > 0 ? 1 : 0); const bobX = tipx + 7;
514
+ for (let y = tipy; y < bobY; y++) if (((y - tipy) & 1) === 0 || y > bobY - 3) put(Math.round(mix(tipx, bobX, (y - tipy) / (bobY - tipy))), y, M.STAR_DIM);
515
+ put(bobX, bobY, M.LANTERN_HI); put(bobX, bobY + 1, M.ROOF_LT);
516
+ if (P.bite > 0.7 && P.bite < 1.0) { put(bobX - 1, bobY - 1, M.FOAM); put(bobX + 1, bobY - 2, M.FOAM); put(bobX + 2, bobY - 1, M.FOAM); }
517
+ }
518
+ }
519
+ // pier lantern
520
+ const pl = lamp > 0.05; rect(103, DECK - 11, 2, 2, pl ? M.LANTERN_HI : M.LANTERN); if (pl) { emit(103, DECK - 11, 150 * lamp, 2); emit(104, DECK - 10, 150 * lamp, 2); pool(104, DECK + 1, 12, 5, lamp * 0.7); }
521
+ // lighthouse lamp + sweeping beam
522
+ if (lamp > 0.02) {
523
+ for (let j = 0; j < 8; j++) for (let i = 0; i < 9; i++) { const xx = LX - 4 + i; const mm = get(xx, T - 15 + j); if (mm === M.GLASS || mm === M.GLASS_HI) put(xx, T - 15 + j, j > 5 ? M.LIT : M.LIT_HI); }
524
+ for (let j = 0; j < 8; j += 1) for (let i = 1; i < 8; i += 2) emit(LX - 4 + i, T - 15 + j, 230 * lamp, 0);
525
+ const th = t * 0.55; const bx2 = Math.cos(th); const LYb = T - 11; const dir = bx2 >= 0 ? 1 : -1; const ab = Math.abs(bx2);
526
+ if (ab > 0.1) { const len = 150 * ab; for (let d = 6; d < len; d += 1) { const hh = 0.8 + d * 0.05 * ab; const fade = (1 - d / len) * (0.5 + 0.5 * lamp); for (let dy = -Math.floor(hh); dy <= hh; dy++) { const x = LX + dir * d; const y = LYb + dy + Math.round(d * 0.02); if (dth(x, y, fade * 2.2 * (1 - Math.abs(dy) / (hh + 1.5)))) { const mm = get(x, y); if (mm === 0 || isSeaMat(mm) || mm === M.STAR || mm === M.STAR_DIM) put(x, y, d < len * 0.25 ? M.LIT_HI : M.LIT); } } } }
527
+ if (ab < 0.45) { const f = 1 - ab / 0.45; disc(LX, LYb, 2 + Math.round(f * 2), M.LIT_HI); for (let k2 = 0; k2 < 18; k2++) { const a = k2 * 0.349; const r2 = 6 + f * 6; if (dth(Math.round(LX + Math.cos(a) * r2), Math.round(LYb + Math.sin(a) * r2), f)) putSky(Math.round(LX + Math.cos(a) * r2), Math.round(LYb + Math.sin(a) * r2), M.LIT); } }
528
+ homeLight(LX, LYb, 20000 * lamp);
529
+ }
530
+ // surf at the shoreline
531
+ const wash = 1.6 * Math.sin(t * 0.7) + 0.8 * Math.sin(t * 1.9 + 1);
532
+ for (let x = xa; x < xb; x++) { if (x > 116 && get(x, SH - 1) === M.ROCK) continue; const sy2 = Math.round(SH + wash + Math.sin(x * 0.09 + t * 0.6) * 1.2); const m0 = get(x, sy2); if (m0 === M.WOOD_DK || m0 === M.ROCK || m0 === M.STONE || m0 === M.STONE_LT) continue; put(x, sy2, M.FOAM); if (((x + Math.floor(t * 2)) & 1) === 0) put(x, sy2 + 1, M.SEA_HI); put(x, sy2 + 2, M.PATH_DK); }
533
+ };
534
+
535
+ // ===== Scene: mountain cabin (snow, pines, chopping wood, aurora at night) =====
536
+ const pineTree = (cx, base, h, tiers, snowy) => {
537
+ rect(cx - 1, base - 7, 3, 7, M.BARK); put(cx - 1, base - 7, M.BARK_LT); put(cx - 1, base - 5, M.BARK_LT);
538
+ const span = h - 6; const tierH = Math.round((span / tiers) * 1.9);
539
+ for (let i = 0; i < tiers; i++) {
540
+ const top = base - h + Math.round((i * span) / tiers); const maxW = 2 + Math.round(((i + 1) * h * 0.34) / tiers);
541
+ for (let y = top; y < Math.min(top + tierH, base - 5); y++) {
542
+ const t = (y - top) / tierH; const hw = Math.round(0.6 + t * (maxW - 0.6));
543
+ for (let x = cx - hw; x <= cx + hw; x++) {
544
+ const rel = (x - cx) / Math.max(hw, 1);
545
+ if (t > 0.9 && ((x + i) & 1) && Math.abs(rel) < 0.85) continue;
546
+ const v = 0.5 - rel * 0.45 + (bayerAt(x, y) / 16 - 0.5) * 0.36 + (noiseB(x * 0.5 + i, y * 0.4) - 0.5) * 0.3;
547
+ let m = v > 0.72 ? M.LEAF2 : v > 0.4 ? M.LEAF1 : M.LEAF0;
548
+ if (snowy) { const edge = hw - Math.abs(x - cx); if (edge < 1.6 && t < 0.85 && t > 0.05) m = rel < 0 ? M.SNOW : M.SNOW_SH; else if (t < 0.28 && edge < 3.4 && dth(x, y, 0.5)) m = rel < 0 ? M.SNOW : M.SNOW_SH; }
549
+ put(x, y, m);
550
+ }
551
+ }
552
+ }
508
553
  };
509
554
  const paintMountainCabin = () => {
510
- rect(0,0,CORE_W,112,0);
511
- // layered mountain silhouettes
512
- for(let x=0;x<CORE_W;x++){
513
- const a=35+Math.round(Math.sin(x*.045)*9+Math.sin(x*.11)*4);
514
- const b=50+Math.round(Math.sin(x*.035+1.7)*8+Math.sin(x*.08)*3);
515
- for(let y=a;y<68;y++) put(x,y,M.HILL_FAR);
516
- for(let y=b;y<77;y++) put(x,y,M.HILL_NEAR);
555
+ wins.length = 0; const xa = -ox; const xb = bw - ox; const yb = bh - oy;
556
+ // ---- far range: left-lit rock faces, snow above the snow line ----
557
+ const tent = (x, cx, hgt, s) => Math.max(0, hgt - Math.abs(x - cx) * s);
558
+ const far = (x) => Math.round(70 - Math.max(tent(x, 40, 44, 1.0), tent(x, 156, 29, 0.92), tent(x, 96, 17, 0.9), tent(x, -6, 34, 1.0), tent(x, 214, 30, 0.95)) + (noiseB(x * 0.33, 2) - 0.5) * 5);
559
+ for (let x = xa; x < xb; x++) {
560
+ const top = far(x); const lit = far(x + 2) - far(x - 2) < 0;
561
+ for (let y = top; y < 80; y++) {
562
+ const snowLine = 52 + (noiseA(x * 0.09, 6) - 0.5) * 9 + (bayerAt(x, y) / 16 - 0.5) * 3.5; const streak = noiseB(x * 0.55, y * 0.12) > 0.68;
563
+ let m; if (y < snowLine) m = lit ? M.SNOW : M.SNOW_SH; else m = lit ? (streak ? M.HILL_NEAR : M.HILL_FAR) : (streak ? M.ROCK : M.HILL_NEAR);
564
+ put(x, y, m);
565
+ }
566
+ }
567
+ // ---- forested foothills ----
568
+ const hill = (x) => Math.round(64 + noiseA(x * 0.035 + 11, 4) * 10 - 2 * Math.sin(x * 0.05));
569
+ for (let x = xa; x < xb; x++) { const top = hill(x); for (let y = top; y < 84; y++) { const d = y - top; put(x, y, d < 1 ? M.LEAF0 : dth(x, y, 0.4 - d * 0.03) ? M.SNOW_SH : (noiseB(x * 0.6, y * 0.7) > 0.55 ? M.TREE_FAR : M.LEAF0)); } }
570
+ for (let x = xa + 2; x < xb; x += 3 + ((hash2(x, 5) * 3) | 0)) { const top = hill(x); const hh = 4 + ((hash2(x, 7) * 4) | 0); for (let j = 0; j < hh; j++) { const hw = Math.floor(j / 3) + (j > 0 ? 0 : 0); for (let i = -hw; i <= hw; i++) put(x + i, top - hh + j + 1, (i < 0 && j % 3 === 0) ? M.SNOW_SH : M.LEAF0); } }
571
+ // ---- snow field with soft dune shading ----
572
+ for (let y = 78; y < yb; y++) for (let x = xa; x < xb; x++) {
573
+ const n = noiseA(x * 0.045 + 3, y * 0.16 + 1) + (noiseB(x * 0.3, y * 0.5) - 0.5) * 0.2 + (bayerAt(x, y) / 16 - 0.5) * 0.16;
574
+ const dune = Math.sin(x * 0.07 + y * 0.18) * 0.12;
575
+ put(x, y, n + dune > 0.72 ? M.SNOW_SH : (y < 81 && dth(x, y, 0.5)) ? M.SNOW_SH : M.SNOW);
517
576
  }
518
- // snow caps
519
- for(let x=0;x<CORE_W;x++){const peak=35+Math.round(Math.sin(x*.045)*9+Math.sin(x*.11)*4);if(x%2===0){put(x,peak,M.CLOUD);put(x,peak+1,M.CLOUD_SH);}}
520
- rect(0,77,CORE_W,35,M.GRASS_DK);
521
- for(let y=82;y<112;y+=7) for(let x=(y%2)*5;x<CORE_W;x+=18) put(x,y,M.STONE_LT);
522
- // pines
523
- const pine=(cx,base,h)=>{rect(cx-1,base-h,3,h,M.BARK);for(let j=0;j<4;j++){const yy=base-h+j*Math.floor(h/4);const w=3+j*3;for(let y=yy;y<yy+Math.floor(h/4)+2;y++)for(let x=cx-w;x<=cx+w;x++)if(Math.abs(x-cx)<=w-(y-yy)*.45)put(x,y,j%2?M.LEAF1:M.LEAF0);}};
524
- pine(18,91,37);pine(34,88,28);pine(178,94,42);pine(160,89,27);pine(116,86,22);
525
- // wooden cabin
526
- rect(66,55,62,35,M.WALL); for(let y=60;y<88;y+=5)rect(66,y,62,1,M.WOOD);
527
- for(let y=34;y<57;y++){const t=(y-34)/23;for(let x=Math.round(58+t*8);x<=136-Math.round(t*8);x++)put(x,y,M.ROOF);}
528
- rect(57,53,80,4,M.WOOD_DK); rect(88,67,17,23,M.WOOD_DK);rect(90,69,13,21,M.WOOD);
529
- rect(71,65,13,12,M.GLASS);rect(73,67,9,8,M.LIT);rect(77,65,2,12,M.WOOD_DK);
530
- rect(111,64,12,13,M.GLASS);rect(112,65,10,3,M.GLASS_HI);
531
- rect(87,90,20,3,M.STONE);rect(83,93,28,2,M.STONE_LT);
532
- // chimney and smoke
533
- rect(115,34,8,17,M.BRICK);rect(113,32,12,3,M.STONE_LT);
534
- // Jagged distant peaks with layered snow shelves and exposed rock
535
- for(let x=0;x<CORE_W;x+=1){const peak=35+Math.round(Math.sin(x*.045)*9+Math.sin(x*.11)*4);if(x%3===0){put(x,peak+3,M.CLOUD);put(x+1,peak+5,M.CLOUD_SH);}}
536
- for(let x=0;x<CORE_W;x+=17){const y=44+(x%5);rect(x,y,5,2,M.HILL_FAR);put(x+2,y-1,M.CLOUD);}
537
- // Pine boughs, trunks and snow caught on branches
538
- const snowPine=(cx,base,h)=>{for(let j=0;j<5;j++){const yy=base-h+j*Math.floor(h/5);const w=3+j*3;for(let x=cx-w;x<=cx+w;x++){if(Math.abs(x-cx)<=w){put(x,yy+1,M.LEAF2);if((x+yy)%3===0)put(x,yy+2,M.CLOUD_SH);}}}rect(cx,base-h,1,h,M.WOOD_DK);};
539
- snowPine(48,91,30);snowPine(145,92,34);snowPine(190,90,29);snowPine(8,94,26);
540
- // Cabin log courses, corner joinery and stone foundation
541
- for(let y=58;y<89;y+=4){rect(67,y,60,1,M.WOOD_DK);for(let x=70+(y%3);x<124;x+=13)put(x,y+1,M.WOOD_LT);}
542
- rect(63,87,68,5,M.STONE);for(let x=66;x<128;x+=8)put(x,89,M.STONE_LT);
543
- // Porch roof, posts, railings and steps
544
- rect(78,51,48,3,M.WOOD_DK);rect(82,48,40,3,M.ROOF_LT);
545
- rect(81,53,2,35,M.WOOD_DK);rect(123,53,2,35,M.WOOD_DK);
546
- rect(80,78,45,2,M.WOOD_LT);for(let x=84;x<123;x+=7)rect(x,72,1,8,M.WOOD_DK);
547
- rect(88,91,18,2,M.WOOD_DK);rect(86,93,23,2,M.STONE_LT);rect(83,95,29,2,M.STONE);
548
- // Window shutters, warm panes and window boxes
549
- rect(69,63,16,15,M.WOOD_DK);rect(71,65,12,11,M.GLASS);
550
- rect(73,67,8,7,M.LIT);rect(77,65,2,11,M.WOOD_DK);
551
- rect(67,62,3,17,M.WOOD);rect(84,62,3,17,M.WOOD);
552
- rect(69,79,16,2,M.WOOD_DK);for(let x=71;x<84;x+=3)put(x,78,M.LEAF2);
553
- rect(108,61,17,17,M.WOOD_DK);rect(110,63,13,13,M.GLASS);
554
- rect(112,65,9,9,M.LIT_DK);rect(116,64,2,12,M.WOOD_DK);
555
- // Firewood stack, lantern, mailbox and snow-covered stones
556
- rect(133,82,18,5,M.WOOD_DK);for(let y=83;y<87;y+=2)for(let x=135+(y%3);x<149;x+=6)rect(x,y,5,1,M.WOOD);
557
- rect(154,76,2,12,M.WOOD_DK);rect(151,74,8,3,M.LIT);put(155,75,M.LIT_HI);
558
- rect(55,84,7,5,M.WOOD_DK);rect(56,82,5,3,M.BRICK_LT);put(58,81,M.LIT_HI);
559
- // Snowdrifts, tracks and scattered blue-white snow sparkle
560
- for(let x=0;x<CORE_W;x+=1){const drift=96+Math.round(Math.sin(x*.07)*2+Math.sin(x*.19));if(x%2===0)put(x,drift,M.CLOUD);if(x%5===0)put(x,drift+1,M.CLOUD_SH);}
561
- for(let i=0;i<7;i++){rect(112+i*3,99+i%2*2,2,1,M.CLOUD_SH);put(114+i*3,97+i%3,M.CLOUD);}
562
- for(let x=3;x<CORE_W;x+=29){put(x,107,M.CLOUD);put(x+1,106,M.CLOUD_SH);}
563
- };
564
- const scene = ['countryside','japanese-street','apartment','seaside','mountain-cabin'].includes(dataset.scene) ? (dataset.scene || 'countryside') : 'countryside';
565
- let sceneAnim = 0;
566
- const drawSceneResident = (dt) => {
567
- if (!calm) sceneAnim += dt;
568
- if (scene === 'apartment') {
569
- const pose = Math.sin(sceneAnim * 0.6) > 0.85 ? SPR.seatBlink : SPR.seat;
570
- blit(pose, 128, 61);
571
- return;
577
+ for (let i = 0; i < 40; i++) { const x = xa + ((hash2(i, 31) * bw) | 0); const y = 84 + ((hash2(i, 32) * (yb - 84)) | 0); put(x, y, M.SNOW_SH); put(x + 1, y, M.SNOW_SH); }
578
+ // ---- mid pines ----
579
+ pineTree(52, 86, 34, 5, true); pineTree(62, 83, 24, 4, true); pineTree(138, 84, 26, 4, true); pineTree(122, 82, 18, 3, true);
580
+ // ---- cabin (front gable) ----
581
+ const CX = 90; const AP = 36; const EY = 62; const SLOPE = 0.76;
582
+ // external stone chimney on the right wall
583
+ for (let y = 33; y < 88; y++) for (let x = 116; x < 124; x++) { const k2 = ((x + (((y >> 1) & 1) * 2)) % 4) < 2; put(x, y, x > 120 ? M.STONE : k2 ? M.STONE_LT : M.STONE); if (y % 2 === 0 && x === 116) put(x, y, M.STONE); }
584
+ rect(115, 31, 10, 3, M.STONE_LT); rect(115, 33, 10, 1, M.STONE); rect(118, 29, 4, 2, M.ROCK);
585
+ // gable wall (vertical planks), main log wall
586
+ for (let y = AP; y < EY; y++) { const hw = (y - AP) / SLOPE; for (let x = Math.round(CX - hw); x <= Math.round(CX + hw); x++) put(x, y, (x % 4 === 0) ? M.WOOD_DK : (x % 4 === 1 ? M.WOOD_LT : M.WOOD)); }
587
+ for (let y = EY; y < 88; y++) for (let x = 66; x < 115; x++) { const row = (y - EY) % 4; let m = row === 0 ? M.WOOD_LT : row === 3 ? M.WOOD_DK : M.WOOD; if (x > 109 && dth(x, y, 0.5)) m = row === 0 ? M.WOOD : M.WOOD_DK; put(x, y, m); }
588
+ for (let y = EY; y < 88; y += 4) { for (const cx2 of [66, 67, 112, 113]) { put(cx2, y, M.WOOD_LT); put(cx2, y + 1, M.BARK); } }
589
+ rect(64, 87, 52, 3, M.STONE); for (let x = 64; x < 116; x += 6) put(x, 87, M.STONE_LT); rect(64, 89, 52, 1, M.ROCK);
590
+ // attic round window
591
+ disc(CX, 50, 3, M.WOOD_DK); disc(CX, 50, 2, M.GLASS); put(CX - 1, 49, M.GLASS_HI);
592
+ // roof rakes with thick snow on top (snow overhang gets a blue underside)
593
+ for (let y = AP - 3; y < EY + 4; y++) {
594
+ const hw = (y - AP) / SLOPE; const l = Math.round(CX - hw); const r = Math.round(CX + hw);
595
+ for (let i = 0; i < 3; i++) { put(l - i + (y >= EY ? 0 : 0), y, M.WOOD_DK); put(r + i, y, M.WOOD_DK); }
596
+ for (let i = 3; i < 7; i++) { const mL = i === 6 ? M.SNOW_SH : (i === 3 && y > AP + 8 ? M.SNOW_SH : M.SNOW); put(l - i + 2, y - 1, mL); put(r + i - 2, y - 1, i === 6 || i === 3 ? M.SNOW_SH : M.SNOW); }
572
597
  }
573
- const x = scene === 'japanese-street' ? 104 + Math.sin(sceneAnim * 0.18) * 20 : scene === 'seaside' ? 104 : 91;
574
- const y = scene === 'seaside' ? 82 : 88;
575
- const moving = !calm && scene !== 'seaside';
576
- const sprite = moving ? SPR.walk[Math.floor(sceneAnim * 3) % 4] : SPR.stand;
577
- blit(sprite, Math.round(x)-3, y-12, Math.sin(sceneAnim * 0.18) < 0);
598
+ for (let x = 56; x <= 124; x++) { if ((x * 5) % 7 < 4) put(x, EY + 4, M.SNOW); }
599
+ rect(57, EY + 3, 68, 1, M.WOOD_DK); rect(CX - 3, AP - 4, 7, 4, M.SNOW); put(CX - 1, AP - 5, M.SNOW); put(CX, AP - 5, M.SNOW);
600
+ // porch canopy and posts
601
+ rect(77, 69, 26, 3, M.WOOD_DK); rect(76, 66, 28, 3, M.SNOW); rect(78, 68, 24, 1, M.SNOW_SH); rect(79, 72, 2, 16, M.WOOD_DK); rect(99, 72, 2, 16, M.WOOD_DK);
602
+ // door
603
+ rect(84, 73, 12, 15, M.WOOD_DK); rect(85, 74, 10, 14, M.WOOD); for (let x = 87; x < 95; x += 3) rect(x, 74, 1, 14, M.WOOD_DK); rect(85, 74, 10, 4, M.GLASS); rect(89, 74, 1, 4, M.WOOD_DK); put(93, 81, M.LANTERN_HI);
604
+ addWin(85, 74, 4, 4, { always: true }); addWin(90, 74, 5, 4, { always: true });
605
+ // side windows with shutters + flower boxes
606
+ const winAt = (x0) => { rect(x0 - 2, 71, 14, 13, M.WOOD_DK); rect(x0, 73, 10, 9, M.GLASS); rect(x0, 73, 10, 1, M.GLASS_HI); put(x0 + 1, 74, M.GLASS_HI); rect(x0 + 4, 73, 2, 9, M.WOOD_DK); rect(x0 - 3, 72, 2, 11, M.WOOD); rect(x0 + 11, 72, 2, 11, M.WOOD); rect(x0 - 1, 84, 12, 2, M.WOOD_DK); for (let x = x0; x < x0 + 10; x += 2) { put(x, 83, M.SNOW); } addWin(x0, 73, 10, 9, { always: true, mull: 1 }); };
607
+ winAt(68); winAt(102);
608
+ // lantern on the porch
609
+ rect(81, 74, 1, 2, M.WOOD_DK); rect(80, 76, 3, 4, M.LANTERN); rect(80, 76, 3, 1, M.WOOD_DK); rect(80, 80, 3, 1, M.WOOD_DK);
610
+ // steps
611
+ rect(81, 88, 18, 2, M.STONE_LT); rect(79, 90, 22, 2, M.STONE); rect(78, 92, 24, 1, M.ROCK);
612
+ // snow drifts against the cabin
613
+ for (let x = 62; x < 120; x++) { const dr = 90 + Math.round(Math.sin(x * 0.3) * 1.2); put(x, dr, M.SNOW); if (x % 3 === 0) put(x, dr + 1, M.SNOW_SH); }
614
+ // ---- woodpile, chopping block, footprints ----
615
+ rect(150, 86, 24, 3, M.SNOW_SH);
616
+ for (let r = 0; r < 4; r++) for (let c = 0; c < 8; c++) { const x = 151 + c * 3 + (r & 1); const y = 77 + r * 3; if (x > 172) continue; rect(x, y, 3, 3, M.WOOD); put(x + 1, y + 1, (c + r) % 2 ? M.WOOD_LT : M.BARK_LT); put(x, y, M.WOOD_DK); put(x + 2, y + 2, M.WOOD_DK); }
617
+ rect(149, 74, 26, 3, M.SNOW); rect(150, 77, 24, 1, M.SNOW_SH); put(149, 77, M.SNOW_SH); put(174, 77, M.SNOW_SH);
618
+ rect(140, 82, 8, 6, M.WOOD); rect(140, 82, 8, 2, M.WOOD_LT); put(141, 83, M.WOOD); put(144, 83, M.WOOD_DK); rect(147, 83, 1, 5, M.WOOD_DK); rect(139, 81, 10, 1, M.SNOW); rect(140, 88, 8, 1, M.SNOW_SH);
619
+ for (let i = 0; i < 12; i++) { const x = 98 + i * 3 + (i & 1) * 2; const y = 95 + (i % 3 === 1 ? 1 : 0) - Math.floor(i / 6); put(x, y, M.SNOW_DK); put(x + 1, y + (i & 1), M.SNOW_DK); }
620
+ // ---- foreground pines ----
621
+ pineTree(24, 100, 66, 7, true); pineTree(179, 98, 58, 7, true); pineTree(196, 104, 40, 5, true);
622
+ const rr = seeded(77); for (let i = 0; i < 14; i++) { const x = xa + ((rr() * bw) | 0); const y = 100 + ((rr() * (yb - 100)) | 0); rect(x, y, 3, 1, M.SNOW_SH); }
578
623
  };
624
+ const cabinP = { x: 132, inside: false, door: 0, face: 1, step: 0, cycle: 0, split: -1 };
625
+ const liveCabin = (h, dt) => {
626
+ const t = anim; const lamp = lampAmt(h); const xa = -ox; const xb = bw - ox; const P = cabinP;
627
+ // aurora curtains
628
+ const an = nightF(h);
629
+ if (an > 0.5) {
630
+ const amp = smoothstep(0.5, 0.95, an);
631
+ for (let x = xa; x < xb; x++) {
632
+ const base = 27 + Math.sin(x * 0.045 + t * 0.22) * 7 + Math.sin(x * 0.11 - t * 0.35) * 3; const len = 17 + 11 * Math.sin(x * 0.03 + t * 0.13 + 1.3) + noiseB(x * 0.15, t * 0.25) * 10;
633
+ const ray = 0.55 + 0.45 * Math.sin(x * 1.6 + noiseA(x * 0.2, t * 0.2) * 6);
634
+ for (let y = Math.floor(base - len); y <= base; y++) { const f = (y - (base - len)) / len; const pr = amp * Math.pow(f, 1.4) * 1.7 * ray; if (dth(x, y, pr)) putSky(x, y, f > 0.86 ? M.AUR_HI : f > 0.45 ? M.AUR_MID : M.AUR_LO); }
635
+ }
636
+ }
637
+ // chimney smoke
638
+ if (!calm) { fx.t += dt * 3.2; while (fx.t >= 1) { fx.t -= 1; fx.smoke.push({ x: 119 + Math.random() * 2, y: 28, t: 0, seed: Math.random() * 10 }); } }
639
+ for (let i = fx.smoke.length - 1; i >= 0; i--) { const s = fx.smoke[i]; s.t += dt; s.y -= dt * (6.5 - s.t * 0.7); s.x += dt * (2.4 + Math.sin(s.t * 1.7 + s.seed) * 2.4); if (s.t > 4.6 || s.y < -4) { fx.smoke.splice(i, 1); continue; } const size = 1 + Math.floor(s.t * 0.75); for (let dy = 0; dy < size; dy++) for (let dx = 0; dx < size; dx++) { const x = Math.round(s.x) + dx; const y = Math.round(s.y) + dy; if (bayerAt(x, y) < 16 - s.t * 2.9) put(x, y, s.t > 2.4 ? M.SMOKE_DK : M.SMOKE); } }
640
+ if (calm) for (let i = 0; i < 5; i++) rect(119 + i * 2, 24 - i * 4, 1 + (i >> 1), 2, M.SMOKE);
641
+ // windows, lantern, light on the snow
642
+ drawWins(h, 0, 1.3);
643
+ if (lamp > 0.05) { rect(80, 76, 3, 4, M.LANTERN_HI); for (let y = 77; y < 79; y++) for (let x = 80; x < 83; x++) emit(x, y, 200 * lamp, 0); pool(90, 92, 30, 7, lamp * 0.95); pool(73, 88, 10, 6, lamp * 0.6); pool(107, 88, 10, 6, lamp * 0.6); pool(81, 82, 8, 6, lamp * 0.7); homeLight(90, 78, 16000 * lamp); }
644
+ // daytime shadows on the snow (cabin + pines), opposite the sun
645
+ const sp = sunPos(h);
646
+ if (sp && sp.y < 70 && h > 6.4 && h < 17.4) {
647
+ const off = clamp((96 - sp.x) / 80, -1.3, 1.3); const sh = (cx, base, rx, ry) => { for (let y = base - ry; y <= base + ry; y++) for (let x = Math.floor(cx - rx); x <= cx + rx; x++) { if ((((x - cx) / rx) ** 2 + ((y - base) / ry) ** 2) > 1) continue; if (get(x, y) === M.SNOW) put(x, y, M.SNOW_SH); } };
648
+ sh(24 + off * 30, 101, 30, 3); sh(179 + off * 24, 99, 26, 3); sh(52 + off * 16, 87, 14, 2); sh(90 + off * 22, 92, 36, 3);
649
+ }
650
+ // the woodcutter
651
+ const outdoors = h >= 8.4 && h < 17.6; const wantX = outdoors ? 132 : 90;
652
+ if (calm) { P.inside = !outdoors; P.x = wantX; }
653
+ else {
654
+ P.door = Math.max(0, P.door - dt);
655
+ if (P.inside && outdoors) { P.inside = false; P.x = 90; P.door = 0.5; }
656
+ if (!P.inside) { const d = wantX - P.x; if (Math.abs(d) > 0.6) { const mv = Math.min(Math.abs(d), 16 * dt); P.x += Math.sign(d) * mv; P.face = Math.sign(d); P.step += mv * 0.28; } else { P.x = wantX; P.step = 0; if (!outdoors) { P.inside = true; P.door = 0.5; } } }
657
+ }
658
+ if (P.door > 0) { rect(85, 74, 10, 14, M.SIL); rect(85, 74, 2, 14, M.WOOD_LT); }
659
+ if (!P.inside) {
660
+ const base = 88; const x0 = Math.round(P.x) - 3; const moving = Math.abs(wantX - P.x) > 0.6;
661
+ if (moving) blit(SPR.walk[Math.floor(P.step) % 4], x0, base - 12, P.face < 0);
662
+ else if (outdoors) {
663
+ P.cycle = calm ? 1.2 : (P.cycle + dt) % 4.4; const c = P.cycle; const bx = 140; const by = 82;
664
+ let spr = SPR.stand; let axe = 0;
665
+ if (c > 2.5 && c < 2.95) { spr = SPR.up; axe = 1; } else if (c >= 2.95 && c < 3.25) { spr = SPR.crouch; axe = 2; } else if (c >= 3.25 && c < 3.6) { spr = SPR.stand; axe = 3; } else if (!calm && Math.sin(t * 0.9) > 0.93) spr = SPR.blink;
666
+ blit(spr, x0, base - 12, false);
667
+ if (axe === 1) { rect(x0 + 3, base - 18, 1, 6, M.WOOD_DK); rect(x0 + 3, base - 19, 3, 2, M.STONE_LT); }
668
+ else if (axe === 2) { line(x0 + 6, base - 7, x0 + 10, base - 5, M.WOOD_DK); rect(x0 + 10, base - 7, 2, 3, M.STONE_LT); }
669
+ else if (axe === 3) { rect(x0 + 7, base - 9, 1, 6, M.WOOD_DK); }
670
+ const sp2 = c > 2.95 && c < 3.9 ? (c - 2.95) : -1;
671
+ if (sp2 < 0) { rect(bx + 1, by - 3, 6, 3, M.WOOD_LT); rect(bx + 1, by - 3, 6, 1, M.WOOD); put(bx + 2, by - 2, M.WOOD_DK); }
672
+ else { const e = Math.min(1, sp2 / 0.9); rect(bx + 1 - Math.round(e * 3), by - 3 + Math.round(e * e * 5), 3, 3, M.WOOD); rect(bx + 4 + Math.round(e * 3), by - 3 + Math.round(e * e * 5), 3, 3, M.WOOD_LT); if (sp2 < 0.25) { put(bx - 1, by - 5, M.SNOW); put(bx + 8, by - 6, M.SNOW); put(bx + 3, by - 7, M.SNOW); } }
673
+ }
674
+ }
675
+ };
676
+
677
+ // ===== Scene: Japanese street (machiya, konbini, ramen shop, utility poles, Fuji) =====
678
+ const STR = { GY: 82, P1: 69, P2: 171 };
679
+ const paintJapaneseStreet = () => {
680
+ wins.length = 0; const { GY, P1, P2 } = STR; const xa = -ox; const xb = bw - ox; const yb = bh - oy;
681
+ // ---- far layer: low hills, Mt. Fuji, far town ----
682
+ const fuji = (x) => Math.round(34 + Math.max(0, Math.abs(x - 150) - 5) * 0.5 + (noiseB(x * 0.3, 3) - 0.5) * 1.2);
683
+ for (let x = xa; x < xb; x++) { const top = fuji(x); if (top < 72) for (let y = top; y < 76; y++) { const sn = y < top + 8 + (noiseA(x * 0.25, 1) * 4) - (x > 150 ? 2 : 0); put(x, y, sn ? (x <= 150 ? M.SNOW : M.SNOW_SH) : (x <= 150 ? M.HILL_FAR : M.HILL_NEAR)); } }
684
+ for (let x = xa; x < xb; x++) { const top = Math.round(60 + noiseA(x * 0.04 + 5, 2) * 7); for (let y = top; y < 76; y++) put(x, y, dth(x, y, 0.3) && y > top ? M.HILL_FAR : M.HILL_NEAR); }
685
+ for (let x = xa; x < xb; x += 5 + ((hash2(x, 2) * 5) | 0)) { const w = 4 + ((hash2(x, 3) * 4) | 0); const hh = 4 + ((hash2(x, 4) * 8) | 0); rect(x, 70 - hh, w, hh + 6, M.CONCRETE_SH); if (hash2(x, 6) < 0.55) addWin(x + 1, 72 - hh, 1, 1, { tiny: true }); }
686
+ // ---- A: machiya (left) ----
687
+ const AX0 = 6; const AX1 = 58;
688
+ rect(AX0, 40, AX1 - AX0, GY - 40, M.PLASTER); rect(AX1 - 4, 40, 4, GY - 40, M.PLASTER_SH);
689
+ for (let y = 41; y < 58; y++) for (let x = AX0; x < AX1; x++) if (y < 44 && dth(x, y, (44 - y) * 0.3)) put(x, y, M.PLASTER_SH);
690
+ // lattice windows on the second floor
691
+ for (const wx of [12, 38]) { rect(wx - 1, 44, 17, 13, M.WOOD_DK); rect(wx, 45, 15, 11, M.GLASS_SKY); rect(wx, 45, 15, 1, M.GLASS_SKY_HI); for (let x = wx + 1; x < wx + 15; x += 2) rect(x, 45, 1, 11, M.WOOD_LT); rect(wx, 50, 15, 1, M.WOOD_DK); addWin(wx, 45, 15, 11, {}); }
692
+ rect(AX0 - 1, 57, AX1 - AX0 + 2, 2, M.WOOD_DK);
693
+ // hip roof (kawara) with ridge and end ornaments
694
+ for (let y = 26; y < 41; y++) {
695
+ const r0 = y - 26; const l = Math.round(15 - r0 * 0.9); const r = Math.round(49 + r0 * 0.9);
696
+ for (let x = l; x <= r; x++) { const even = r0 % 2 === 0; let m = even ? M.ROOF_LT : M.ROOF; if (even && (x + ((r0 >> 1) & 1) * 2) % 4 === 0) m = M.ROOF; if (even && x - l < 6 + r0 && dth(x, y, 0.45)) m = M.ROOF_HI; put(x, y, m); }
697
+ }
698
+ rect(14, 25, 36, 1, M.ROOF_HI); rect(12, 23, 3, 3, M.ROOF_LT); rect(49, 23, 3, 3, M.ROOF_LT); put(13, 22, M.ROOF_HI); put(50, 22, M.ROOF_HI); rect(2, 41, 60, 1, M.WOOD_DK);
699
+ // lower eave
700
+ for (let y = 59; y < 63; y++) { const r0 = y - 59; for (let x = AX0 - 3 - r0; x < AX1 + 3 + r0; x++) put(x, y, r0 === 3 ? M.ROOF : (r0 % 2 === 0 ? M.ROOF_LT : M.ROOF)); } rect(AX0 - 6, 63, AX1 - AX0 + 12, 1, M.WOOD_DK);
701
+ // ground floor: wooden lattice, sliding door, noren, lantern
702
+ rect(AX0, 64, AX1 - AX0, GY - 64, M.WOOD); for (let x = AX0; x < AX1; x += 3) rect(x, 64, 1, GY - 64, M.WOOD_LT); for (let y = 64; y < GY; y += 9) rect(AX0, y, AX1 - AX0, 1, M.WOOD_DK);
703
+ rect(20, 66, 24, 16, M.WOOD_DK); rect(21, 67, 22, 15, M.GLASS_SKY); for (let x = 21; x < 43; x += 5) rect(x, 67, 1, 15, M.WOOD_DK); rect(21, 74, 22, 1, M.WOOD_DK); addWin(21, 67, 22, 7, { mull: 1 });
704
+ rect(19, 63, 26, 2, M.WOOD_DK); for (let x = 21; x < 43; x += 6) { rect(x, 65, 5, 9, M.NOREN); rect(x + 1, 67, 3, 1, M.CLOUD); rect(x + 2, 66, 1, 4, M.CLOUD); }
705
+ rect(11, 66, 1, 4, M.WOOD_DK); rect(8, 70, 7, 8, M.LANTERN); rect(8, 70, 7, 1, M.WOOD_DK); rect(8, 77, 7, 1, M.WOOD_DK); rect(10, 72, 3, 3, M.LANTERN_HI);
706
+ rect(AX1 - 12, 74, 8, 8, M.BRICK); rect(AX1 - 13, 73, 10, 1, M.BRICK_LT); for (let i = 0; i < 4; i++) { put(AX1 - 11 + i * 2, 72, M.LEAF2); put(AX1 - 12 + i * 2, 71, M.LEAF1); }
707
+ // ---- B: narrow alley with vending machines ----
708
+ rect(58, 30, 20, GY - 30, M.ASPHALT_DK); for (let y = 30; y < GY; y++) for (let x = 58; x < 78; x++) if (dth(x, y, 0.1 + (y - 30) * 0.006)) put(x, y, M.CONCRETE_SH);
709
+ const vend = (x0) => { rect(x0, 62, 8, 20, M.CONCRETE_SH); rect(x0 + 1, 63, 6, 11, M.GLASS); for (let r = 0; r < 3; r++) for (let c = 0; c < 3; c++) put(x0 + 1 + c * 2, 64 + r * 3, [M.LANTERN, M.SOFA_LT, M.CLOUD, M.BOOK_DK][(r + c * 2) % 4]); rect(x0 + 1, 76, 6, 2, M.ROOF); rect(x0 + 2, 79, 4, 2, M.WOOD_DK); };
710
+ vend(60); vend(69);
711
+ // ---- C: modern apartment + konbini ----
712
+ const CX0 = 78; const CX1 = 124; const CT = 20;
713
+ rect(CX0, CT, CX1 - CX0, GY - CT, M.CONCRETE); rect(CX1 - 4, CT, 4, GY - CT, M.CONCRETE_SH);
714
+ rect(CX0 - 1, CT - 2, CX1 - CX0 + 2, 3, M.CONCRETE_SH); rect(CX0 - 1, CT - 2, CX1 - CX0 + 2, 1, M.PLASTER);
715
+ for (const fy of [28, 44, 60]) { rect(CX0, fy, CX1 - CX0, 1, M.CONCRETE_SH); rect(CX0, fy + 1, CX1 - CX0, 1, M.PLASTER); }
716
+ for (const fy of [31, 47]) for (const wx of [82, 97, 112]) {
717
+ rect(wx - 1, fy, 12, 9, M.CONCRETE_SH); rect(wx, fy + 1, 10, 7, M.GLASS_SKY); rect(wx, fy + 1, 10, 1, M.GLASS_SKY_HI); rect(wx + 4, fy + 1, 1, 7, M.CONCRETE_SH); addWin(wx, fy + 1, 10, 7, { mull: 1 });
718
+ rect(wx - 2, fy + 9, 14, 1, M.WOOD_DK); for (let x = wx - 2; x < wx + 12; x += 2) put(x, fy + 8, M.WOOD_DK);
719
+ if (hash2(wx, fy) < 0.55) { rect(wx + 1, fy + 5, 1, 4, M.CLOUD); rect(wx + 3, fy + 4, 1, 5, M.LANTERN); rect(wx + 5, fy + 5, 1, 4, M.SOFA_LT); put(wx, fy + 4, M.WOOD_DK); rect(wx, fy + 4, 7, 1, M.WOOD_DK); }
720
+ }
721
+ rect(CX0 + 3, 52, 6, 3, M.CONCRETE_SH); rect(CX0 + 3, 52, 6, 1, M.PLASTER);
722
+ // roof-top water tank + antenna
723
+ rect(104, 11, 10, 9, M.CONCRETE_SH); rect(103, 10, 12, 2, M.CONCRETE); rect(106, 18, 1, 2, M.WOOD_DK); rect(111, 18, 1, 2, M.WOOD_DK); line(88, CT - 3, 88, 8, M.WOOD_DK); rect(85, 11, 7, 1, M.WOOD_DK); rect(86, 14, 5, 1, M.WOOD_DK);
724
+ // konbini storefront
725
+ rect(CX0 + 2, 62, CX1 - CX0 - 4, 20, M.CONCRETE_SH); rect(CX0 + 4, 66, 34, 15, M.GLASS_SKY); rect(CX0 + 4, 66, 34, 1, M.GLASS_SKY_HI);
726
+ for (let x = CX0 + 6; x < CX0 + 36; x += 5) { rect(x, 71, 4, 1, M.WOOD_LT); rect(x, 75, 4, 1, M.WOOD_LT); put(x + 1, 69, M.LANTERN); put(x + 2, 73, M.SOFA_LT); put(x, 77, M.CLOUD); }
727
+ rect(CX0 + 22, 66, 1, 15, M.CONCRETE_SH); rect(CX0 + 4, 80, 34, 1, M.CONCRETE_SH);
728
+ rect(CX0 + 2, 61, CX1 - CX0 - 4, 5, M.LANTERN); rect(CX0 + 2, 61, CX1 - CX0 - 4, 1, M.WOOD_DK); for (let x = CX0 + 5; x < CX1 - 7; x += 4) { rect(x, 62, 2, 3, M.NOREN); put(x + 2, 63, M.CLOUD); }
729
+ addWin(CX0 + 4, 67, 34, 14, { always: true, mull: 1 });
730
+ rect(CX0 + 42, 66, 2, 14, M.WOOD_DK); rect(CX0 + 40, 74, 6, 6, M.LANTERN); // standing shop sign
731
+ // ---- D: ramen-ya (right) ----
732
+ const DX0 = 126; const DX1 = xb;
733
+ rect(DX0, 30, 56, 14, M.PLASTER); rect(DX0 + 52, 30, 4, 14, M.PLASTER_SH);
734
+ rect(DX0 + 6, 33, 14, 9, M.WOOD_DK); rect(DX0 + 7, 34, 12, 7, M.GLASS_SKY); rect(DX0 + 13, 34, 1, 7, M.WOOD_DK); addWin(DX0 + 7, 34, 12, 7, { mull: 1 });
735
+ rect(DX0 + 36, 33, 14, 9, M.WOOD_DK); rect(DX0 + 37, 34, 12, 7, M.GLASS_SKY); rect(DX0 + 43, 34, 1, 7, M.WOOD_DK); addWin(DX0 + 37, 34, 12, 7, { mull: 1 });
736
+ for (let y = 22; y < 31; y++) { const r0 = y - 22; for (let x = DX0 - 3 + r0 * 1; x < DX0 + 59 - r0; x++) { const even = r0 % 2 === 0; let m = even ? M.ROOF_LT : M.ROOF; if (even && (x + ((r0 >> 1) & 1) * 2) % 4 === 0) m = M.ROOF; put(x, y, m); } }
737
+ rect(DX0 + 6, 21, 44, 1, M.ROOF_HI);
738
+ rect(DX0 - 4, 44, 66, 8, M.ROOF); for (let x = DX0 - 4; x < DX0 + 62; x += 4) { put(x, 45, M.ROOF_LT); put(x + 2, 47, M.ROOF_LT); put(x, 49, M.ROOF_LT); } rect(DX0 - 4, 52, 66, 1, M.WOOD_DK);
739
+ rect(DX0, 53, 56, GY - 53, M.WOOD); rect(DX0 + 52, 53, 4, GY - 53, M.WOOD_DK); for (let x = DX0; x < DX0 + 56; x += 4) rect(x, 53, 1, GY - 53, M.WOOD_LT);
740
+ rect(DX0 + 22, 56, 20, 26, M.WOOD_DK); rect(DX0 + 23, 57, 18, 25, M.GLASS_SKY); rect(DX0 + 32, 57, 1, 25, M.WOOD_DK); rect(DX0 + 23, 66, 18, 1, M.WOOD_DK); addWin(DX0 + 23, 57, 18, 9, { mull: 1 });
741
+ rect(DX0 + 4, 58, 14, 14, M.WOOD_DK); rect(DX0 + 5, 59, 12, 12, M.GLASS_SKY); rect(DX0 + 5, 59, 12, 1, M.GLASS_SKY_HI); addWin(DX0 + 5, 59, 12, 12, { mull: 1 }); rect(DX0 + 4, 72, 14, 2, M.WOOD_DK);
742
+ rect(DX0 + 20, 53, 24, 3, M.NOREN); for (let x = DX0 + 22; x < DX0 + 43; x += 6) { rect(x, 56, 5, 9, M.NOREN); rect(x + 2, 58, 1, 5, M.CLOUD); rect(x + 1, 60, 3, 1, M.CLOUD); }
743
+ rect(DX0 + 45, 62, 6, 12, M.WOOD_DK); rect(DX0 + 46, 63, 4, 10, M.LANTERN); put(DX0 + 47, 65, M.NOREN); put(DX0 + 48, 67, M.NOREN); put(DX0 + 47, 69, M.NOREN);
744
+ rect(DX0 - 2, 56, 1, 4, M.WOOD_DK); rect(DX0 - 5, 60, 7, 9, M.LANTERN); rect(DX0 - 5, 60, 7, 1, M.WOOD_DK); rect(DX0 - 5, 68, 7, 1, M.WOOD_DK); rect(DX0 - 3, 62, 3, 4, M.LANTERN_HI); put(DX0 - 2, 63, M.NOREN);
745
+ rect(DX0 + 40, 20, 3, 3, M.CONCRETE_SH); rect(DX0 + 39, 19, 5, 1, M.CONCRETE);
746
+ // ---- ground: sidewalk, curb, road ----
747
+ for (let y = GY; y < yb; y++) for (let x = xa; x < xb; x++) {
748
+ let m;
749
+ if (y < GY + 7) { m = ((x + (y & 1) * 3) % 6 === 0 && y !== GY) ? M.STONE : (y === GY ? M.STONE : M.STONE_LT); if (y > GY + 1 && dth(x, y, 0.1)) m = M.STONE; }
750
+ else if (y < GY + 9) m = y === GY + 7 ? M.STONE_LT : M.STONE;
751
+ else { const n = noiseA(x * 0.05 + 2, y * 0.3) + noiseB(x * 0.35, y * 0.5) * 0.3; m = M.ASPHALT; if (n > 0.95 && dth(x, y, 0.6)) m = M.ASPHALT_LT; else if (n < 0.35 && dth(x, y, 0.4)) m = M.ASPHALT_DK; }
752
+ put(x, y, m);
753
+ }
754
+ for (let x = xa; x < xb; x += 24) rect(x, 101, 12, 1, M.LINE); for (let x = xa + 4; x < xb; x += 24) rect(x, 102, 8, 1, M.LINE);
755
+ disc(146, 103, 4, M.ASPHALT_DK); disc(146, 103, 3, M.ASPHALT_LT); for (let x = 143; x < 150; x += 2) put(x, 103, M.ASPHALT_DK); rect(144, 102, 5, 1, M.ASPHALT_DK);
756
+ for (let x = 156; x < 188; x += 7) { rect(x, 92, 4, 1, M.LINE); rect(x, 94, 4, 1, M.LINE); }
757
+ for (let i = 0; i < 6; i++) { const x = xa + ((hash2(i, 41) * bw) | 0); const y = 106 + ((hash2(i, 42) * 8) | 0); rect(x, y, 8, 2, M.ASPHALT_LT); rect(x + 1, y + 2, 6, 1, M.ASPHALT_DK); }
758
+ rect(30, 107, 14, 1, M.LINE); rect(30, 109, 14, 1, M.LINE); rect(76, 108, 14, 1, M.LINE); rect(76, 110, 14, 1, M.LINE);
759
+ // parked kei van
760
+ { const x0 = 90; const y0 = 91;
761
+ rect(x0 + 1, y0 + 13, 31, 1, M.ASPHALT_DK);
762
+ rect(x0 + 2, y0 + 3, 29, 8, M.WALL); rect(x0 + 27, y0 + 3, 4, 8, M.WALL_SH); rect(x0 + 5, y0, 22, 4, M.WALL); rect(x0 + 5, y0, 22, 1, M.PLASTER);
763
+ rect(x0 + 6, y0 + 1, 8, 3, M.GLASS_SKY); put(x0 + 6, y0 + 1, M.GLASS_SKY_HI); put(x0 + 7, y0 + 1, M.GLASS_SKY_HI); rect(x0 + 16, y0 + 1, 9, 3, M.GLASS_SKY); rect(x0 + 15, y0 + 1, 1, 3, M.WALL_SH);
764
+ rect(x0 + 2, y0 + 8, 29, 1, M.BAND); rect(x0 + 2, y0 + 10, 29, 1, M.WALL_SH); rect(x0, y0 + 9, 3, 2, M.STONE); put(x0 + 2, y0 + 5, M.LANTERN); put(x0 + 2, y0 + 6, M.LANTERN); put(x0 + 30, y0 + 5, M.BRICK_LT);
765
+ for (const wx of [x0 + 8, x0 + 24]) { disc(wx, y0 + 11, 3, M.ROOF); disc(wx, y0 + 11, 1, M.STONE_LT); }
766
+ rect(x0 + 18, y0 + 4, 1, 5, M.WALL_SH); put(x0 + 17, y0 + 6, M.STONE_LT); }
767
+ // ---- utility poles + street lamp ----
768
+ for (const px0 of [P1, P2]) {
769
+ rect(px0, 10, 2, GY + 6 - 10, M.WOOD_DK); rect(px0, 10, 1, GY + 6 - 10, M.WOOD); rect(px0 - 7, 14, 16, 2, M.WOOD_DK); rect(px0 - 5, 21, 12, 2, M.WOOD_DK);
770
+ for (const dx of [-6, -2, 3, 7]) { put(px0 + dx, 13, M.STONE_LT); } for (const dx of [-4, 5]) put(px0 + dx, 20, M.STONE_LT);
771
+ rect(px0 + 2, 28, 5, 8, M.CONCRETE_SH); rect(px0 + 2, 28, 5, 1, M.CONCRETE); rect(px0 + 2, 36, 5, 1, M.WOOD_DK);
772
+ for (let y = 44; y < GY; y += 7) rect(px0 - 1, y, 4, 1, M.WOOD_LT);
773
+ }
774
+ rect(P2 - 12, 31, 12, 1, M.WOOD_DK); rect(P2 - 15, 32, 5, 2, M.CONCRETE_SH); rect(P2 - 14, 34, 3, 1, M.LANTERN);
775
+ const wire = (xs, ys, xe, ye, sag) => { for (let x = xs; x <= xe; x++) { const u = (x - xs) / (xe - xs); put(x, Math.round(mix(ys, ye, u) + sag * 4 * u * (1 - u)), M.NOREN); } };
776
+ for (const [dx0, y0] of [[-6, 13], [-2, 13], [3, 13], [7, 13], [-4, 20], [5, 20]]) { wire(xa, y0 + 1, P1 + dx0, y0, 3); wire(P1 + dx0, y0, P2 + dx0, y0, 7); wire(P2 + dx0, y0, xb, y0 + 2, 3); }
777
+ // ---- potted plants, signboard, bicycle ----
778
+ rect(100, 78, 6, 4, M.BRICK); rect(99, 77, 8, 1, M.BRICK_LT); put(101, 75, M.LEAF2); put(103, 74, M.LEAF3); put(104, 76, M.LEAF2); put(102, 76, M.LEAF1);
779
+ for (const bx of [53, 82]) { /* bike wheels */ }
780
+ const bike = (bx) => { for (const wx of [bx, bx + 11]) for (let a = 0; a < 16; a++) put(Math.round(wx + 3.5 * Math.cos(a * 0.3927)), Math.round(GY + 4 + 3.5 * Math.sin(a * 0.3927)), M.ROOF); line(bx, GY + 4, bx + 5, GY - 1, M.WOOD_DK); line(bx + 5, GY - 1, bx + 11, GY + 4, M.WOOD_DK); line(bx + 5, GY - 1, bx + 4, GY + 4, M.WOOD_DK); line(bx + 4, GY + 4, bx, GY + 4, M.WOOD_DK); rect(bx + 4, GY - 3, 3, 1, M.WOOD_DK); rect(bx + 10, GY - 3, 3, 1, M.WOOD_DK); line(bx + 11, GY + 4, bx + 10, GY - 3, M.WOOD_DK); rect(bx + 11, GY - 1, 4, 3, M.ROOF_LT); };
781
+ bike(4);
782
+ };
783
+ const catSit = makeSprite(['C.C...', 'CCCC..', 'CCCCC.', '.CCCCC', '.CCCCC', '.CC.CC']);
784
+ const catSleep = makeSprite(['......', '......', '.CCCC.', 'CCCCCC', 'CCCCCC']);
785
+ const walkerP = { x: 24, face: 1, step: 0, wait: 2.5, goal: 150 };
786
+ const liveStreet = (h, dt) => {
787
+ const { GY, P1, P2 } = STR; const t = anim; const lamp = lampAmt(h); const xa = -ox; const xb = bw - ox;
788
+ // windows light up one by one; shop windows (always) follow the lamp curve
789
+ drawWins(h, winFrac(h), 0.9);
790
+ // ramen steam
791
+ if (!calm) { fx.t += dt * (lamp > 0.3 ? 3 : 1.2); while (fx.t >= 1) { fx.t -= 1; fx.smoke.push({ x: 167 + Math.random() * 2, y: 18, t: 0, seed: Math.random() * 10 }); } }
792
+ for (let i = fx.smoke.length - 1; i >= 0; i--) { const s = fx.smoke[i]; s.t += dt; s.y -= dt * (6 - s.t); s.x += dt * (1.6 + Math.sin(s.t * 1.9 + s.seed) * 2.2); if (s.t > 4.2 || s.y < -4) { fx.smoke.splice(i, 1); continue; } const size = 1 + Math.floor(s.t * 0.8); for (let dy = 0; dy < size; dy++) for (let dx = 0; dx < size; dx++) { const x = Math.round(s.x) + dx; const y = Math.round(s.y) + dy; if (bayerAt(x, y) < 15 - s.t * 3) put(x, y, s.t > 2.3 ? M.SMOKE_DK : M.SMOKE); } }
793
+ // street lamp, lanterns and light pools
794
+ const lampOn = lamp > 0.05;
795
+ if (lampOn) {
796
+ rect(P2 - 14, 34, 3, 1, M.LANTERN_HI); emit(P2 - 13, 34, 190 * lamp, 2); pool(P2 - 13, 92, 20, 8, lamp * 0.8);
797
+ pool(100, 90, 34, 9, lamp * 0.95); pool(100, 80, 26, 12, lamp * 0.5); pool(30, 84, 24, 9, lamp * 0.7); pool(160, 88, 26, 9, lamp * 0.8); pool(70, 86, 10, 8, lamp * 0.6);
798
+ for (const [lx, ly, lw, lh] of [[8, 70, 7, 8], [121, 60, 7, 9], [171, 62, 6, 12]]) for (let j = 1; j < lh - 1; j++) for (let i = 1; i < lw - 1; i += 2) emit(lx + i, ly + j, 130 * lamp, 2);
799
+ homeLight(100, 74, 26000 * lamp);
800
+ }
801
+ // vending machines glow
802
+ if (lampOn) for (const vx of [60, 69]) { rect(vx + 1, 63, 6, 11, M.LIT); for (let r = 0; r < 3; r++) for (let c = 0; c < 3; c++) put(vx + 1 + c * 2, 64 + r * 3, [M.LANTERN_HI, M.LIT_HI, M.CLOUD, M.LIT_DK][(r + c * 2) % 4]); for (let j = 0; j < 11; j += 2) emit(vx + 3, 63 + j, 90 * lamp, 2); }
803
+ // cat on the curb (sleeps at night)
804
+ const asleep = h >= 22.5 || h < 6.5; const cx0 = 112; const cy0 = GY - 1;
805
+ if (asleep) blit(catSleep, cx0, cy0 - 4); else { blit(catSit, cx0, cy0 - 5); const tail = calm ? 0 : Math.round(Math.sin(t * 2.4)); put(cx0 + 6, cy0 - 1 + tail, M.CAT); put(cx0 + 7, cy0 - 2 + tail, M.CAT_DK); }
806
+ // passer-by
807
+ const W = walkerP; const away = h >= 23.4 || h < 6.4;
808
+ if (calm) { W.x = 40; }
809
+ else if (!away) {
810
+ if (W.wait > 0) { W.wait -= dt; W.step = 0; }
811
+ else { const d = W.goal - W.x; if (Math.abs(d) < 0.8) { W.wait = 1.8 + Math.random() * 2.5; W.goal = [18, 46, 96, 122, 150, 184][(Math.random() * 6) | 0]; } else { const mv = Math.min(Math.abs(d), 12 * dt); W.x += Math.sign(d) * mv; W.face = Math.sign(d); W.step += mv * 0.28; } }
812
+ }
813
+ if (!away || calm) { const moving = !calm && W.wait <= 0; blit(moving ? SPR.walk[Math.floor(W.step) % 4] : (!calm && Math.sin(t * 0.8) > 0.93 ? SPR.blink : SPR.stand), Math.round(W.x) - 3, GY + 4 - 12, W.face < 0); }
814
+ };
815
+
816
+ // ===== Scene: apartment (city window, sofa, cat on the bench, lamps) =====
817
+ const APT = { FL: 80, WX0: 17, WX1: 73, WY0: 12, WY1: 61 };
818
+ const roomLamp = (h) => (h >= 12 ? smoothstep(17.6, 19.2, h) * (1 - smoothstep(23.2, 23.5, h)) : smoothstep(5.2, 5.6, h) * (1 - smoothstep(6.8, 7.4, h)));
819
+ const catBench = makeSprite(['..C.C...', '.CCCCC..', 'CCCCCCC.', 'CCCCCCCC', 'QQQQQQQQ']);
820
+ const catBenchB = makeSprite(['..C.C...', '.CCCCC..', 'CCCCCCC.', 'CCCCCCCC', 'CQQQQQQC']);
821
+ const paintApartment = () => {
822
+ wins.length = 0; const { FL, WX0, WX1, WY0, WY1 } = APT; const xa = -ox; const xb = bw - ox; const yb = bh - oy;
823
+ // ---- walls: soft vertical stripes, shadow under the ceiling, wainscot ----
824
+ for (let y = -oy; y < FL; y++) for (let x = xa; x < xb; x++) {
825
+ let m = M.WALL; if ((x + 3) % 12 === 0) m = M.WALL_SH; if (y < 6 && dth(x, y, (6 - y) * 0.16)) m = M.WALL_SH; if (y > 66 && y < FL - 2) m = M.PLASTER; if (y === 66) m = M.WALL_SH; if (y > 66 && (x + 3) % 12 === 0) m = M.PLASTER_SH; put(x, y, m);
826
+ }
827
+ for (let x = xa; x < xb; x++) { put(x, FL - 2, M.WOOD_LT); put(x, FL - 1, M.WOOD_DK); }
828
+ // ---- floor planks (pale oak: tan by day, amber at golden hour) ----
829
+ for (let y = FL; y < yb; y++) for (let x = xa; x < xb; x++) {
830
+ const row = Math.floor((y - FL) / 5); const seam = (y - FL) % 5 === 0; const vs = (x + Math.floor((((Math.sin(row * 12.9898) * 43758.5453) % 1) + 1) % 1 * 38)) % 38 === 0;
831
+ let m = M.PLANK; if (seam || vs) m = M.PATH_DK; else if (((x * 3 + row * 11) % 19) < 2) m = M.SAND_LT; else if (y < FL + 4 && dth(x, y, 0.5)) m = M.PATH_DK; put(x, y, m);
832
+ }
833
+ // ---- window: sky shows through the glass; skyline layers on top ----
834
+ rect(WX0 - 3, WY0 - 3, WX1 - WX0 + 6, WY1 - WY0 + 6, M.WOOD_DK); rect(WX0, WY0, WX1 - WX0, WY1 - WY0, 0);
835
+ for (let x = WX0; x < WX1; x += 4 + ((hash2(x, 21) * 4) | 0)) { const w = 4 + ((hash2(x, 22) * 4) | 0); const hh = 7 + ((hash2(x, 23) * 12) | 0); rect(x, WY1 - hh, Math.min(w, WX1 - x), hh, M.HILL_FAR); }
836
+ for (let x = WX0 + 1; x < WX1; ) { const w = 7 + ((hash2(x, 24) * 5) | 0); const hh = 12 + ((hash2(x, 25) * 17) | 0); const ww = Math.min(w, WX1 - x); rect(x, WY1 - hh, ww, hh, M.CONCRETE_SH); rect(x, WY1 - hh, ww, 1, M.CONCRETE); for (let wy = WY1 - hh + 3; wy < WY1 - 2; wy += 4) for (let wx = x + 2; wx < x + ww - 1; wx += 3) { put(wx, wy, M.GLASS); if (hash2(wx * 3, wy * 5) < 0.75) addWin(wx, wy, 1, 1, { tiny: true }); } x += ww + 1; }
837
+ for (let y = 20; y < WY1; y++) { put(63, y, M.HILL_NEAR); if (y % 6 === 0 && y > 26) { rect(63 - Math.floor((y - 20) / 6), y, 1 + 2 * Math.floor((y - 20) / 6) + 1, 1, M.HILL_NEAR); } }
838
+ rect(WX0 + 17, WY0, 2, WY1 - WY0, M.WOOD_DK); rect(WX0 + 37, WY0, 2, WY1 - WY0, M.WOOD_DK); rect(WX0, WY0 + 20, WX1 - WX0, 2, M.WOOD_DK); rect(WX0, WY0, WX1 - WX0, 1, M.WOOD_LT);
839
+ rect(WX0 - 5, WY1 + 2, WX1 - WX0 + 10, 3, M.WOOD_LT); rect(WX0 - 5, WY1 + 5, WX1 - WX0 + 10, 1, M.WOOD_DK);
840
+ // curtains with folds and tie-backs
841
+ for (const cx of [5, 76]) { for (let y = 8; y < 68; y++) for (let x = cx; x < cx + 8; x++) put(x, y, (x - cx) % 3 === 1 ? M.SHIRT_DK : M.SHIRT); rect(cx, 44, 8, 2, M.STONE_LT); rect(cx - 1, 7, 10, 2, M.WOOD_DK); for (let x = cx; x < cx + 8; x += 2) put(x, 67, M.SHIRT_DK); }
842
+ // window bench + cushions
843
+ rect(16, 70, 62, 3, M.WOOD_LT); rect(16, 73, 62, 8, M.WOOD); rect(16, 73, 62, 1, M.WOOD_DK); rect(16, 80, 62, 1, M.WOOD_DK); for (const dx of [22, 44, 66]) { rect(dx, 75, 8, 4, M.WOOD_DK); rect(dx + 3, 76, 2, 1, M.WOOD_LT); } rect(18, 81, 3, 4, M.WOOD_DK); rect(73, 81, 3, 4, M.WOOD_DK);
844
+ rect(20, 65, 12, 5, M.SOFA_LT); rect(20, 65, 12, 1, M.SOFA); rect(21, 66, 10, 1, M.SOFA_LT); rect(22, 69, 8, 1, M.SOFA_DK);
845
+ rect(62, 64, 12, 6, M.SOFA); rect(62, 64, 12, 1, M.SOFA_LT); rect(62, 69, 12, 1, M.SOFA_DK);
846
+ paintBush(41, 62, 4, 3, 1202); rect(37, 66, 9, 4, M.BRICK); rect(36, 65, 11, 1, M.BRICK_LT);
847
+ // ---- plant between window and sofa ----
848
+ paintBush(91, 64, 7, 9, 1201); rect(86, 76, 11, 9, M.BRICK); rect(85, 75, 13, 2, M.BRICK_LT); rect(86, 84, 11, 1, M.WOOD_DK); line(91, 76, 91, 68, M.STEM);
849
+ // ---- pendant, frames, clock, shelf ----
850
+ rect(130, -oy, 1, 16 + oy, M.WOOD_DK); for (let y = 16; y < 23; y++) { const hw = 2 + Math.round((y - 16) * 0.9); for (let x = 130 - hw; x <= 130 + hw; x++) put(x, y, y === 16 ? M.WOOD_DK : M.LANTERN); } rect(128, 22, 5, 1, M.WOOD_DK);
851
+ rect(106, 22, 17, 15, M.WOOD_DK); rect(108, 24, 13, 11, M.CLOUD_SH); for (let y = 0; y < 6; y++) for (let x = -y; x <= y; x++) put(114 + x, 28 + y, y < 2 ? M.SNOW : M.HILL_FAR); rect(108, 31, 13, 4, M.HILL_NEAR); disc(118, 26, 1, M.LANTERN); put(114, 28, M.SNOW);
852
+ rect(137, 28, 11, 9, M.WOOD_DK); rect(139, 30, 7, 5, M.CLOUD); rect(140, 33, 5, 2, M.LEAF1); put(142, 31, M.LEAF2);
853
+ disc(158, 28, 6, M.WOOD_DK); disc(158, 28, 5, M.WALL); put(158, 24, M.WOOD_DK); put(158, 32, M.WOOD_DK); put(154, 28, M.WOOD_DK); put(162, 28, M.WOOD_DK); line(158, 28, 158, 25, M.WOOD_DK); line(158, 28, 161, 29, M.WOOD_DK);
854
+ rect(104, 44, 58, 2, M.WOOD_DK); rect(104, 43, 58, 1, M.WOOD_LT);
855
+ for (let x = 108; x < 124; x += 3) { const hh = 6 + ((hash2(x, 71) * 4) | 0); rect(x, 43 - hh, 2, hh, [M.BOOK, M.BOOK_DK, M.LEAF1, M.WOOD_LT, M.ROOF_LT][(x / 3) % 5 | 0]); }
856
+ rect(138, 36, 7, 7, M.BRICK); rect(137, 35, 9, 1, M.BRICK_LT); paintBush(141, 33, 4, 3, 1203);
857
+ rect(150, 38, 8, 5, M.BOOK_DK); rect(151, 39, 6, 1, M.BOOK);
858
+ // ---- sofa ----
859
+ rect(103, 54, 68, 16, M.SOFA); rect(103, 54, 68, 1, M.SOFA_LT); rect(103, 55, 68, 1, M.SOFA_LT); for (let x = 103; x < 171; x += 11) rect(x, 56, 1, 13, M.SOFA_DK);
860
+ rect(103, 69, 68, 12, M.SOFA_LT); rect(103, 69, 68, 1, M.SOFA); rect(103, 79, 68, 2, M.SOFA_DK); for (let x = 103; x < 171; x += 22) rect(x + 22, 70, 1, 9, M.SOFA);
861
+ rect(97, 58, 9, 24, M.SOFA); rect(97, 58, 9, 2, M.SOFA_LT); rect(98, 58, 1, 22, M.SOFA_LT); rect(97, 80, 9, 2, M.SOFA_DK); rect(168, 58, 9, 24, M.SOFA); rect(168, 58, 9, 2, M.SOFA_LT); rect(168, 80, 9, 2, M.SOFA_DK); rect(175, 60, 2, 22, M.SOFA_DK);
862
+ rect(100, 82, 2, 3, M.WOOD_DK); rect(172, 82, 2, 3, M.WOOD_DK);
863
+ rect(108, 58, 12, 11, M.SHIRT); rect(108, 58, 12, 1, M.SHIRT); rect(108, 68, 12, 1, M.SHIRT_DK); put(109, 59, M.LANTERN_HI); put(114, 63, M.SHIRT_DK); put(113, 62, M.SHIRT_DK);
864
+ rect(151, 58, 12, 11, M.CLOUD_SH); rect(151, 58, 12, 1, M.CLOUD); rect(151, 68, 12, 1, M.U2 !== undefined ? M.STAR_DIM : M.CLOUD_SH); for (let y = 60; y < 68; y += 3) rect(152, y, 10, 1, M.STAR_DIM);
865
+ // ---- rug and coffee table ----
866
+ for (let y = 87; y < 109; y++) { const t = (y - 87) / 21; const l = Math.round(98 - t * 6); const r = Math.round(180 + t * 6); for (let x = l; x <= r; x++) { const edge = x < l + 2 || x > r - 2 || y < 89 || y > 106; put(x, y, edge ? M.RUG_LT : (((x + y) % 8 === 0) || ((x - y + 400) % 8 === 0)) ? M.RUG_LT : M.RUG); } }
867
+ for (let x = 102; x < 176; x += 4) { put(x, 87, M.RUG); put(x, 108, M.RUG); }
868
+ rect(110, 93, 44, 4, M.WOOD_LT); rect(110, 97, 44, 1, M.WOOD_DK); rect(113, 98, 3, 8, M.WOOD_DK); rect(148, 98, 3, 8, M.WOOD_DK); rect(112, 108, 40, 1, M.RUG);
869
+ rect(137, 90, 10, 2, M.BOOK_DK); rect(138, 88, 8, 2, M.BOOK); rect(139, 87, 6, 1, M.BOOK_DK); put(121, 91, M.CLOUD); rect(120, 91, 4, 2, M.CLOUD); put(124, 92, M.CLOUD); rect(120, 92, 4, 1, M.CLOUD_SH);
870
+ // ---- floor lamp ----
871
+ rect(186, 46, 2, 40, M.WOOD_DK); rect(182, 84, 10, 2, M.WOOD_DK); for (let y = 36; y < 46; y++) { const hw = 3 + Math.round((y - 36) * 0.5); for (let x = 187 - hw; x <= 187 + hw; x++) put(x, y, M.LANTERN); } rect(183, 36, 8, 1, M.WOOD_DK); rect(182, 45, 10, 1, M.WOOD_DK);
872
+ // ---- baseboard heater + wall socket + small details ----
873
+ rect(180, 66, 2, 2, M.WOOD_DK);
874
+ };
875
+ const aptP = { blink: 2, stretch: 0, tc: 0, mug: [], ac: 3 };
876
+ const liveApartment = (h, dt) => {
877
+ const { FL, WX0, WX1, WY0, WY1 } = APT; const t = anim; const room = roomLamp(h); const night = nightF(h); const xa = -ox; const xb = bw - ox;
878
+ // window: lit city windows, tower beacon, a plane
879
+ drawWins(h, clamp(winFrac(h) * 1.25 + (night > 0.5 ? 0.2 : 0)));
880
+ if (night > 0.35) { const blink = calm ? true : Math.sin(t * 2.2) > 0; if (blink) { put(63, 19, M.LANTERN_HI); emit(63, 19, 150, 2); } else put(63, 19, M.LANTERN); const px0 = Math.round(WX0 + ((t * 4) % 120) - 20); if (!calm && px0 > WX0 && px0 < WX1 - 1 && Math.floor(t * 3) % 2 === 0) { put(px0, WY0 + 8, M.LIT_HI); } }
881
+ // sunbeam from the window across the floor
882
+ const sp = sunPos(h);
883
+ if (sp && sp.y < 62 && h > 6.8 && h < 17.4) {
884
+ const skew = (sp.x - 96) * 0.55; const k0 = clamp(1 - sp.y / 62, 0.35, 1);
885
+ for (let y = FL + 2; y < 112; y++) { const u = (y - FL) / 30; for (let x = Math.round(WX0 + 4 + skew * u); x < Math.round(WX1 - 4 + skew * u); x++) { if (x < 12) continue; const m = get(x, y); if ((m === M.PLANK || m === M.SAND_LT || m === M.PATH_DK) && dth(x, y, 0.8 * k0 * (1 - u * 0.35))) put(x, y, M.WALL); } }
886
+ }
887
+ // lamps
888
+ const floorLamp = room; const pend = room * (h >= 12 || h < 7 ? 1 : 0);
889
+ if (floorLamp > 0.05) {
890
+ for (let y = 37; y < 45; y++) { const hw = 3 + Math.round((y - 36) * 0.5); for (let x = 187 - hw + 1; x <= 187 + hw - 1; x++) put(x, y, y < 40 ? M.LANTERN_HI : M.LANTERN); }
891
+ for (let y = 37; y < 45; y += 2) for (let x = 183; x < 192; x += 2) emit(x, y, 210 * floorLamp, 0);
892
+ pool(187, 60, 21, 20, floorLamp * 0.75); pool(182, 90, 20, 8, floorLamp * 0.6); homeLight(187, 42, 14000 * floorLamp);
893
+ }
894
+ if (pend > 0.05) { for (let y = 17; y < 23; y++) { const hw = 2 + Math.round((y - 16) * 0.9); for (let x = 130 - hw + 1; x <= 130 + hw - 1; x++) put(x, y, M.LANTERN_HI); } for (let y = 17; y < 22; y += 2) for (let x = 127; x < 134; x += 2) emit(x, y, 190 * pend, 0); pool(130, 24, 26, 17, pend * 0.65); pool(132, 92, 24, 9, pend * 0.7); homeLight(130, 20, 11000 * pend); }
895
+ // cat on the window bench (breathes; sleeps through the night)
896
+ const cb = calm ? false : Math.sin(t * 1.3) > 0.2; blit(cb ? catBenchB : catBench, 46, 65); if (!calm && Math.sin(t * 0.5) > 0.55) put(54, 66 + (Math.sin(t * 3) > 0 ? 1 : 0), M.CAT_DK);
897
+ if (night > 0.2 || (h > 9 && h < 14)) { /* zzz */ }
898
+ // mug steam
899
+ if (!calm) { aptP.tc += dt * 2.2; while (aptP.tc >= 1) { aptP.tc -= 1; aptP.mug.push({ x: 122 + Math.random() * 1.5, y: 90, t: 0, seed: Math.random() * 7 }); } }
900
+ for (let i = aptP.mug.length - 1; i >= 0; i--) { const s = aptP.mug[i]; s.t += dt; s.y -= dt * 4.5; s.x += Math.sin(s.t * 3 + s.seed) * dt * 3; if (s.t > 1.7) { aptP.mug.splice(i, 1); continue; } if (bayerAt(Math.round(s.x), Math.round(s.y)) < 15 - s.t * 7) put(Math.round(s.x), Math.round(s.y), M.SMOKE); }
901
+ // reader on the sofa (away while asleep)
902
+ const present = !(h >= 23.4 || h < 6.4);
903
+ if (present) {
904
+ aptP.blink -= calm ? 0 : dt; const c = calm ? 0 : (t % 6.2) / 6.2; let spr = SPR.bust;
905
+ if (c > 0.16 && c < 0.24) spr = SPR.bustHalf; else if (c >= 0.24 && c < 0.46) spr = SPR.bustUp; else if (c >= 0.46 && c < 0.54) spr = SPR.bustHalf; else if (!calm && Math.sin(t * 0.7) > 0.93) spr = SPR.bustBlink;
906
+ blit(spr, 131, 49); rect(122, 57, 26, 12, M.SHIRT_DK); rect(122, 57, 26, 1, M.SHIRT); for (let x = 124; x < 146; x += 5) put(x, 62, M.SHIRT); rect(131, 57 + (spr === SPR.bustUp ? 2 : 0), 8, 5, M.BOOK); rect(135, 57 + (spr === SPR.bustUp ? 2 : 0), 1, 5, M.BOOK_DK);
907
+ if (room > 0.3 && spr !== SPR.bustUp) { for (let x = 132; x < 139; x += 2) emit(x, 59, 40 * room, 2); }
908
+ }
909
+ };
910
+
911
+ const scene = ['countryside', 'japanese-street', 'apartment', 'seaside', 'mountain-cabin'].includes(dataset.scene) ? dataset.scene : 'countryside';
912
+ const LIVE = { seaside: liveSeaside, 'mountain-cabin': liveCabin, 'japanese-street': liveStreet, apartment: liveApartment };
913
+ const drawSceneLive = (h, dt) => { const fn = LIVE[scene]; if (fn) fn(h, dt); };
914
+
579
915
 
580
916
  const buildBase = () => {
581
917
  matBuf.fill(0); if (scene === 'countryside') { paintGround(); paintFence(); paintBush(22, 78, 16, 8, 61); paintBush(176, 80, 14, 7, 62); paintBush(-6, 81, 11, 6, 63); paintBush(204, 81, 10, 5, 64); paintTrunk(); paintHouse(); paintFlowers(96, 126, 85, 92, 26, 91); paintFlowers(14, 38, 86, 96, 16, 92); paintFlowers(150, 205, 90, 106, 24, 93); paintFlowers(-14, 14, 88, 104, 12, 95); paintFlowers(40, 62, 90, 104, 12, 94); buildCanopy(); } else { canopy = []; if (scene === 'japanese-street') paintJapaneseStreet(); else if (scene === 'apartment') paintApartment(); else if (scene === 'seaside') paintSeaside(); else paintMountainCabin(); } baseBuf.set(matBuf);
@@ -695,7 +1031,7 @@ function mount(root) {
695
1031
  for (const c of sources) { c.w = 0; c.x = 0; c.y = 0; }
696
1032
  lampFlick = calm ? 1 : 0.95 + 0.05 * Math.sin(anim * 13) * Math.sin(anim * 5.3);
697
1033
  if (!calm) { birds.next -= dt; if (birds.t >= 0) { birds.t += dt; if (birds.t > 9) birds.t = -1; } else if (birds.next <= 0) { birds.t = 0; birds.next = 12 + Math.random() * 14; birds.y = 10 + Math.random() * 24; birds.dir = Math.random() < 0.5 ? 1 : -1; } shoot.next -= dt; if (shoot.t >= 0) { shoot.t += dt; if (shoot.t > 0.6) shoot.t = -1; } else if (shoot.next <= 0) { shoot.t = 0; shoot.next = 9 + Math.random() * 16; shoot.x = 10 + Math.random() * 130; shoot.y = 2 + Math.random() * 20; } }
698
- drawSky(h); if (scene === 'countryside') { const plan = stepResident(dt, h, hurry); drawShadows(h); const state = drawWindow(h); drawDoor(); drawLights(state, h); drawResident(plan, h); drawCanopy(); drawParticles(h, dt); } else { drawSceneResident(dt); drawParticles(h, dt); }
1034
+ drawSky(h); if (scene === 'countryside') { const plan = stepResident(dt, h, hurry); drawShadows(h); const state = drawWindow(h); drawDoor(); drawLights(state, h); drawResident(plan, h); drawCanopy(); drawParticles(h, dt); } else { drawSceneLive(h, dt); drawParticles(h, dt); }
699
1035
  pickKeys(h); const ta = table[keyA]; const tb = table[keyB]; const sa = skyKeys[keyA]; const sb = skyKeys[keyB]; const p = keyP * 16; const n = bw * bh;
700
1036
  for (let i = 0; i < n; i++) { const m = matBuf[i]; const useB = thrA[i] < p; const idx = m === 0 ? (useB ? sb[i] : sa[i]) : (useB ? tb : ta)[varBuf[i] * NM + m]; pixels[i] = palette32[idx]; }
701
1037
  };
@@ -824,7 +1160,7 @@ function mount(root) {
824
1160
  on(canvas, 'webglcontextrestored', () => { if (ctx2d) return; clearTimeout(restoreTimer); lost = false; ready = setupGl(); if (!ready) useCanvas2d(); redrawAll(); });
825
1161
  readPalette(); measure(); if (lost) waitForRestore(); res.x = 131; res.inside = false;
826
1162
  renderWorld(clockOf(), 0, 0); if (ready) uploadWorld(); if (startNight) { commit(1); dirty = true; } wake();
827
- return () => { stopDrawing(); clearTimeout(restoreTimer); visible = false; io.disconnect(); ro.disconnect(); for (const off of cleanups) off(); if (gl && ready) { gl.deleteProgram(program); gl.deleteBuffer(quad); gl.deleteVertexArray(vao); gl.deleteTexture(texWorld); gl.deleteTexture(texNear); } };
1163
+ return () => { stopDrawing(); clearTimeout(restoreTimer); visible = false; io.disconnect(); ro.disconnect(); for (const off of cleanups) off(); if (gl && ready) { gl.deleteProgram(program); gl.deleteBuffer(quad); gl.deleteVertexArray(vao); gl.deleteTexture(texWorld); gl.deleteTexture(texNear); } };
828
1164
  }
829
1165
  const SELECTOR = '[data-ld-part="toggle-daylight"]';
830
1166
  const STYLE_ID = 'ld-toggle-daylight-style';
@@ -935,4 +1271,4 @@ if (typeof document !== 'undefined') {
935
1271
  }
936
1272
  if (typeof window !== 'undefined') window.PixelDaylightSwitch = api;
937
1273
  if (typeof module !== 'undefined' && module.exports) module.exports = api;
938
- })();
1274
+ })();