pixel-daylight-switch-gsvegetable 1.3.1 → 2.1.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.ja.md +207 -0
- package/README.md +134 -360
- package/README.zh-CN.md +207 -0
- package/app.js +664 -267
- package/package.json +19 -4
package/app.js
CHANGED
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
*/
|
|
10
10
|
(function () {
|
|
11
11
|
"use strict";
|
|
12
|
+
const reportDiagnostic = (title, detail) => {
|
|
13
|
+
try {
|
|
14
|
+
if (typeof window !== 'undefined' && window.__PDS_DIAG__ && typeof window.__PDS_DIAG__.show === 'function') {
|
|
15
|
+
window.__PDS_DIAG__.show(title, detail);
|
|
16
|
+
}
|
|
17
|
+
} catch (_) {}
|
|
18
|
+
};
|
|
12
19
|
const CORE_W = 192;
|
|
13
20
|
const CORE_H = 112;
|
|
14
21
|
const HORIZON = 82;
|
|
@@ -96,6 +103,42 @@ const MATS = [
|
|
|
96
103
|
['MARK', I0, I0, C3, C3],
|
|
97
104
|
['ZZZ', U3, U3, U3, U3],
|
|
98
105
|
['WING', A2, A2, A1, I2],
|
|
106
|
+
['SNOW', C3, A3, U2, I3],
|
|
107
|
+
['SNOW_SH', U2, U1, U0, I2],
|
|
108
|
+
['SNOW_DK', U1, U0, I3, I1],
|
|
109
|
+
['SEA', U1, U0, I3, I1],
|
|
110
|
+
['SEA_DK', U0, I3, I2, I0],
|
|
111
|
+
['SEA_LT', U2, A1, U0, I2],
|
|
112
|
+
['SEA_HI', U3, A3, U2, I3],
|
|
113
|
+
['FOAM', C3, A3, U2, I3],
|
|
114
|
+
['ROCK', U0, I3, I1, I0],
|
|
115
|
+
['LANTERN', A1, A2, A3, A3],
|
|
116
|
+
['LANTERN_HI', A2, A3, A3, A3],
|
|
117
|
+
['BAND', A1, A1, I3, I1],
|
|
118
|
+
['PLASTER', C2, A2, U1, I2],
|
|
119
|
+
['PLASTER_SH', C1, A1, U0, I1],
|
|
120
|
+
['CONCRETE', U2, U1, I3, I1],
|
|
121
|
+
['CONCRETE_SH', U1, U0, I2, I0],
|
|
122
|
+
['ASPHALT', U0, I3, I2, I1],
|
|
123
|
+
['ASPHALT_LT', U1, U0, I3, I1],
|
|
124
|
+
['ASPHALT_DK', I3, I2, I1, I0],
|
|
125
|
+
['LINE', C3, A3, U2, I3],
|
|
126
|
+
['GLASS_SKY', U2, A2, I3, I1],
|
|
127
|
+
['GLASS_SKY_HI', U3, A3, U1, I2],
|
|
128
|
+
['NOREN', I3, I2, I1, I0],
|
|
129
|
+
['CAT', A2, A1, I3, I1],
|
|
130
|
+
['CAT_DK', A0, A0, I2, I0],
|
|
131
|
+
['AUR_HI', G3, G3, G3, G3],
|
|
132
|
+
['AUR_MID', G2, G2, G2, G2],
|
|
133
|
+
['AUR_LO', G1, G1, G1, G1],
|
|
134
|
+
['AUR_ICE', U3, U3, U3, U3],
|
|
135
|
+
['SAND_LT', C2, A2, U1, I3],
|
|
136
|
+
['SOFA', G1, G0, I2, I0],
|
|
137
|
+
['SOFA_LT', G2, G1, I3, I1],
|
|
138
|
+
['SOFA_DK', G0, G0, I1, I0],
|
|
139
|
+
['RUG', U1, U0, I3, I1],
|
|
140
|
+
['RUG_LT', U2, U1, I3, I2],
|
|
141
|
+
['SHALLOW', G3, A2, U1, I2],
|
|
99
142
|
];
|
|
100
143
|
const M = Object.fromEntries(MATS.map((row, i) => [row[0], i]));
|
|
101
144
|
const KEYS = [
|
|
@@ -148,7 +191,7 @@ function labTone(lab, L) {
|
|
|
148
191
|
}
|
|
149
192
|
return oklabToLinear(L, 0, 0).map((v) => Math.round(toGamma(clamp(v)) * 255));
|
|
150
193
|
}
|
|
151
|
-
const CHARS = { H: 'HAIR', S: 'SKIN', E: 'HAIR', T: 'SHIRT', D: 'SHIRT_DK', P: 'PANTS', B: 'BOOT', W: 'BOOK', K: 'BOOK_DK' };
|
|
194
|
+
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
195
|
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
196
|
const HEAD = ['..HHH..', '.HHHHH.', '.HHSSS.', '.HSSES.', '..SSSS.'];
|
|
154
197
|
const HEAD_BLINK = ['..HHH..', '.HHHHH.', '.HHSSS.', '.HSSSS.', '..SSSS.'];
|
|
@@ -315,268 +358,568 @@ function mount(root) {
|
|
|
315
358
|
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
359
|
};
|
|
317
360
|
|
|
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);
|
|
361
|
+
|
|
362
|
+
// ===== Scene toolkit: shared by the four non-countryside scenes =====
|
|
363
|
+
// Every painter fills the WHOLE world buffer (-ox..bw-ox, -oy..bh-oy), not just the 192x112 core,
|
|
364
|
+
// so wide windows never show raw sky at the edges. Dynamic things (lights, people, water, smoke) are
|
|
365
|
+
// redrawn each frame on top of the static base in drawSceneLive().
|
|
366
|
+
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; };
|
|
367
|
+
const dth = (x, y, t) => t * 16 > bayerAt(x, y);
|
|
368
|
+
const strip = (y0, y1, m) => { for (let y = y0; y < y1; y++) for (let x = -ox; x < bw - ox; x++) put(x, y, m); };
|
|
369
|
+
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); };
|
|
370
|
+
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); };
|
|
371
|
+
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) }; };
|
|
372
|
+
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) }; };
|
|
373
|
+
const nightF = (h) => clamp(smoothstep(18.2, 20.2, h) + (1 - smoothstep(4.6, 6.2, h)));
|
|
374
|
+
const lampAmt = (h) => (h >= 12 ? smoothstep(17.4, 19.0, h) : 1 - smoothstep(5.2, 6.8, h));
|
|
375
|
+
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))); };
|
|
376
|
+
// windows that light up one by one as evening falls
|
|
377
|
+
const wins = [];
|
|
378
|
+
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 });
|
|
379
|
+
const drawWins = (h, level, boost = 1) => {
|
|
380
|
+
for (const w of wins) {
|
|
381
|
+
const on = w.always ? lampAmt(h) > 0.4 : w.seed < level; if (!on) continue;
|
|
382
|
+
if (w.tiny) { put(w.x, w.y, M.LIT); emit(w.x, w.y, 70 * boost, 2); continue; }
|
|
383
|
+
const flick = calm ? 1 : 0.93 + 0.07 * Math.sin(anim * 2.1 + w.seed * 40);
|
|
384
|
+
for (let j = 0; j < w.h; j++) for (let i = 0; i < w.w; i++) {
|
|
385
|
+
const ring = i === 0 || j === 0 || i === w.w - 1 || j === w.h - 1; const low = j === w.h - 1 && w.h > 3;
|
|
386
|
+
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;
|
|
387
|
+
put(w.x + i, w.y + j, m);
|
|
388
|
+
if (!ring && ((i + j) & 1) === 0) emit(w.x + i, w.y + j, 62 * boost * flick, 2);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
448
391
|
};
|
|
392
|
+
// warm light pool: switches nearby non-sky pixels to the "lit" tone table
|
|
393
|
+
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; } };
|
|
394
|
+
// shell glow without bloom (the shell reads this as "the room is lit")
|
|
395
|
+
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; };
|
|
396
|
+
const isSeaMat = (m) => m === M.SEA || m === M.SEA_DK || m === M.SEA_LT || m === M.SEA_HI;
|
|
397
|
+
const fx = { smoke: [], t: 0 };
|
|
398
|
+
// ===== Scene: seaside (lighthouse + pier + village across the bay) =====
|
|
399
|
+
const SEAL = { YH: 60, SH: 98, LX: 182, T: 38, BASE: 76, DECK: 90 };
|
|
449
400
|
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
|
-
|
|
401
|
+
wins.length = 0; const { YH, SH, LX, T, BASE, DECK } = SEAL; const xa = -ox; const xb = bw - ox;
|
|
402
|
+
// sea: pale haze at the horizon, deeper water in the middle, lighter shallows near the shore
|
|
403
|
+
for (let y = YH; y <= SH + 10; y++) {
|
|
404
|
+
const t = clamp((y - YH) / (SH - YH));
|
|
405
|
+
for (let x = xa; x < xb; x++) {
|
|
406
|
+
const n = noiseA(x * 0.05 + 9, y * 0.45 + 2);
|
|
407
|
+
let m;
|
|
408
|
+
if (t < 0.16) m = dth(x, y, (0.16 - t) * 6.5) ? M.SEA_LT : M.SEA;
|
|
409
|
+
else if (t < 0.6) m = dth(x, y, (n - 0.3) * 1.7 + t * 0.35) ? M.SEA_DK : M.SEA;
|
|
410
|
+
else m = dth(x, y, (t - 0.6) * 1.9 + (n - 0.5) * 0.4) ? M.SEA_LT : M.SEA;
|
|
411
|
+
if (t > 0.78 && dth(x, y, (t - 0.78) * 3.8 + (n - 0.5) * 0.3)) m = M.SHALLOW;
|
|
412
|
+
put(x, y, m);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
for (let x = xa; x < xb; x += 2) put(x, YH, M.SEA_HI);
|
|
416
|
+
// far hills + village across the bay (left) and a low island (centre)
|
|
417
|
+
const farTop = (x) => Math.round(YH - 10 + noiseA(x * 0.05 + 2, 3.5) * 3 + Math.pow(clamp((x - 6) / 64), 1.6) * 10);
|
|
418
|
+
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); }
|
|
419
|
+
for (const cx of [4, 22, 40]) for (let i = 0; i < 3; i++) {
|
|
420
|
+
const x = cx + i * 4 + ((hash2(cx, i) * 2) | 0); const top = farTop(x) + 2; const hh = 2 + ((hash2(x, 9) * 2) | 0);
|
|
421
|
+
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 });
|
|
422
|
+
}
|
|
423
|
+
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); }
|
|
424
|
+
// rocky headland with the lighthouse (right): left-lit, dithered three-tone rock with strata
|
|
425
|
+
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);
|
|
426
|
+
for (let x = 118; x < xb; x++) {
|
|
427
|
+
const top = cliffTop(x);
|
|
428
|
+
for (let y = top; y <= SH + 8; y++) {
|
|
429
|
+
const depth = y - top; const strata = Math.sin((y + noiseA(x * 0.05, 3) * 7) * 1.25) * 0.09;
|
|
430
|
+
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);
|
|
431
|
+
let m = v > 0.6 ? M.STONE_LT : v > 0.3 ? M.STONE : M.ROCK;
|
|
432
|
+
if (depth < 2 && x >= 150) m = (x + y) % 3 === 0 ? M.GRASS_LT : M.GRASS;
|
|
433
|
+
else if (depth >= 2 && depth < 4 && x >= 150 && dth(x, y, 0.45)) m = M.GRASS_DK;
|
|
434
|
+
else if (depth < 1) m = M.STONE_LT;
|
|
435
|
+
put(x, y, m);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
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); }
|
|
439
|
+
// keeper's cottage
|
|
440
|
+
const CX0 = 153; const CB = BASE;
|
|
441
|
+
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);
|
|
442
|
+
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); }
|
|
443
|
+
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);
|
|
444
|
+
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, {});
|
|
445
|
+
// lighthouse tower
|
|
446
|
+
const half = (y) => 5.5 + ((y - T) / (BASE - T)) * 3;
|
|
447
|
+
for (let y = T; y < BASE; y++) {
|
|
448
|
+
const hw = Math.round(half(y));
|
|
449
|
+
for (let x = LX - hw; x <= LX + hw; x++) {
|
|
450
|
+
const t = (x - (LX - hw)) / (2 * hw); let m = M.WALL;
|
|
451
|
+
if (t > 0.78 || (t > 0.58 && dth(x, y, 0.55))) m = M.WALL_SH;
|
|
452
|
+
if ((y >= T + 9 && y < T + 15) || (y >= T + 25 && y < T + 31)) m = t > 0.62 ? M.WOOD : M.BAND;
|
|
453
|
+
put(x, y, m);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
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);
|
|
457
|
+
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);
|
|
458
|
+
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);
|
|
459
|
+
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); }
|
|
460
|
+
put(LX, T - 24, M.WOOD_DK); put(LX, T - 23, M.WOOD_DK); put(LX, T - 25, M.LANTERN_HI);
|
|
461
|
+
// pier (left): plank deck, piles standing in the water, a lantern post at the end
|
|
462
|
+
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); }
|
|
463
|
+
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); }
|
|
464
|
+
for (let x = xa + 9; x < 100; x += 12) line(x, DECK + 3, x + 5, DECK + 7, M.WOOD_DK);
|
|
465
|
+
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);
|
|
466
|
+
// beach: tan sand, a thin wet band at the water, a few light grains
|
|
467
|
+
for (let y = SH + 2; y < bh - oy; y++) {
|
|
468
|
+
for (let x = xa; x < xb; x++) {
|
|
469
|
+
const d = y - (SH + 2); const n = noiseA(x * 0.08 + 3, y * 0.25) + noiseB(x * 0.4, y * 0.6) * 0.3;
|
|
470
|
+
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;
|
|
471
|
+
put(x, y, m);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
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); }
|
|
475
|
+
// row boat on the sand
|
|
476
|
+
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); }
|
|
477
|
+
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);
|
|
478
|
+
paintBush(8, 112, 9, 3, 951); paintBush(196, 113, 12, 3, 952); paintBush(112, 115, 8, 2, 953);
|
|
479
|
+
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); }
|
|
480
|
+
};
|
|
481
|
+
const seaPerson = { x: 84, face: 1, step: 0, bite: 0, nextBite: 6, bob: 0 };
|
|
482
|
+
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; };
|
|
483
|
+
const liveSeaside = (h, dt) => {
|
|
484
|
+
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;
|
|
485
|
+
// wave crests
|
|
486
|
+
for (let y = YH + 2; y < SH - 1; y++) {
|
|
487
|
+
if (y & 1) continue; const tt = (y - YH) / (SH - YH); const cnt = 3 + Math.round(tt * 4);
|
|
488
|
+
for (let i = 0; i < cnt; i++) {
|
|
489
|
+
const sp = (0.7 + tt * 4.5) * (hash2(y, i + 70) > 0.5 ? 1 : -1); const span = bw + 30;
|
|
490
|
+
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);
|
|
491
|
+
if (Math.sin(t * 0.8 + hash2(y, i + 3) * 6.28) < -0.25) continue;
|
|
492
|
+
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); }
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
// sun / moon glitter on the water
|
|
496
|
+
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;
|
|
497
|
+
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); } } }
|
|
498
|
+
// distant village lights
|
|
499
|
+
drawWins(h, winFrac(h) * 1.1);
|
|
500
|
+
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);
|
|
501
|
+
// sailing boat
|
|
502
|
+
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));
|
|
503
|
+
if (bx > xa - 14 && bx < xb + 14) {
|
|
504
|
+
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);
|
|
505
|
+
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); }
|
|
506
|
+
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); }
|
|
507
|
+
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); }
|
|
508
|
+
}
|
|
509
|
+
// fisherman on the pier
|
|
510
|
+
const away = h >= 22.6 || h < 7.4; const P = seaPerson;
|
|
511
|
+
const moving = calm ? ((P.x = away ? -40 : 84), false) : walkTo(P, away ? -40 : 84, 14, dt);
|
|
512
|
+
if (P.x > xa - 8) {
|
|
513
|
+
if (moving) blit(SPR.walk[Math.floor(P.step) % 4], Math.round(P.x) - 3, DECK - 12, P.face < 0);
|
|
514
|
+
else {
|
|
515
|
+
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);
|
|
516
|
+
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;
|
|
517
|
+
blit(spr, sx, sy, true);
|
|
518
|
+
const hx = sx + 6; const hy = sy + 5; const tipx = hx + 12; const tipy = hy - 8 + (P.bite > 0.5 ? 2 : 0);
|
|
519
|
+
line(hx, hy, tipx, tipy, M.WOOD_LT);
|
|
520
|
+
const bobY = DECK + 4 + Math.round(calm ? 0 : Math.sin(t * 1.6)) + (P.bite > 0 ? 1 : 0); const bobX = tipx + 7;
|
|
521
|
+
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);
|
|
522
|
+
put(bobX, bobY, M.LANTERN_HI); put(bobX, bobY + 1, M.ROOF_LT);
|
|
523
|
+
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); }
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
// pier lantern
|
|
527
|
+
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); }
|
|
528
|
+
// lighthouse lamp + sweeping beam
|
|
529
|
+
if (lamp > 0.02) {
|
|
530
|
+
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); }
|
|
531
|
+
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);
|
|
532
|
+
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);
|
|
533
|
+
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); } } } }
|
|
534
|
+
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); } }
|
|
535
|
+
homeLight(LX, LYb, 20000 * lamp);
|
|
536
|
+
}
|
|
537
|
+
// surf at the shoreline
|
|
538
|
+
const wash = 1.6 * Math.sin(t * 0.7) + 0.8 * Math.sin(t * 1.9 + 1);
|
|
539
|
+
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); }
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
// ===== Scene: mountain cabin (snow, pines, chopping wood, aurora at night) =====
|
|
543
|
+
const pineTree = (cx, base, h, tiers, snowy) => {
|
|
544
|
+
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);
|
|
545
|
+
const span = h - 6; const tierH = Math.round((span / tiers) * 1.9);
|
|
546
|
+
for (let i = 0; i < tiers; i++) {
|
|
547
|
+
const top = base - h + Math.round((i * span) / tiers); const maxW = 2 + Math.round(((i + 1) * h * 0.34) / tiers);
|
|
548
|
+
for (let y = top; y < Math.min(top + tierH, base - 5); y++) {
|
|
549
|
+
const t = (y - top) / tierH; const hw = Math.round(0.6 + t * (maxW - 0.6));
|
|
550
|
+
for (let x = cx - hw; x <= cx + hw; x++) {
|
|
551
|
+
const rel = (x - cx) / Math.max(hw, 1);
|
|
552
|
+
if (t > 0.9 && ((x + i) & 1) && Math.abs(rel) < 0.85) continue;
|
|
553
|
+
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;
|
|
554
|
+
let m = v > 0.72 ? M.LEAF2 : v > 0.4 ? M.LEAF1 : M.LEAF0;
|
|
555
|
+
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; }
|
|
556
|
+
put(x, y, m);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
508
560
|
};
|
|
509
561
|
const paintMountainCabin = () => {
|
|
510
|
-
|
|
511
|
-
//
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
for(let y=
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
//
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
for(let y=
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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;
|
|
562
|
+
wins.length = 0; const xa = -ox; const xb = bw - ox; const yb = bh - oy;
|
|
563
|
+
// ---- far range: left-lit rock faces, snow above the snow line ----
|
|
564
|
+
const tent = (x, cx, hgt, s) => Math.max(0, hgt - Math.abs(x - cx) * s);
|
|
565
|
+
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);
|
|
566
|
+
for (let x = xa; x < xb; x++) {
|
|
567
|
+
const top = far(x); const lit = far(x + 2) - far(x - 2) < 0;
|
|
568
|
+
for (let y = top; y < 80; y++) {
|
|
569
|
+
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;
|
|
570
|
+
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);
|
|
571
|
+
put(x, y, m);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// ---- forested foothills ----
|
|
575
|
+
const hill = (x) => Math.round(64 + noiseA(x * 0.035 + 11, 4) * 10 - 2 * Math.sin(x * 0.05));
|
|
576
|
+
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)); } }
|
|
577
|
+
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); } }
|
|
578
|
+
// ---- snow field with soft dune shading ----
|
|
579
|
+
for (let y = 78; y < yb; y++) for (let x = xa; x < xb; x++) {
|
|
580
|
+
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;
|
|
581
|
+
const dune = Math.sin(x * 0.07 + y * 0.18) * 0.12;
|
|
582
|
+
put(x, y, n + dune > 0.72 ? M.SNOW_SH : (y < 81 && dth(x, y, 0.5)) ? M.SNOW_SH : M.SNOW);
|
|
572
583
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
584
|
+
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); }
|
|
585
|
+
// ---- mid pines ----
|
|
586
|
+
pineTree(52, 86, 34, 5, true); pineTree(62, 83, 24, 4, true); pineTree(138, 84, 26, 4, true); pineTree(122, 82, 18, 3, true);
|
|
587
|
+
// ---- cabin (front gable) ----
|
|
588
|
+
const CX = 90; const AP = 36; const EY = 62; const SLOPE = 0.76;
|
|
589
|
+
// external stone chimney on the right wall
|
|
590
|
+
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); }
|
|
591
|
+
rect(115, 31, 10, 3, M.STONE_LT); rect(115, 33, 10, 1, M.STONE); rect(118, 29, 4, 2, M.ROCK);
|
|
592
|
+
// gable wall (vertical planks), main log wall
|
|
593
|
+
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)); }
|
|
594
|
+
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); }
|
|
595
|
+
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); } }
|
|
596
|
+
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);
|
|
597
|
+
// attic round window
|
|
598
|
+
disc(CX, 50, 3, M.WOOD_DK); disc(CX, 50, 2, M.GLASS); put(CX - 1, 49, M.GLASS_HI);
|
|
599
|
+
// roof rakes with thick snow on top (snow overhang gets a blue underside)
|
|
600
|
+
for (let y = AP - 3; y < EY + 4; y++) {
|
|
601
|
+
const hw = (y - AP) / SLOPE; const l = Math.round(CX - hw); const r = Math.round(CX + hw);
|
|
602
|
+
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); }
|
|
603
|
+
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); }
|
|
604
|
+
}
|
|
605
|
+
for (let x = 56; x <= 124; x++) { if ((x * 5) % 7 < 4) put(x, EY + 4, M.SNOW); }
|
|
606
|
+
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);
|
|
607
|
+
// porch canopy and posts
|
|
608
|
+
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);
|
|
609
|
+
// door
|
|
610
|
+
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);
|
|
611
|
+
addWin(85, 74, 4, 4, { always: true }); addWin(90, 74, 5, 4, { always: true });
|
|
612
|
+
// side windows with shutters + flower boxes
|
|
613
|
+
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 }); };
|
|
614
|
+
winAt(68); winAt(102);
|
|
615
|
+
// lantern on the porch
|
|
616
|
+
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);
|
|
617
|
+
// steps
|
|
618
|
+
rect(81, 88, 18, 2, M.STONE_LT); rect(79, 90, 22, 2, M.STONE); rect(78, 92, 24, 1, M.ROCK);
|
|
619
|
+
// snow drifts against the cabin
|
|
620
|
+
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); }
|
|
621
|
+
// ---- woodpile, chopping block, footprints ----
|
|
622
|
+
rect(150, 86, 24, 3, M.SNOW_SH);
|
|
623
|
+
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); }
|
|
624
|
+
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);
|
|
625
|
+
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);
|
|
626
|
+
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); }
|
|
627
|
+
// ---- foreground pines ----
|
|
628
|
+
pineTree(24, 100, 66, 7, true); pineTree(179, 98, 58, 7, true); pineTree(196, 104, 40, 5, true);
|
|
629
|
+
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); }
|
|
630
|
+
};
|
|
631
|
+
const cabinP = { x: 132, inside: false, door: 0, face: 1, step: 0, cycle: 0, split: -1 };
|
|
632
|
+
const liveCabin = (h, dt) => {
|
|
633
|
+
const t = anim; const lamp = lampAmt(h); const xa = -ox; const xb = bw - ox; const P = cabinP;
|
|
634
|
+
// aurora curtains
|
|
635
|
+
const an = nightF(h);
|
|
636
|
+
if (an > 0.5) {
|
|
637
|
+
const amp = smoothstep(0.5, 0.95, an);
|
|
638
|
+
for (let x = xa; x < xb; x++) {
|
|
639
|
+
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;
|
|
640
|
+
const ray = 0.55 + 0.45 * Math.sin(x * 1.6 + noiseA(x * 0.2, t * 0.2) * 6);
|
|
641
|
+
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); }
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
// chimney smoke
|
|
645
|
+
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 }); } }
|
|
646
|
+
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); } }
|
|
647
|
+
if (calm) for (let i = 0; i < 5; i++) rect(119 + i * 2, 24 - i * 4, 1 + (i >> 1), 2, M.SMOKE);
|
|
648
|
+
// windows, lantern, light on the snow
|
|
649
|
+
drawWins(h, 0, 1.3);
|
|
650
|
+
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); }
|
|
651
|
+
// daytime shadows on the snow (cabin + pines), opposite the sun
|
|
652
|
+
const sp = sunPos(h);
|
|
653
|
+
if (sp && sp.y < 70 && h > 6.4 && h < 17.4) {
|
|
654
|
+
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); } };
|
|
655
|
+
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);
|
|
656
|
+
}
|
|
657
|
+
// the woodcutter
|
|
658
|
+
const outdoors = h >= 8.4 && h < 17.6; const wantX = outdoors ? 132 : 90;
|
|
659
|
+
if (calm) { P.inside = !outdoors; P.x = wantX; }
|
|
660
|
+
else {
|
|
661
|
+
P.door = Math.max(0, P.door - dt);
|
|
662
|
+
if (P.inside && outdoors) { P.inside = false; P.x = 90; P.door = 0.5; }
|
|
663
|
+
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; } } }
|
|
664
|
+
}
|
|
665
|
+
if (P.door > 0) { rect(85, 74, 10, 14, M.SIL); rect(85, 74, 2, 14, M.WOOD_LT); }
|
|
666
|
+
if (!P.inside) {
|
|
667
|
+
const base = 88; const x0 = Math.round(P.x) - 3; const moving = Math.abs(wantX - P.x) > 0.6;
|
|
668
|
+
if (moving) blit(SPR.walk[Math.floor(P.step) % 4], x0, base - 12, P.face < 0);
|
|
669
|
+
else if (outdoors) {
|
|
670
|
+
P.cycle = calm ? 1.2 : (P.cycle + dt) % 4.4; const c = P.cycle; const bx = 140; const by = 82;
|
|
671
|
+
let spr = SPR.stand; let axe = 0;
|
|
672
|
+
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;
|
|
673
|
+
blit(spr, x0, base - 12, false);
|
|
674
|
+
if (axe === 1) { rect(x0 + 3, base - 18, 1, 6, M.WOOD_DK); rect(x0 + 3, base - 19, 3, 2, M.STONE_LT); }
|
|
675
|
+
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); }
|
|
676
|
+
else if (axe === 3) { rect(x0 + 7, base - 9, 1, 6, M.WOOD_DK); }
|
|
677
|
+
const sp2 = c > 2.95 && c < 3.9 ? (c - 2.95) : -1;
|
|
678
|
+
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); }
|
|
679
|
+
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); } }
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
// ===== Scene: Japanese street (machiya, konbini, ramen shop, utility poles, Fuji) =====
|
|
685
|
+
const STR = { GY: 82, P1: 69, P2: 171 };
|
|
686
|
+
const paintJapaneseStreet = () => {
|
|
687
|
+
wins.length = 0; const { GY, P1, P2 } = STR; const xa = -ox; const xb = bw - ox; const yb = bh - oy;
|
|
688
|
+
// ---- far layer: low hills, Mt. Fuji, far town ----
|
|
689
|
+
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);
|
|
690
|
+
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)); } }
|
|
691
|
+
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); }
|
|
692
|
+
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 }); }
|
|
693
|
+
// ---- A: machiya (left) ----
|
|
694
|
+
const AX0 = 6; const AX1 = 58;
|
|
695
|
+
rect(AX0, 40, AX1 - AX0, GY - 40, M.PLASTER); rect(AX1 - 4, 40, 4, GY - 40, M.PLASTER_SH);
|
|
696
|
+
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);
|
|
697
|
+
// lattice windows on the second floor
|
|
698
|
+
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, {}); }
|
|
699
|
+
rect(AX0 - 1, 57, AX1 - AX0 + 2, 2, M.WOOD_DK);
|
|
700
|
+
// hip roof (kawara) with ridge and end ornaments
|
|
701
|
+
for (let y = 26; y < 41; y++) {
|
|
702
|
+
const r0 = y - 26; const l = Math.round(15 - r0 * 0.9); const r = Math.round(49 + r0 * 0.9);
|
|
703
|
+
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); }
|
|
704
|
+
}
|
|
705
|
+
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);
|
|
706
|
+
// lower eave
|
|
707
|
+
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);
|
|
708
|
+
// ground floor: wooden lattice, sliding door, noren, lantern
|
|
709
|
+
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);
|
|
710
|
+
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 });
|
|
711
|
+
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); }
|
|
712
|
+
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);
|
|
713
|
+
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); }
|
|
714
|
+
// ---- B: narrow alley with vending machines ----
|
|
715
|
+
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);
|
|
716
|
+
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); };
|
|
717
|
+
vend(60); vend(69);
|
|
718
|
+
// ---- C: modern apartment + konbini ----
|
|
719
|
+
const CX0 = 78; const CX1 = 124; const CT = 20;
|
|
720
|
+
rect(CX0, CT, CX1 - CX0, GY - CT, M.CONCRETE); rect(CX1 - 4, CT, 4, GY - CT, M.CONCRETE_SH);
|
|
721
|
+
rect(CX0 - 1, CT - 2, CX1 - CX0 + 2, 3, M.CONCRETE_SH); rect(CX0 - 1, CT - 2, CX1 - CX0 + 2, 1, M.PLASTER);
|
|
722
|
+
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); }
|
|
723
|
+
for (const fy of [31, 47]) for (const wx of [82, 97, 112]) {
|
|
724
|
+
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 });
|
|
725
|
+
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);
|
|
726
|
+
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); }
|
|
727
|
+
}
|
|
728
|
+
rect(CX0 + 3, 52, 6, 3, M.CONCRETE_SH); rect(CX0 + 3, 52, 6, 1, M.PLASTER);
|
|
729
|
+
// roof-top water tank + antenna
|
|
730
|
+
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);
|
|
731
|
+
// konbini storefront
|
|
732
|
+
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);
|
|
733
|
+
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); }
|
|
734
|
+
rect(CX0 + 22, 66, 1, 15, M.CONCRETE_SH); rect(CX0 + 4, 80, 34, 1, M.CONCRETE_SH);
|
|
735
|
+
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); }
|
|
736
|
+
addWin(CX0 + 4, 67, 34, 14, { always: true, mull: 1 });
|
|
737
|
+
rect(CX0 + 42, 66, 2, 14, M.WOOD_DK); rect(CX0 + 40, 74, 6, 6, M.LANTERN); // standing shop sign
|
|
738
|
+
// ---- D: ramen-ya (right) ----
|
|
739
|
+
const DX0 = 126; const DX1 = xb;
|
|
740
|
+
rect(DX0, 30, 56, 14, M.PLASTER); rect(DX0 + 52, 30, 4, 14, M.PLASTER_SH);
|
|
741
|
+
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 });
|
|
742
|
+
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 });
|
|
743
|
+
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); } }
|
|
744
|
+
rect(DX0 + 6, 21, 44, 1, M.ROOF_HI);
|
|
745
|
+
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);
|
|
746
|
+
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);
|
|
747
|
+
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 });
|
|
748
|
+
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);
|
|
749
|
+
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); }
|
|
750
|
+
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);
|
|
751
|
+
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);
|
|
752
|
+
rect(DX0 + 40, 20, 3, 3, M.CONCRETE_SH); rect(DX0 + 39, 19, 5, 1, M.CONCRETE);
|
|
753
|
+
// ---- ground: sidewalk, curb, road ----
|
|
754
|
+
for (let y = GY; y < yb; y++) for (let x = xa; x < xb; x++) {
|
|
755
|
+
let m;
|
|
756
|
+
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; }
|
|
757
|
+
else if (y < GY + 9) m = y === GY + 7 ? M.STONE_LT : M.STONE;
|
|
758
|
+
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; }
|
|
759
|
+
put(x, y, m);
|
|
760
|
+
}
|
|
761
|
+
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);
|
|
762
|
+
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);
|
|
763
|
+
for (let x = 156; x < 188; x += 7) { rect(x, 92, 4, 1, M.LINE); rect(x, 94, 4, 1, M.LINE); }
|
|
764
|
+
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); }
|
|
765
|
+
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);
|
|
766
|
+
// parked kei van
|
|
767
|
+
{ const x0 = 90; const y0 = 91;
|
|
768
|
+
rect(x0 + 1, y0 + 13, 31, 1, M.ASPHALT_DK);
|
|
769
|
+
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);
|
|
770
|
+
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);
|
|
771
|
+
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);
|
|
772
|
+
for (const wx of [x0 + 8, x0 + 24]) { disc(wx, y0 + 11, 3, M.ROOF); disc(wx, y0 + 11, 1, M.STONE_LT); }
|
|
773
|
+
rect(x0 + 18, y0 + 4, 1, 5, M.WALL_SH); put(x0 + 17, y0 + 6, M.STONE_LT); }
|
|
774
|
+
// ---- utility poles + street lamp ----
|
|
775
|
+
for (const px0 of [P1, P2]) {
|
|
776
|
+
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);
|
|
777
|
+
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);
|
|
778
|
+
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);
|
|
779
|
+
for (let y = 44; y < GY; y += 7) rect(px0 - 1, y, 4, 1, M.WOOD_LT);
|
|
780
|
+
}
|
|
781
|
+
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);
|
|
782
|
+
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); } };
|
|
783
|
+
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); }
|
|
784
|
+
// ---- potted plants, signboard, bicycle ----
|
|
785
|
+
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);
|
|
786
|
+
for (const bx of [53, 82]) { /* bike wheels */ }
|
|
787
|
+
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); };
|
|
788
|
+
bike(4);
|
|
789
|
+
};
|
|
790
|
+
const catSit = makeSprite(['C.C...', 'CCCC..', 'CCCCC.', '.CCCCC', '.CCCCC', '.CC.CC']);
|
|
791
|
+
const catSleep = makeSprite(['......', '......', '.CCCC.', 'CCCCCC', 'CCCCCC']);
|
|
792
|
+
const walkerP = { x: 24, face: 1, step: 0, wait: 2.5, goal: 150 };
|
|
793
|
+
const liveStreet = (h, dt) => {
|
|
794
|
+
const { GY, P1, P2 } = STR; const t = anim; const lamp = lampAmt(h); const xa = -ox; const xb = bw - ox;
|
|
795
|
+
// windows light up one by one; shop windows (always) follow the lamp curve
|
|
796
|
+
drawWins(h, winFrac(h), 0.9);
|
|
797
|
+
// ramen steam
|
|
798
|
+
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 }); } }
|
|
799
|
+
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); } }
|
|
800
|
+
// street lamp, lanterns and light pools
|
|
801
|
+
const lampOn = lamp > 0.05;
|
|
802
|
+
if (lampOn) {
|
|
803
|
+
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);
|
|
804
|
+
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);
|
|
805
|
+
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);
|
|
806
|
+
homeLight(100, 74, 26000 * lamp);
|
|
807
|
+
}
|
|
808
|
+
// vending machines glow
|
|
809
|
+
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); }
|
|
810
|
+
// cat on the curb (sleeps at night)
|
|
811
|
+
const asleep = h >= 22.5 || h < 6.5; const cx0 = 112; const cy0 = GY - 1;
|
|
812
|
+
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); }
|
|
813
|
+
// passer-by
|
|
814
|
+
const W = walkerP; const away = h >= 23.4 || h < 6.4;
|
|
815
|
+
if (calm) { W.x = 40; }
|
|
816
|
+
else if (!away) {
|
|
817
|
+
if (W.wait > 0) { W.wait -= dt; W.step = 0; }
|
|
818
|
+
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; } }
|
|
819
|
+
}
|
|
820
|
+
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); }
|
|
578
821
|
};
|
|
579
822
|
|
|
823
|
+
// ===== Scene: apartment (city window, sofa, cat on the bench, lamps) =====
|
|
824
|
+
const APT = { FL: 80, WX0: 17, WX1: 73, WY0: 12, WY1: 61 };
|
|
825
|
+
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)));
|
|
826
|
+
const catBench = makeSprite(['..C.C...', '.CCCCC..', 'CCCCCCC.', 'CCCCCCCC', 'QQQQQQQQ']);
|
|
827
|
+
const catBenchB = makeSprite(['..C.C...', '.CCCCC..', 'CCCCCCC.', 'CCCCCCCC', 'CQQQQQQC']);
|
|
828
|
+
const paintApartment = () => {
|
|
829
|
+
wins.length = 0; const { FL, WX0, WX1, WY0, WY1 } = APT; const xa = -ox; const xb = bw - ox; const yb = bh - oy;
|
|
830
|
+
// ---- walls: soft vertical stripes, shadow under the ceiling, wainscot ----
|
|
831
|
+
for (let y = -oy; y < FL; y++) for (let x = xa; x < xb; x++) {
|
|
832
|
+
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);
|
|
833
|
+
}
|
|
834
|
+
for (let x = xa; x < xb; x++) { put(x, FL - 2, M.WOOD_LT); put(x, FL - 1, M.WOOD_DK); }
|
|
835
|
+
// ---- floor planks (pale oak: tan by day, amber at golden hour) ----
|
|
836
|
+
for (let y = FL; y < yb; y++) for (let x = xa; x < xb; x++) {
|
|
837
|
+
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;
|
|
838
|
+
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);
|
|
839
|
+
}
|
|
840
|
+
// ---- window: sky shows through the glass; skyline layers on top ----
|
|
841
|
+
rect(WX0 - 3, WY0 - 3, WX1 - WX0 + 6, WY1 - WY0 + 6, M.WOOD_DK); rect(WX0, WY0, WX1 - WX0, WY1 - WY0, 0);
|
|
842
|
+
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); }
|
|
843
|
+
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; }
|
|
844
|
+
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); } }
|
|
845
|
+
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);
|
|
846
|
+
rect(WX0 - 5, WY1 + 2, WX1 - WX0 + 10, 3, M.WOOD_LT); rect(WX0 - 5, WY1 + 5, WX1 - WX0 + 10, 1, M.WOOD_DK);
|
|
847
|
+
// curtains with folds and tie-backs
|
|
848
|
+
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); }
|
|
849
|
+
// window bench + cushions
|
|
850
|
+
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);
|
|
851
|
+
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);
|
|
852
|
+
rect(62, 64, 12, 6, M.SOFA); rect(62, 64, 12, 1, M.SOFA_LT); rect(62, 69, 12, 1, M.SOFA_DK);
|
|
853
|
+
paintBush(41, 62, 4, 3, 1202); rect(37, 66, 9, 4, M.BRICK); rect(36, 65, 11, 1, M.BRICK_LT);
|
|
854
|
+
// ---- plant between window and sofa ----
|
|
855
|
+
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);
|
|
856
|
+
// ---- pendant, frames, clock, shelf ----
|
|
857
|
+
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);
|
|
858
|
+
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);
|
|
859
|
+
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);
|
|
860
|
+
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);
|
|
861
|
+
rect(104, 44, 58, 2, M.WOOD_DK); rect(104, 43, 58, 1, M.WOOD_LT);
|
|
862
|
+
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]); }
|
|
863
|
+
rect(138, 36, 7, 7, M.BRICK); rect(137, 35, 9, 1, M.BRICK_LT); paintBush(141, 33, 4, 3, 1203);
|
|
864
|
+
rect(150, 38, 8, 5, M.BOOK_DK); rect(151, 39, 6, 1, M.BOOK);
|
|
865
|
+
// ---- sofa ----
|
|
866
|
+
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);
|
|
867
|
+
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);
|
|
868
|
+
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);
|
|
869
|
+
rect(100, 82, 2, 3, M.WOOD_DK); rect(172, 82, 2, 3, M.WOOD_DK);
|
|
870
|
+
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);
|
|
871
|
+
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);
|
|
872
|
+
// ---- rug and coffee table ----
|
|
873
|
+
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); } }
|
|
874
|
+
for (let x = 102; x < 176; x += 4) { put(x, 87, M.RUG); put(x, 108, M.RUG); }
|
|
875
|
+
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);
|
|
876
|
+
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);
|
|
877
|
+
// ---- floor lamp ----
|
|
878
|
+
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);
|
|
879
|
+
// ---- baseboard heater + wall socket + small details ----
|
|
880
|
+
rect(180, 66, 2, 2, M.WOOD_DK);
|
|
881
|
+
};
|
|
882
|
+
const aptP = { blink: 2, stretch: 0, tc: 0, mug: [], ac: 3 };
|
|
883
|
+
const liveApartment = (h, dt) => {
|
|
884
|
+
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;
|
|
885
|
+
// window: lit city windows, tower beacon, a plane
|
|
886
|
+
drawWins(h, clamp(winFrac(h) * 1.25 + (night > 0.5 ? 0.2 : 0)));
|
|
887
|
+
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); } }
|
|
888
|
+
// sunbeam from the window across the floor
|
|
889
|
+
const sp = sunPos(h);
|
|
890
|
+
if (sp && sp.y < 62 && h > 6.8 && h < 17.4) {
|
|
891
|
+
const skew = (sp.x - 96) * 0.55; const k0 = clamp(1 - sp.y / 62, 0.35, 1);
|
|
892
|
+
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); } }
|
|
893
|
+
}
|
|
894
|
+
// lamps
|
|
895
|
+
const floorLamp = room; const pend = room * (h >= 12 || h < 7 ? 1 : 0);
|
|
896
|
+
if (floorLamp > 0.05) {
|
|
897
|
+
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); }
|
|
898
|
+
for (let y = 37; y < 45; y += 2) for (let x = 183; x < 192; x += 2) emit(x, y, 210 * floorLamp, 0);
|
|
899
|
+
pool(187, 60, 21, 20, floorLamp * 0.75); pool(182, 90, 20, 8, floorLamp * 0.6); homeLight(187, 42, 14000 * floorLamp);
|
|
900
|
+
}
|
|
901
|
+
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); }
|
|
902
|
+
// cat on the window bench (breathes; sleeps through the night)
|
|
903
|
+
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);
|
|
904
|
+
if (night > 0.2 || (h > 9 && h < 14)) { /* zzz */ }
|
|
905
|
+
// mug steam
|
|
906
|
+
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 }); } }
|
|
907
|
+
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); }
|
|
908
|
+
// reader on the sofa (away while asleep)
|
|
909
|
+
const present = !(h >= 23.4 || h < 6.4);
|
|
910
|
+
if (present) {
|
|
911
|
+
aptP.blink -= calm ? 0 : dt; const c = calm ? 0 : (t % 6.2) / 6.2; let spr = SPR.bust;
|
|
912
|
+
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;
|
|
913
|
+
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);
|
|
914
|
+
if (room > 0.3 && spr !== SPR.bustUp) { for (let x = 132; x < 139; x += 2) emit(x, 59, 40 * room, 2); }
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
const scene = ['countryside', 'japanese-street', 'apartment', 'seaside', 'mountain-cabin'].includes(dataset.scene) ? dataset.scene : 'countryside';
|
|
919
|
+
const LIVE = { seaside: liveSeaside, 'mountain-cabin': liveCabin, 'japanese-street': liveStreet, apartment: liveApartment };
|
|
920
|
+
const drawSceneLive = (h, dt) => { const fn = LIVE[scene]; if (fn) fn(h, dt); };
|
|
921
|
+
|
|
922
|
+
|
|
580
923
|
const buildBase = () => {
|
|
581
924
|
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);
|
|
582
925
|
const r = seeded(808); stars = [];
|
|
@@ -695,19 +1038,60 @@ function mount(root) {
|
|
|
695
1038
|
for (const c of sources) { c.w = 0; c.x = 0; c.y = 0; }
|
|
696
1039
|
lampFlick = calm ? 1 : 0.95 + 0.05 * Math.sin(anim * 13) * Math.sin(anim * 5.3);
|
|
697
1040
|
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 {
|
|
1041
|
+
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
1042
|
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
1043
|
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
1044
|
};
|
|
702
1045
|
const GRID_W = 72; let GRID_H = 44; let glowRaw = new Float32Array(1); let glowNearBuf = new Float32Array(1); let glowFarBuf = new Float32Array(1); let glowTemp = new Float32Array(1);
|
|
703
1046
|
const boxBlur = (src, dst, w, h, r) => { const size = r * 2 + 1; for (let y = 0; y < h; y++) { const row = y * w * 4; for (let c = 0; c < 3; c++) { let sum = 0; for (let x = -r; x <= r; x++) sum += src[row + clamp(x, 0, w - 1) * 4 + c]; for (let x = 0; x < w; x++) { dst[row + x * 4 + c] = sum / size; sum += src[row + clamp(x + r + 1, 0, w - 1) * 4 + c] - src[row + clamp(x - r, 0, w - 1) * 4 + c]; } } } for (let x = 0; x < w; x++) { for (let c = 0; c < 3; c++) { let sum = 0; for (let y = -r; y <= r; y++) sum += dst[clamp(y, 0, h - 1) * w * 4 + x * 4 + c]; for (let y = 0; y < h; y++) { src[y * w * 4 + x * 4 + c] = sum / size; sum += dst[clamp(y + r + 1, 0, h - 1) * w * 4 + x * 4 + c] - dst[clamp(y - r, 0, h - 1) * w * 4 + x * 4 + c]; } } } };
|
|
704
1047
|
let gl = null;
|
|
705
|
-
|
|
1048
|
+
let webglContextError = '';
|
|
1049
|
+
try {
|
|
1050
|
+
gl = canvas.getContext('webgl2', { alpha: true, premultipliedAlpha: true, antialias: false, depth: false, stencil: false, powerPreference: 'high-performance' });
|
|
1051
|
+
if (!gl) webglContextError = 'canvas.getContext("webgl2") returned null. WebGL2 may be unavailable, disabled, or blocked by the device WebView.';
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
webglContextError = error && (error.stack || error.message) || String(error);
|
|
1054
|
+
gl = null;
|
|
1055
|
+
}
|
|
706
1056
|
let program = null; const loc = {}; let vao = null; let quad = null; let texWorld = null; let texNear = null;
|
|
707
|
-
const compile = (type, source) => {
|
|
1057
|
+
const compile = (type, source) => {
|
|
1058
|
+
const label = type === gl.VERTEX_SHADER ? 'Vertex Shader' : 'Fragment Shader';
|
|
1059
|
+
const shader = gl.createShader(type);
|
|
1060
|
+
if (!shader) throw new Error(label + ': gl.createShader returned null');
|
|
1061
|
+
gl.shaderSource(shader, source);
|
|
1062
|
+
gl.compileShader(shader);
|
|
1063
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
1064
|
+
const log = gl.getShaderInfoLog(shader) || 'No shader compiler log was provided.';
|
|
1065
|
+
gl.deleteShader(shader);
|
|
1066
|
+
throw new Error(label + ' compilation failed:\\n' + log);
|
|
1067
|
+
}
|
|
1068
|
+
return shader;
|
|
1069
|
+
};
|
|
708
1070
|
const setupGl = () => {
|
|
709
|
-
if (!gl)
|
|
710
|
-
|
|
1071
|
+
if (!gl) {
|
|
1072
|
+
reportDiagnostic('WebGL2 Failed', webglContextError || 'WebGL2 context could not be created.');
|
|
1073
|
+
return false;
|
|
1074
|
+
}
|
|
1075
|
+
try {
|
|
1076
|
+
program = gl.createProgram();
|
|
1077
|
+
if (!program) throw new Error('gl.createProgram returned null');
|
|
1078
|
+
const v = compile(gl.VERTEX_SHADER, VERT);
|
|
1079
|
+
const f = compile(gl.FRAGMENT_SHADER, FRAG);
|
|
1080
|
+
gl.attachShader(program, v);
|
|
1081
|
+
gl.attachShader(program, f);
|
|
1082
|
+
gl.bindAttribLocation(program, 0, 'aPos');
|
|
1083
|
+
gl.linkProgram(program);
|
|
1084
|
+
gl.deleteShader(v);
|
|
1085
|
+
gl.deleteShader(f);
|
|
1086
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
1087
|
+
throw new Error('Shader program link failed:\\n' + (gl.getProgramInfoLog(program) || 'No program link log was provided.'));
|
|
1088
|
+
}
|
|
1089
|
+
} catch (error) {
|
|
1090
|
+
const detail = error && (error.stack || error.message) || String(error);
|
|
1091
|
+
console.warn('cannot create shader', error);
|
|
1092
|
+
reportDiagnostic('WebGL2 Failed', detail);
|
|
1093
|
+
return false;
|
|
1094
|
+
}
|
|
711
1095
|
gl.useProgram(program);
|
|
712
1096
|
for (const name of ['uWorld', 'uGlowNear', 'uRes', 'uPill', 'uOrigin', 'uWorldSize', 'uGlowSize', 'uLens', 'uBezel', 'uK', 'uNight', 'uLift', 'uSeam', 'uCalm', 'uRefraction', 'uDispersion', 'uShine', 'uShellDay', 'uShellNight', 'uInk', 'uLight', 'uHiNight', 'uLoNight', 'uSkyDay', 'uHomeCol', 'uSkyCol', 'uHomePos', 'uSkyPos']) { loc[name] = gl.getUniformLocation(program, name); }
|
|
713
1097
|
vao = gl.createVertexArray(); gl.bindVertexArray(vao); quad = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, quad); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW); gl.enableVertexAttribArray(0); gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
@@ -716,7 +1100,17 @@ function mount(root) {
|
|
|
716
1100
|
texWorld = makeTex(); texNear = makeTex(); gl.uniform1i(loc.uWorld, 0); gl.uniform1i(loc.uGlowNear, 1); gl.disable(gl.BLEND); return true;
|
|
717
1101
|
};
|
|
718
1102
|
let ready = setupGl(); let lost = !ready && !!gl && gl.isContextLost(); let ctx2d = null; let lowCanvas = null;
|
|
719
|
-
const useCanvas2d = () => {
|
|
1103
|
+
const useCanvas2d = () => {
|
|
1104
|
+
const fresh = canvas.cloneNode(false);
|
|
1105
|
+
canvas.replaceWith(fresh);
|
|
1106
|
+
canvas = fresh;
|
|
1107
|
+
try { ctx2d = canvas.getContext('2d'); }
|
|
1108
|
+
catch (error) {
|
|
1109
|
+
reportDiagnostic('Canvas 2D Failed', error && (error.stack || error.message) || String(error));
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
if (!ctx2d) reportDiagnostic('Canvas 2D Failed', 'canvas.getContext("2d") returned null after WebGL2 initialization failed.');
|
|
1113
|
+
};
|
|
720
1114
|
if (!ready && !lost) useCanvas2d();
|
|
721
1115
|
const shape = { W: 1, H: 1, cssW: 1, cssH: 1, dpr: 1, pillW: 1, pillH: 1, bezel: 1, winW: 1, winH: 1, r0: 1, travel: 1, travelCss: 1, ox: 0, oy: 0 };
|
|
722
1116
|
let renderScale = 1; let fullK = 1; let thumbSeam = 1;
|
|
@@ -753,12 +1147,14 @@ function mount(root) {
|
|
|
753
1147
|
const clockOf = () => { if (branch === 'am') return mix(36, amStart, x); if (x >= 1) return nightClock; return CLOCK_DAY + (CLOCK_NIGHT - CLOCK_DAY) * x; };
|
|
754
1148
|
const beginTravel = () => { if (x <= 0.0005) branch = 'pm'; else if (x >= 0.9995) { branch = 'am'; amStart = nightClock; } };
|
|
755
1149
|
const setStatus = (text) => { if (statusEl.textContent !== text) statusEl.textContent = text; };
|
|
756
|
-
const
|
|
1150
|
+
const locale = (() => { const el = root.closest('[lang]') || root.ownerDocument.documentElement; const value = (el.getAttribute('lang') || 'en').toLowerCase(); return value.startsWith('ja') ? 'ja' : value.startsWith('zh') ? 'zh-CN' : 'en'; })();
|
|
1151
|
+
const phaseNames = locale === 'ja' ? ['夜','夜明け','朝','昼','夕方'] : locale === 'zh-CN' ? ['夜晚','黎明','早晨','白天','傍晚'] : ['Night','Dawn','Morning','Day','Evening'];
|
|
1152
|
+
const phaseName = (h) => h < 5 || h >= 20 ? phaseNames[0] : h < 7 ? phaseNames[1] : h < 10 ? phaseNames[2] : h < 16.5 ? phaseNames[3] : phaseNames[4];
|
|
757
1153
|
const commit = (value) => {
|
|
758
1154
|
const night = value >= 1; x = night ? 1 : 0; velocity = 0; moving = false;
|
|
759
1155
|
if (night) { nightClock = branch === 'am' ? amStart : CLOCK_NIGHT; branch = 'pm'; } else { branch = 'pm'; }
|
|
760
1156
|
if (checked !== night) { checked = night; button.setAttribute('aria-checked', String(night)); root.dispatchEvent(new CustomEvent('daylight:change', { bubbles: true, composed: true, detail: { night } })); }
|
|
761
|
-
setStatus(night ? '夜' : '昼');
|
|
1157
|
+
setStatus(night ? (locale === 'ja' ? '夜' : locale === 'zh-CN' ? '夜晚' : 'Night') : (locale === 'ja' ? '昼' : locale === 'zh-CN' ? '白天' : 'Day'));
|
|
762
1158
|
};
|
|
763
1159
|
const drawGl = (night) => {
|
|
764
1160
|
gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); gl.useProgram(program); gl.bindVertexArray(vao);
|
|
@@ -824,7 +1220,7 @@ function mount(root) {
|
|
|
824
1220
|
on(canvas, 'webglcontextrestored', () => { if (ctx2d) return; clearTimeout(restoreTimer); lost = false; ready = setupGl(); if (!ready) useCanvas2d(); redrawAll(); });
|
|
825
1221
|
readPalette(); measure(); if (lost) waitForRestore(); res.x = 131; res.inside = false;
|
|
826
1222
|
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);
|
|
1223
|
+
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
1224
|
}
|
|
829
1225
|
const SELECTOR = '[data-ld-part="toggle-daylight"]';
|
|
830
1226
|
const STYLE_ID = 'ld-toggle-daylight-style';
|
|
@@ -900,7 +1296,7 @@ function ensureMarkup(root) {
|
|
|
900
1296
|
if (!root.querySelector('.scene')) root.appendChild(make('canvas', 'scene', { 'aria-hidden': 'true' }));
|
|
901
1297
|
if (!root.querySelector('.hit')) {
|
|
902
1298
|
const lang = ((root.closest('[lang]') || doc.documentElement).getAttribute('lang') || '').toLowerCase();
|
|
903
|
-
const label = root.getAttribute('data-label') || (lang.indexOf('zh') === 0 ? '昼夜开关' : 'Day/night switch');
|
|
1299
|
+
const label = root.getAttribute('data-label') || (lang.indexOf('ja') === 0 ? '昼夜スイッチ' : lang.indexOf('zh') === 0 ? '昼夜开关' : 'Day/night switch');
|
|
904
1300
|
root.appendChild(make('button', 'hit', { type: 'button', role: 'switch', 'aria-checked': 'false', 'aria-label': label }));
|
|
905
1301
|
}
|
|
906
1302
|
if (!root.querySelector('[data-status]')) {
|
|
@@ -920,6 +1316,7 @@ function mountOne(root) {
|
|
|
920
1316
|
return destroy;
|
|
921
1317
|
} catch (err) {
|
|
922
1318
|
console.error('[pixel-daylight-switch]', err);
|
|
1319
|
+
reportDiagnostic('App Initialization Failed', err && (err.stack || err.message) || String(err));
|
|
923
1320
|
return null;
|
|
924
1321
|
}
|
|
925
1322
|
}
|
|
@@ -935,4 +1332,4 @@ if (typeof document !== 'undefined') {
|
|
|
935
1332
|
}
|
|
936
1333
|
if (typeof window !== 'undefined') window.PixelDaylightSwitch = api;
|
|
937
1334
|
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
938
|
-
})();
|
|
1335
|
+
})();
|