@codexstar/pi-pompom 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/extensions/pompom.ts +204 -89
  2. package/package.json +1 -1
@@ -276,74 +276,113 @@ function shadeObject(hit: ReturnType<typeof getObjHit>, px: number, py: number,
276
276
  const spot = Math.sin(hitNx * 10) * Math.cos(hitNy * 8);
277
277
  if (spot > 0.6) { r = Math.max(0, r - 40); g = Math.max(0, g - 20); b = Math.max(0, b - 10); }
278
278
  }
279
- if (hitObj.id === "body") {
280
- let bdx = px - hitObj.x, bdy = py - hitObj.y;
281
- if (isFlipping) {
282
- const s = Math.sin(-flipPhase), c = Math.cos(-flipPhase);
283
- const nx = bdx * c - bdy * s, ny = bdx * s + bdy * c;
284
- bdx = nx; bdy = ny;
285
- }
286
- // Blush
287
- const blx1 = bdx + 0.16, bly1 = bdy - 0.04;
288
- const blx2 = bdx - 0.16, bly2 = bdy - 0.04;
289
- const blush = Math.exp(-(blx1 * blx1 + bly1 * bly1) * 80) + Math.exp(-(blx2 * blx2 + bly2 * bly2) * 80);
290
- if (!isSleeping) {
291
- r = r * (1 - blush) + 255 * blush; g = g * (1 - blush) + 80 * blush; b = b * (1 - blush) + 100 * blush;
292
- }
293
- // Eyes
294
- const isTired = (energy < 20 || hunger < 30) && !isSleeping;
295
- const eyeOpen = isSleeping ? 0.05 : (isTired ? 0.4 : 1.0) - blinkFade;
296
- const ex1 = bdx - lookX * 0.08 + 0.1, ey1 = bdy - lookY * 0.05 + 0.02;
297
- const ex2 = bdx - lookX * 0.08 - 0.1, ey2 = bdy - lookY * 0.05 + 0.02;
298
-
299
- if (isSleeping || currentState === "singing") {
300
- if (isSleeping) {
301
- if ((Math.abs(ey1) < 0.01 && Math.abs(ex1) < 0.05) || (Math.abs(ey2) < 0.01 && Math.abs(ex2) < 0.05)) { r = 30; g = 20; b = 30; }
302
- } else {
303
- if ((Math.abs(ey1 + ex1 * ex1 * 15) < 0.015 && Math.abs(ex1) < 0.06 && ey1 > -ex1 * ex1 * 15) ||
304
- (Math.abs(ey2 + ex2 * ex2 * 15) < 0.015 && Math.abs(ex2) < 0.06 && ey2 > -ex2 * ex2 * 15)) { r = 30; g = 20; b = 30; }
305
- }
306
- } else {
307
- const eDist1 = ex1 * ex1 + (ey1 * ey1) / (eyeOpen * eyeOpen + 0.001);
308
- const eDist2 = ex2 * ex2 + (ey2 * ey2) / (eyeOpen * eyeOpen + 0.001);
309
- if (eDist1 < 0.004 || eDist2 < 0.004) {
310
- r = 15; g = 10; b = 20;
311
- if (ey1 > 0 || ey2 > 0) { r = 50; g = 180; b = 100; }
312
- if ((ex1 + 0.012) ** 2 + (ey1 + 0.012) ** 2 < 0.0008 || (ex2 + 0.012) ** 2 + (ey2 + 0.012) ** 2 < 0.0008) {
313
- if (!isTired) { r = 255; g = 255; b = 255; }
314
- } else if ((ex1 - 0.015) ** 2 + (ey1 - 0.015) ** 2 < 0.0005 || (ex2 - 0.015) ** 2 + (ey2 - 0.015) ** 2 < 0.0005) {
315
- if (!isTired) { r = 255; g = 255; b = 255; }
316
- }
317
- }
318
- }
319
- // Nose
320
- const nnx = bdx - lookX * 0.06, nny = bdy - lookY * 0.05 - 0.02;
321
- if (nnx * nnx * 1.5 + nny * nny < 0.0006 && !isSleeping) { r = 30; g = 20; b = 30; }
322
- // Mouth
323
- if (!isSleeping && !hasBall) {
324
- const mx = bdx - lookX * 0.06, my = bdy - lookY * 0.05 - 0.06;
325
- if ((Math.abs(my - (mx - 0.025) ** 2 * 20 + 0.01) < 0.01 && mx > 0 && mx < 0.05) ||
326
- (Math.abs(my - (mx + 0.025) ** 2 * 20 + 0.01) < 0.01 && mx < 0 && mx > -0.05)) {
327
- r = 50; g = 30; b = 40;
328
- }
329
- if (currentState === "excited" || currentState === "singing" || speechTimer > 0 || isTalking) {
330
- const mouthOpen = (speechTimer > 0 || currentState === "singing" || isTalking)
331
- ? (isTalking ? talkAudioLevel * 0.04 + 0.005 : Math.abs(Math.sin(time * 12)) * 0.025)
332
- : 0.015;
333
- if (mx * mx + (my + 0.01) ** 2 < mouthOpen && my < -0.01) {
334
- r = 240; g = 80; b = 100;
335
- if (my < -0.025) { r = 255; g = 120; b = 140; }
336
- }
337
- }
338
- }
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
+ }
339
323
  } else {
340
- if (hitObj.id === "earL" || hitObj.id === "earR") {
341
- if (hitU > -0.3 && hitU < 0.3 && hitV > -0.5 && hitV < 0.5) { r = 255; g = 130; b = 160; }
342
- }
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
+ }
343
352
  }
344
- if (hitNz < 0.25) {
345
- r *= 0.6; g *= 0.6; b *= 0.6;
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
+ }
376
+ }
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; }
346
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
+ }
347
386
  } else if (hitObj.mat === 2) {
348
387
  r = Math.max(0, th.r - 20); g = Math.max(0, th.g - 15); b = Math.max(0, th.b - 10);
349
388
  if (hitNy > 0.5) { r = 255; g = 180; b = 190; }
@@ -457,45 +496,121 @@ function getPixel(px: number, py: number, objects: RenderObj[], skyColors: Retur
457
496
  const directHit = getObjHit(px, py, objects);
458
497
  if (directHit.hitObj) return shadeObject(directHit, px, py, objects);
459
498
 
499
+ const w = (skyColors as any).weather as Weather | undefined;
500
+ const tod = (skyColors as any).timeOfDay as TimeOfDay | undefined;
501
+
460
502
  const grad = Math.max(0, (1.0 + py) / 2.0);
461
503
  let bgR = Math.floor(skyColors.rTop * (1 - grad) + skyColors.rBot * grad);
462
504
  let bgG = Math.floor(skyColors.gTop * (1 - grad) + skyColors.gBot * grad);
463
505
  let bgB = Math.floor(skyColors.bTop * (1 - grad) + skyColors.bBot * grad);
464
506
 
465
- // Stars at night / dusk twinkling via time modulation
507
+ // 5. SNOW: soft glow/haziness across whole sky
508
+ if (w === "snow") {
509
+ bgR = Math.min(255, bgR + 30);
510
+ bgG = Math.min(255, bgG + 30);
511
+ bgB = Math.min(255, bgB + 40);
512
+ }
513
+
514
+ // 3. STARS & MOON
466
515
  if (skyColors.isNight) {
467
- const star = Math.sin(px * 80 + 1.3) * Math.cos(py * 80 + 0.7);
468
- const twinkle = Math.sin(time * 3 + px * 20 + py * 30) * 0.5 + 0.5;
469
- if (star > 0.98) { const b = Math.floor(180 + twinkle * 75); bgR = b; bgG = b; bgB = b; }
470
- else if (star > 0.96) { const b = Math.floor(40 + twinkle * 40); bgR += b; bgG += b; bgB += b; }
516
+ const moonDist = Math.sqrt((px - 0.4) ** 2 + (py + 0.4) ** 2);
517
+ if (moonDist < 0.15) {
518
+ const moonGlow = 1.0 - (moonDist / 0.15);
519
+ bgR = Math.min(255, bgR + moonGlow * 60);
520
+ bgG = Math.min(255, bgG + moonGlow * 60);
521
+ bgB = Math.min(255, bgB + moonGlow * 80);
522
+ }
523
+
524
+ const starPattern = Math.sin(px * 150) * Math.cos(py * 150 + px * 40);
525
+ if (starPattern > 0.92) {
526
+ const twinkle = Math.sin(time * 3 + px * 30 + py * 40) * 0.5 + 0.5;
527
+ const starColorHash = Math.abs(Math.sin(px * 313 + py * 717));
528
+ let sr = 255, sg = 255, sb = 255;
529
+ if (starColorHash < 0.3) { sr = 180; sg = 200; sb = 255; }
530
+ else if (starColorHash < 0.6) { sr = 255; sg = 255; sb = 180; }
531
+ else if (starColorHash < 0.8) { sr = 255; sg = 180; sb = 150; }
532
+
533
+ const intensity = starPattern > 0.98 ? twinkle : twinkle * 0.4;
534
+ bgR = Math.min(255, bgR + sr * intensity);
535
+ bgG = Math.min(255, bgG + sg * intensity);
536
+ bgB = Math.min(255, bgB + sb * intensity);
537
+ }
471
538
  }
472
539
 
473
- // Clouds wispy noise shapes in upper sky
474
- const w = (skyColors as any).weather as Weather | undefined;
540
+ // 4. SUNSET/DAWN (SUN DISK)
541
+ if (tod === "sunset" || tod === "dawn") {
542
+ const sunDist = Math.sqrt((px + 0.3) ** 2 + (py - 0.2) ** 2);
543
+ const halo = Math.max(0, 1.0 - sunDist * 2.0);
544
+ if (sunDist < 0.05) {
545
+ bgR = 255; bgG = 240; bgB = 200;
546
+ } else if (halo > 0) {
547
+ const hIntensity = Math.pow(halo, 2);
548
+ bgR = Math.min(255, bgR + hIntensity * 120);
549
+ bgG = Math.min(255, bgG + hIntensity * (tod === "sunset" ? 70 : 90));
550
+ bgB = Math.min(255, bgB + hIntensity * 40);
551
+ }
552
+ }
553
+
554
+ // 1. CLOUDS & 6. STORM CLOUDS
475
555
  if (w === "cloudy" || w === "rain" || w === "storm" || w === "snow") {
476
- const cloudDensity = w === "storm" ? 0.7 : w === "rain" ? 0.5 : w === "snow" ? 0.4 : 0.3;
477
- const cn = Math.sin(px * 8 + time * 0.3) * Math.cos(py * 12 - time * 0.2) +
478
- Math.sin(px * 16 + py * 6) * 0.5;
479
- if (cn > 1.0 - cloudDensity && py < 0.2) {
480
- const blend = Math.min(1.0, (cn - (1.0 - cloudDensity)) * 4);
481
- const cr = w === "storm" ? 50 : 180, cg = w === "storm" ? 50 : 185, cb = w === "storm" ? 55 : 195;
556
+ let cloudDensity = w === "storm" ? 0.8 : w === "rain" ? 0.6 : w === "snow" ? 0.5 : 0.4;
557
+
558
+ // Denser in upper half, wispy at lower altitudes
559
+ cloudDensity *= Math.max(0, 1.0 - (py + 1.0) / 1.6);
560
+
561
+ const drift = time * 0.15;
562
+ const n1 = Math.sin((px + drift) * 6) * Math.cos(py * 8) * 0.5 + 0.5;
563
+ const n2 = Math.sin((px - drift * 0.5) * 14 + py * 10) * 0.5 + 0.5;
564
+ const n3 = Math.sin((px + drift * 2) * 25 - py * 20) * 0.5 + 0.5;
565
+ const noise = n1 * 0.5 + n2 * 0.3 + n3 * 0.2;
566
+
567
+ const shadowNoise = Math.sin((px + drift) * 6 + 0.3) * Math.cos((py - 0.08) * 8) * 0.5 + 0.5;
568
+
569
+ if (noise > 1.0 - cloudDensity) {
570
+ const blend = Math.min(1.0, (noise - (1.0 - cloudDensity)) * 4);
571
+ const isShadow = shadowNoise < noise - 0.1;
572
+
573
+ let cr = w === "storm" ? 60 : 200;
574
+ let cg = w === "storm" ? 65 : 205;
575
+ let cb = w === "storm" ? 75 : 215;
576
+
577
+ if (isShadow) {
578
+ cr *= 0.6; cg *= 0.6; cb *= 0.7;
579
+ }
580
+
482
581
  bgR = Math.floor(bgR * (1 - blend) + cr * blend);
483
582
  bgG = Math.floor(bgG * (1 - blend) + cg * blend);
484
583
  bgB = Math.floor(bgB * (1 - blend) + cb * blend);
485
584
  }
486
585
  }
487
586
 
488
- // Sunset/dawn glow at horizon
489
- const tod = (skyColors as any).timeOfDay as TimeOfDay | undefined;
490
- if (tod === "sunset" || tod === "dawn") {
491
- const horizonGlow = Math.exp(-(py - 0.4) * (py - 0.4) * 20);
492
- if (tod === "sunset") {
493
- bgR = Math.min(255, Math.floor(bgR + horizonGlow * 80));
494
- bgG = Math.min(255, Math.floor(bgG + horizonGlow * 30));
495
- } else {
496
- bgR = Math.min(255, Math.floor(bgR + horizonGlow * 50));
497
- bgG = Math.min(255, Math.floor(bgG + horizonGlow * 40));
498
- bgB = Math.min(255, Math.floor(bgB + horizonGlow * 30));
587
+ // 6. STORM LIGHTNING
588
+ if (w === "storm" && Math.sin(time * 47) > 0.99) {
589
+ bgR = Math.min(255, bgR + 180);
590
+ bgG = Math.min(255, bgG + 180);
591
+ bgB = Math.min(255, bgB + 200);
592
+ }
593
+
594
+ // 2. RAIN VISUAL
595
+ if (w === "rain" || w === "storm") {
596
+ if (Math.sin(px * 40 + py * 80 + time * 15) > 0.95) {
597
+ bgR = Math.min(255, bgR + 50);
598
+ bgG = Math.min(255, bgG + 50);
599
+ bgB = Math.min(255, bgB + 70);
600
+ }
601
+ }
602
+
603
+ // 7. FOG/MIST
604
+ if (w === "rain" || w === "snow") {
605
+ const fogDist = Math.max(0, py);
606
+ if (fogDist > 0) {
607
+ const fogBlend = Math.min(1.0, (fogDist / 0.6) * 0.5);
608
+ const fr = w === "snow" ? 220 : 140;
609
+ const fg = w === "snow" ? 220 : 150;
610
+ const fb = w === "snow" ? 230 : 160;
611
+ bgR = Math.floor(bgR * (1 - fogBlend) + fr * fogBlend);
612
+ bgG = Math.floor(bgG * (1 - fogBlend) + fg * fogBlend);
613
+ bgB = Math.floor(bgB * (1 - fogBlend) + fb * fogBlend);
499
614
  }
500
615
  }
501
616
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codexstar/pi-pompom",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "A 3D raymarched virtual pet companion for Pi CLI",
5
5
  "type": "module",
6
6
  "author": "codexstar69 <engazedigital@gmail.com>",