@codexstar/pi-pompom 1.3.0 → 1.5.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/extensions/pompom.ts +226 -82
- package/package.json +1 -1
package/extensions/pompom.ts
CHANGED
|
@@ -145,13 +145,67 @@ function fbm(x: number, y: number): number {
|
|
|
145
145
|
Math.sin(x * 30 - time) * Math.cos(y * 30) * 0.02;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
type Weather = "clear" | "cloudy" | "rain" | "snow" | "storm";
|
|
149
|
+
type TimeOfDay = "dawn" | "morning" | "day" | "sunset" | "dusk" | "night";
|
|
150
|
+
|
|
151
|
+
function getTimeOfDay(): TimeOfDay {
|
|
149
152
|
const h = new Date().getHours();
|
|
153
|
+
if (h >= 5 && h < 7) return "dawn";
|
|
154
|
+
if (h >= 7 && h < 10) return "morning";
|
|
155
|
+
if (h >= 10 && h < 16) return "day";
|
|
156
|
+
if (h >= 16 && h < 18) return "sunset";
|
|
157
|
+
if (h >= 18 && h < 20) return "dusk";
|
|
158
|
+
return "night";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function getWeather(): Weather {
|
|
162
|
+
// Cycle weather based on minute within each hour for variety
|
|
163
|
+
const m = new Date().getMinutes();
|
|
164
|
+
if (m < 15) return "clear";
|
|
165
|
+
if (m < 25) return "cloudy";
|
|
166
|
+
if (m < 35) return "rain";
|
|
167
|
+
if (m < 40) return "storm";
|
|
168
|
+
if (m < 45) return "snow";
|
|
169
|
+
if (m < 55) return "cloudy";
|
|
170
|
+
return "clear";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function getWeatherAndTime() {
|
|
174
|
+
const tod = getTimeOfDay();
|
|
175
|
+
const weather = getWeather();
|
|
150
176
|
let rTop = 0, gTop = 0, bTop = 0, rBot = 0, gBot = 0, bBot = 0;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
177
|
+
|
|
178
|
+
// Sky gradients per time of day
|
|
179
|
+
if (tod === "dawn") {
|
|
180
|
+
rTop = 40; gTop = 30; bTop = 80; rBot = 255; gBot = 140; bBot = 80;
|
|
181
|
+
} else if (tod === "morning") {
|
|
182
|
+
rTop = 60; gTop = 140; bTop = 255; rBot = 200; gBot = 220; bBot = 255;
|
|
183
|
+
} else if (tod === "day") {
|
|
184
|
+
rTop = 40; gTop = 120; bTop = 255; rBot = 180; gBot = 220; bBot = 255;
|
|
185
|
+
} else if (tod === "sunset") {
|
|
186
|
+
rTop = 140; gTop = 50; bTop = 120; rBot = 255; gBot = 120; bBot = 60;
|
|
187
|
+
} else if (tod === "dusk") {
|
|
188
|
+
rTop = 50; gTop = 20; bTop = 80; rBot = 120; gBot = 60; bBot = 80;
|
|
189
|
+
} else { // night
|
|
190
|
+
rTop = 5; gTop = 5; bTop = 15; rBot = 15; gBot = 10; bBot = 30;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Weather tinting — overcast dims the sky, storm darkens further
|
|
194
|
+
if (weather === "cloudy") {
|
|
195
|
+
rTop = Math.floor(rTop * 0.7 + 40); gTop = Math.floor(gTop * 0.7 + 40); bTop = Math.floor(bTop * 0.7 + 40);
|
|
196
|
+
rBot = Math.floor(rBot * 0.7 + 40); gBot = Math.floor(gBot * 0.7 + 40); bBot = Math.floor(bBot * 0.7 + 40);
|
|
197
|
+
} else if (weather === "rain") {
|
|
198
|
+
rTop = Math.floor(rTop * 0.5 + 30); gTop = Math.floor(gTop * 0.5 + 30); bTop = Math.floor(bTop * 0.5 + 40);
|
|
199
|
+
rBot = Math.floor(rBot * 0.5 + 30); gBot = Math.floor(gBot * 0.5 + 30); bBot = Math.floor(bBot * 0.5 + 40);
|
|
200
|
+
} else if (weather === "storm") {
|
|
201
|
+
rTop = Math.floor(rTop * 0.3 + 15); gTop = Math.floor(gTop * 0.3 + 15); bTop = Math.floor(bTop * 0.3 + 20);
|
|
202
|
+
rBot = Math.floor(rBot * 0.3 + 20); gBot = Math.floor(gBot * 0.3 + 20); bBot = Math.floor(bBot * 0.3 + 25);
|
|
203
|
+
} else if (weather === "snow") {
|
|
204
|
+
rTop = Math.floor(rTop * 0.6 + 60); gTop = Math.floor(gTop * 0.6 + 60); bTop = Math.floor(bTop * 0.6 + 70);
|
|
205
|
+
rBot = Math.floor(rBot * 0.6 + 60); gBot = Math.floor(gBot * 0.6 + 60); bBot = Math.floor(bBot * 0.6 + 70);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return { rTop, gTop, bTop, rBot, gBot, bBot, isNight: tod === "night" || tod === "dusk", weather, timeOfDay: tod };
|
|
155
209
|
}
|
|
156
210
|
|
|
157
211
|
function getObjHit(px: number, py: number, objects: RenderObj[]) {
|
|
@@ -222,74 +276,113 @@ function shadeObject(hit: ReturnType<typeof getObjHit>, px: number, py: number,
|
|
|
222
276
|
const spot = Math.sin(hitNx * 10) * Math.cos(hitNy * 8);
|
|
223
277
|
if (spot > 0.6) { r = Math.max(0, r - 40); g = Math.max(0, g - 20); b = Math.max(0, b - 10); }
|
|
224
278
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (!isSleeping && !hasBall) {
|
|
270
|
-
const mx = bdx - lookX * 0.06, my = bdy - lookY * 0.05 - 0.06;
|
|
271
|
-
if ((Math.abs(my - (mx - 0.025) ** 2 * 20 + 0.01) < 0.01 && mx > 0 && mx < 0.05) ||
|
|
272
|
-
(Math.abs(my - (mx + 0.025) ** 2 * 20 + 0.01) < 0.01 && mx < 0 && mx > -0.05)) {
|
|
273
|
-
r = 50; g = 30; b = 40;
|
|
274
|
-
}
|
|
275
|
-
if (currentState === "excited" || currentState === "singing" || speechTimer > 0 || isTalking) {
|
|
276
|
-
const mouthOpen = (speechTimer > 0 || currentState === "singing" || isTalking)
|
|
277
|
-
? (isTalking ? talkAudioLevel * 0.04 + 0.005 : Math.abs(Math.sin(time * 12)) * 0.025)
|
|
278
|
-
: 0.015;
|
|
279
|
-
if (mx * mx + (my + 0.01) ** 2 < mouthOpen && my < -0.01) {
|
|
280
|
-
r = 240; g = 80; b = 100;
|
|
281
|
-
if (my < -0.025) { r = 255; g = 120; b = 140; }
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
279
|
+
let isOnFace = false;
|
|
280
|
+
if (hitObj.id === "body") {
|
|
281
|
+
let bdx = px - hitObj.x, bdy = py - hitObj.y;
|
|
282
|
+
if (isFlipping) {
|
|
283
|
+
const s = Math.sin(-flipPhase), c = Math.cos(-flipPhase);
|
|
284
|
+
const nx = bdx * c - bdy * s, ny = bdx * s + bdy * c;
|
|
285
|
+
bdx = nx; bdy = ny;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ── Face plate: bright cream area so features pop ──
|
|
289
|
+
const faceR = Math.sqrt(bdx * bdx + bdy * bdy);
|
|
290
|
+
if (faceR < 0.22) {
|
|
291
|
+
isOnFace = true;
|
|
292
|
+
const faceMix = Math.max(0, 1.0 - faceR / 0.22);
|
|
293
|
+
r = Math.floor(r * (1 - faceMix * 0.6) + 255 * faceMix * 0.6);
|
|
294
|
+
g = Math.floor(g * (1 - faceMix * 0.6) + 252 * faceMix * 0.6);
|
|
295
|
+
b = Math.floor(b * (1 - faceMix * 0.6) + 248 * faceMix * 0.6);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ── Blush: big rosy cheeks ──
|
|
299
|
+
const blx1 = bdx + 0.15, bly1 = bdy - 0.05;
|
|
300
|
+
const blx2 = bdx - 0.15, bly2 = bdy - 0.05;
|
|
301
|
+
const blush = Math.exp(-(blx1 * blx1 + bly1 * bly1) * 40) + Math.exp(-(blx2 * blx2 + bly2 * bly2) * 40);
|
|
302
|
+
if (!isSleeping) {
|
|
303
|
+
r = Math.floor(r * (1 - blush) + 255 * blush);
|
|
304
|
+
g = Math.floor(g * (1 - blush) + 70 * blush);
|
|
305
|
+
b = Math.floor(b * (1 - blush) + 90 * blush);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ── Eyes: kawaii style — white sclera → colored iris → dark pupil → highlight ──
|
|
309
|
+
const isTired = (energy < 20 || hunger < 30) && !isSleeping;
|
|
310
|
+
const eyeOpen = isSleeping ? 0.05 : (isTired ? 0.4 : 1.0) - blinkFade;
|
|
311
|
+
const ex1 = bdx - lookX * 0.08 + 0.11, ey1 = bdy - lookY * 0.05 + 0.02;
|
|
312
|
+
const ex2 = bdx - lookX * 0.08 - 0.11, ey2 = bdy - lookY * 0.05 + 0.02;
|
|
313
|
+
|
|
314
|
+
if (isSleeping || currentState === "singing") {
|
|
315
|
+
// Closed eyes — horizontal lines
|
|
316
|
+
if (isSleeping) {
|
|
317
|
+
if ((Math.abs(ey1) < 0.012 && Math.abs(ex1) < 0.06) || (Math.abs(ey2) < 0.012 && Math.abs(ex2) < 0.06)) { r = 40; g = 30; b = 50; }
|
|
318
|
+
} else {
|
|
319
|
+
// Happy squint arcs
|
|
320
|
+
if ((Math.abs(ey1 + ex1 * ex1 * 12) < 0.018 && Math.abs(ex1) < 0.07 && ey1 > -ex1 * ex1 * 12) ||
|
|
321
|
+
(Math.abs(ey2 + ex2 * ex2 * 12) < 0.018 && Math.abs(ex2) < 0.07 && ey2 > -ex2 * ex2 * 12)) { r = 40; g = 30; b = 50; }
|
|
322
|
+
}
|
|
285
323
|
} else {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
324
|
+
const eDist1 = ex1 * ex1 + (ey1 * ey1) / (eyeOpen * eyeOpen + 0.001);
|
|
325
|
+
const eDist2 = ex2 * ex2 + (ey2 * ey2) / (eyeOpen * eyeOpen + 0.001);
|
|
326
|
+
|
|
327
|
+
// Layer 1: White sclera (outermost)
|
|
328
|
+
if (eDist1 < 0.009 || eDist2 < 0.009) {
|
|
329
|
+
r = 250; g = 250; b = 255;
|
|
330
|
+
|
|
331
|
+
// Layer 2: Colored iris
|
|
332
|
+
if (eDist1 < 0.005 || eDist2 < 0.005) {
|
|
333
|
+
r = 40; g = 130; b = 90; // teal-green iris
|
|
334
|
+
// Lower iris lighter
|
|
335
|
+
if (ey1 > 0.01 || ey2 > 0.01) { r = 60; g = 170; b = 110; }
|
|
336
|
+
|
|
337
|
+
// Layer 3: Dark pupil
|
|
338
|
+
if (eDist1 < 0.002 || eDist2 < 0.002) {
|
|
339
|
+
r = 10; g = 10; b = 15;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Big highlight (upper-left) — spans ~2 chars
|
|
344
|
+
if ((ex1 + 0.02) ** 2 + (ey1 + 0.02) ** 2 < 0.0015 || (ex2 + 0.02) ** 2 + (ey2 + 0.02) ** 2 < 0.0015) {
|
|
345
|
+
if (!isTired) { r = 255; g = 255; b = 255; }
|
|
346
|
+
}
|
|
347
|
+
// Small secondary highlight (lower-right)
|
|
348
|
+
if ((ex1 - 0.02) ** 2 + (ey1 - 0.02) ** 2 < 0.0006 || (ex2 - 0.02) ** 2 + (ey2 - 0.02) ** 2 < 0.0006) {
|
|
349
|
+
if (!isTired) { r = 230; g = 240; b = 255; }
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ── Nose: small dark oval ──
|
|
355
|
+
const nnx = bdx - lookX * 0.06, nny = bdy - lookY * 0.05 - 0.03;
|
|
356
|
+
if (nnx * nnx * 1.2 + nny * nny < 0.001 && !isSleeping) { r = 40; g = 30; b = 40; }
|
|
357
|
+
|
|
358
|
+
// ── Mouth: clear smile arc ──
|
|
359
|
+
if (!isSleeping && !hasBall) {
|
|
360
|
+
const mx = bdx - lookX * 0.06, my = bdy - lookY * 0.05 - 0.07;
|
|
361
|
+
// Smile curves
|
|
362
|
+
if ((Math.abs(my - (mx - 0.03) ** 2 * 15 + 0.012) < 0.013 && mx > 0 && mx < 0.06) ||
|
|
363
|
+
(Math.abs(my - (mx + 0.03) ** 2 * 15 + 0.012) < 0.013 && mx < 0 && mx > -0.06)) {
|
|
364
|
+
r = 50; g = 30; b = 40;
|
|
365
|
+
}
|
|
366
|
+
// Open mouth when excited/talking
|
|
367
|
+
if (currentState === "excited" || currentState === "singing" || currentState === "dance" || speechTimer > 0 || isTalking) {
|
|
368
|
+
const mouthOpen = (speechTimer > 0 || currentState === "singing" || isTalking)
|
|
369
|
+
? (isTalking ? talkAudioLevel * 0.04 + 0.008 : Math.abs(Math.sin(time * 12)) * 0.03)
|
|
370
|
+
: 0.02;
|
|
371
|
+
if (mx * mx + (my + 0.012) ** 2 < mouthOpen && my < -0.01) {
|
|
372
|
+
r = 230; g = 70; b = 90;
|
|
373
|
+
if (my < -0.03) { r = 255; g = 110; b = 130; }
|
|
374
|
+
}
|
|
375
|
+
}
|
|
289
376
|
}
|
|
290
|
-
|
|
291
|
-
|
|
377
|
+
} else {
|
|
378
|
+
if (hitObj.id === "earL" || hitObj.id === "earR") {
|
|
379
|
+
if (hitU > -0.3 && hitU < 0.3 && hitV > -0.5 && hitV < 0.5) { r = 255; g = 130; b = 160; }
|
|
292
380
|
}
|
|
381
|
+
}
|
|
382
|
+
// Dark outline — but NOT on the face area (preserves feature contrast)
|
|
383
|
+
if (hitNz < 0.25 && !isOnFace) {
|
|
384
|
+
r = Math.floor(r * 0.6); g = Math.floor(g * 0.6); b = Math.floor(b * 0.6);
|
|
385
|
+
}
|
|
293
386
|
} else if (hitObj.mat === 2) {
|
|
294
387
|
r = Math.max(0, th.r - 20); g = Math.max(0, th.g - 15); b = Math.max(0, th.b - 10);
|
|
295
388
|
if (hitNy > 0.5) { r = 255; g = 180; b = 190; }
|
|
@@ -407,11 +500,44 @@ function getPixel(px: number, py: number, objects: RenderObj[], skyColors: Retur
|
|
|
407
500
|
let bgR = Math.floor(skyColors.rTop * (1 - grad) + skyColors.rBot * grad);
|
|
408
501
|
let bgG = Math.floor(skyColors.gTop * (1 - grad) + skyColors.gBot * grad);
|
|
409
502
|
let bgB = Math.floor(skyColors.bTop * (1 - grad) + skyColors.bBot * grad);
|
|
503
|
+
|
|
504
|
+
// Stars at night / dusk — twinkling via time modulation
|
|
410
505
|
if (skyColors.isNight) {
|
|
411
|
-
const star = Math.sin(px * 80) * Math.cos(py * 80);
|
|
412
|
-
|
|
413
|
-
|
|
506
|
+
const star = Math.sin(px * 80 + 1.3) * Math.cos(py * 80 + 0.7);
|
|
507
|
+
const twinkle = Math.sin(time * 3 + px * 20 + py * 30) * 0.5 + 0.5;
|
|
508
|
+
if (star > 0.98) { const b = Math.floor(180 + twinkle * 75); bgR = b; bgG = b; bgB = b; }
|
|
509
|
+
else if (star > 0.96) { const b = Math.floor(40 + twinkle * 40); bgR += b; bgG += b; bgB += b; }
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Clouds — wispy noise shapes in upper sky
|
|
513
|
+
const w = (skyColors as any).weather as Weather | undefined;
|
|
514
|
+
if (w === "cloudy" || w === "rain" || w === "storm" || w === "snow") {
|
|
515
|
+
const cloudDensity = w === "storm" ? 0.7 : w === "rain" ? 0.5 : w === "snow" ? 0.4 : 0.3;
|
|
516
|
+
const cn = Math.sin(px * 8 + time * 0.3) * Math.cos(py * 12 - time * 0.2) +
|
|
517
|
+
Math.sin(px * 16 + py * 6) * 0.5;
|
|
518
|
+
if (cn > 1.0 - cloudDensity && py < 0.2) {
|
|
519
|
+
const blend = Math.min(1.0, (cn - (1.0 - cloudDensity)) * 4);
|
|
520
|
+
const cr = w === "storm" ? 50 : 180, cg = w === "storm" ? 50 : 185, cb = w === "storm" ? 55 : 195;
|
|
521
|
+
bgR = Math.floor(bgR * (1 - blend) + cr * blend);
|
|
522
|
+
bgG = Math.floor(bgG * (1 - blend) + cg * blend);
|
|
523
|
+
bgB = Math.floor(bgB * (1 - blend) + cb * blend);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Sunset/dawn glow at horizon
|
|
528
|
+
const tod = (skyColors as any).timeOfDay as TimeOfDay | undefined;
|
|
529
|
+
if (tod === "sunset" || tod === "dawn") {
|
|
530
|
+
const horizonGlow = Math.exp(-(py - 0.4) * (py - 0.4) * 20);
|
|
531
|
+
if (tod === "sunset") {
|
|
532
|
+
bgR = Math.min(255, Math.floor(bgR + horizonGlow * 80));
|
|
533
|
+
bgG = Math.min(255, Math.floor(bgG + horizonGlow * 30));
|
|
534
|
+
} else {
|
|
535
|
+
bgR = Math.min(255, Math.floor(bgR + horizonGlow * 50));
|
|
536
|
+
bgG = Math.min(255, Math.floor(bgG + horizonGlow * 40));
|
|
537
|
+
bgB = Math.min(255, Math.floor(bgB + horizonGlow * 30));
|
|
538
|
+
}
|
|
414
539
|
}
|
|
540
|
+
|
|
415
541
|
return [bgR, bgG, bgB];
|
|
416
542
|
}
|
|
417
543
|
|
|
@@ -486,14 +612,21 @@ function updatePhysics(dt: number) {
|
|
|
486
612
|
ffZ = posZ + Math.sin(time * 0.9) * 0.4;
|
|
487
613
|
|
|
488
614
|
// Weather particles
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
particles.push({
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
});
|
|
615
|
+
const weather = getWeather();
|
|
616
|
+
const effectDim = Math.max(40, Math.min(W, H * 4));
|
|
617
|
+
const wScale = 2.0 / effectDim;
|
|
618
|
+
if (weather === "rain" && Math.random() < 0.4) {
|
|
619
|
+
particles.push({ x: (Math.random() - 0.5) * W * wScale, y: -H * wScale, vx: 0.15, vy: 2.5 + Math.random(), char: "|", r: 150, g: 200, b: 255, life: 1.0, type: "rain" });
|
|
620
|
+
}
|
|
621
|
+
if (weather === "storm" && Math.random() < 0.6) {
|
|
622
|
+
particles.push({ x: (Math.random() - 0.5) * W * wScale, y: -H * wScale, vx: 0.4 + Math.random() * 0.3, vy: 3.0 + Math.random() * 2, char: "/", r: 180, g: 200, b: 255, life: 0.8, type: "rain" });
|
|
623
|
+
// Occasional lightning flash (brief bright particle)
|
|
624
|
+
if (Math.random() < 0.005) {
|
|
625
|
+
particles.push({ x: (Math.random() - 0.5) * W * wScale * 0.5, y: -H * wScale * 0.5, vx: 0, vy: 0, char: "#", r: 255, g: 255, b: 255, life: 0.1, type: "lightning" });
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (weather === "snow" && Math.random() < 0.2) {
|
|
629
|
+
particles.push({ x: (Math.random() - 0.5) * W * wScale, y: -H * wScale, vx: (Math.random() - 0.5) * 0.3, vy: 0.4 + Math.random() * 0.3, char: ".", r: 240, g: 245, b: 255, life: 3.0, type: "snow" });
|
|
497
630
|
}
|
|
498
631
|
|
|
499
632
|
// Ball physics
|
|
@@ -639,6 +772,8 @@ function updatePhysics(dt: number) {
|
|
|
639
772
|
if (p.type === "z") p.x += Math.sin(p.y * 4.0) * 0.005;
|
|
640
773
|
if (p.type === "note") p.x += Math.sin(p.y * 6.0) * 0.01;
|
|
641
774
|
if (p.type === "rain" && p.y > 0.6) { p.type = "splash"; p.char = "."; p.vy = -0.5; p.vx = (Math.random() - 0.5) * 0.5; p.life = 0.2; }
|
|
775
|
+
if (p.type === "snow") { p.vx += Math.sin(time * 2 + p.x * 5) * 0.01; if (p.y > 0.55) { p.life = 0; } }
|
|
776
|
+
if (p.type === "lightning") { p.life -= dt * 8; }
|
|
642
777
|
p.life -= dt * 0.8;
|
|
643
778
|
if (p.life <= 0) particles.splice(i, 1);
|
|
644
779
|
}
|
|
@@ -798,7 +933,16 @@ export function renderPompom(width: number, audioLevel: number, dt: number): str
|
|
|
798
933
|
else if (currentState === "peek") stateMsg = "Pompom is peeking back in... hi!";
|
|
799
934
|
else if (currentState === "offscreen") stateMsg = "Pompom wandered off... they'll be back";
|
|
800
935
|
else if (isTalking) stateMsg = "Pompom is listening to you speak";
|
|
801
|
-
else
|
|
936
|
+
else {
|
|
937
|
+
const w = getWeather(), tod = getTimeOfDay();
|
|
938
|
+
if (w === "storm") stateMsg = "Pompom hides from the thunder!";
|
|
939
|
+
else if (w === "rain") stateMsg = "Pompom watches the rain fall";
|
|
940
|
+
else if (w === "snow") stateMsg = "Pompom catches snowflakes!";
|
|
941
|
+
else if (tod === "dawn") stateMsg = "Pompom watches the sunrise";
|
|
942
|
+
else if (tod === "sunset") stateMsg = "Pompom enjoys the sunset";
|
|
943
|
+
else if (tod === "night") stateMsg = "Pompom stargazes under the night sky";
|
|
944
|
+
else stateMsg = "Pompom is vibing. Pet, feed, or play!";
|
|
945
|
+
}
|
|
802
946
|
|
|
803
947
|
// Build status: "─ ⌥ w·Wake p·Pet ... │ State ───" exactly W visible chars
|
|
804
948
|
const shortcuts: [string, string][] = [
|