pixel-daylight-switch-gsvegetable 1.3.0 → 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.
- package/README.md +484 -174
- package/app.js +594 -258
- package/package.json +21 -2
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
451
|
-
|
|
452
|
-
for(let y=
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
for(let
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
for(let
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
rect(
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
|
|
511
|
-
//
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
for(let y=
|
|
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
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
for(let y=
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
rect(
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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 {
|
|
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
|
+
})();
|