@codexstar/pi-pompom 1.2.0 → 1.4.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 +129 -24
  2. package/package.json +1 -1
@@ -9,7 +9,7 @@
9
9
  // Widget dimensions — set once, used by renderPompom
10
10
  let W = 50;
11
11
  let H = 14; // character rows (each = 2 logical pixels via half-block)
12
- const VIEW_OFFSET_Y = 0.18; // shift camera down so ground is visible in compact mode
12
+ const VIEW_OFFSET_Y = 0.2; // shift camera down so ground is visible in compact mode
13
13
 
14
14
  const PHYSICS_DT = 0.016; // 60fps physics sub-stepping
15
15
 
@@ -91,7 +91,7 @@ function say(text: string, duration = 4.0) {
91
91
  }
92
92
 
93
93
  function project2D(x: number, y: number): [number, number] {
94
- const effectDim = Math.max(40, Math.min(W, H * 2.8));
94
+ const effectDim = Math.max(40, Math.min(W, H * 4));
95
95
  const scale = 2.0 / effectDim;
96
96
  const cx = (x / scale) + (W / 2.0);
97
97
  const cy = ((y - VIEW_OFFSET_Y) / scale + H) / 2.0;
@@ -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
- function getWeatherAndTime() {
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
- if (h >= 6 && h < 17) { rTop = 40; gTop = 120; bTop = 255; rBot = 180; gBot = 220; bBot = 255; }
152
- else if (h >= 17 && h < 19) { rTop = 140; gTop = 50; bTop = 120; rBot = 255; gBot = 160; bBot = 100; }
153
- else { rTop = 10; gTop = 10; bTop = 20; rBot = 25; gBot = 20; bBot = 40; }
154
- return { rTop, gTop, bTop, rBot, gBot, bBot, isNight: h >= 19 || h < 6 };
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[]) {
@@ -255,9 +309,9 @@ function shadeObject(hit: ReturnType<typeof getObjHit>, px: number, py: number,
255
309
  if (eDist1 < 0.004 || eDist2 < 0.004) {
256
310
  r = 15; g = 10; b = 20;
257
311
  if (ey1 > 0 || ey2 > 0) { r = 50; g = 180; b = 100; }
258
- if ((ex1 + 0.012) ** 2 + (ey1 + 0.012) ** 2 < 0.0003 || (ex2 + 0.012) ** 2 + (ey2 + 0.012) ** 2 < 0.0003) {
312
+ if ((ex1 + 0.012) ** 2 + (ey1 + 0.012) ** 2 < 0.0008 || (ex2 + 0.012) ** 2 + (ey2 + 0.012) ** 2 < 0.0008) {
259
313
  if (!isTired) { r = 255; g = 255; b = 255; }
260
- } else if ((ex1 - 0.015) ** 2 + (ey1 - 0.015) ** 2 < 0.0001 || (ex2 - 0.015) ** 2 + (ey2 - 0.015) ** 2 < 0.0001) {
314
+ } else if ((ex1 - 0.015) ** 2 + (ey1 - 0.015) ** 2 < 0.0005 || (ex2 - 0.015) ** 2 + (ey2 - 0.015) ** 2 < 0.0005) {
261
315
  if (!isTired) { r = 255; g = 255; b = 255; }
262
316
  }
263
317
  }
@@ -407,11 +461,44 @@ function getPixel(px: number, py: number, objects: RenderObj[], skyColors: Retur
407
461
  let bgR = Math.floor(skyColors.rTop * (1 - grad) + skyColors.rBot * grad);
408
462
  let bgG = Math.floor(skyColors.gTop * (1 - grad) + skyColors.gBot * grad);
409
463
  let bgB = Math.floor(skyColors.bTop * (1 - grad) + skyColors.bBot * grad);
464
+
465
+ // Stars at night / dusk — twinkling via time modulation
410
466
  if (skyColors.isNight) {
411
- const star = Math.sin(px * 80) * Math.cos(py * 80);
412
- if (star > 0.99) { bgR = 255; bgG = 255; bgB = 255; }
413
- else if (star > 0.97) { bgR += 50; bgG += 50; bgB += 50; }
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; }
471
+ }
472
+
473
+ // Clouds — wispy noise shapes in upper sky
474
+ const w = (skyColors as any).weather as Weather | undefined;
475
+ 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;
482
+ bgR = Math.floor(bgR * (1 - blend) + cr * blend);
483
+ bgG = Math.floor(bgG * (1 - blend) + cg * blend);
484
+ bgB = Math.floor(bgB * (1 - blend) + cb * blend);
485
+ }
486
+ }
487
+
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));
499
+ }
414
500
  }
501
+
415
502
  return [bgR, bgG, bgB];
416
503
  }
417
504
 
@@ -463,7 +550,7 @@ function buildObjects(): RenderObj[] {
463
550
  }
464
551
 
465
552
  function getScreenEdgeX(): number {
466
- const effectDim = Math.max(40, Math.min(W, H * 2.8));
553
+ const effectDim = Math.max(40, Math.min(W, H * 4));
467
554
  const scale = 2.0 / effectDim;
468
555
  return (W / 2.0) * scale;
469
556
  }
@@ -486,14 +573,21 @@ function updatePhysics(dt: number) {
486
573
  ffZ = posZ + Math.sin(time * 0.9) * 0.4;
487
574
 
488
575
  // Weather particles
489
- const isRaining = new Date().getMinutes() % 10 < 3 && !getWeatherAndTime().isNight;
490
- if (isRaining && Math.random() < 0.3) {
491
- const effectDim = Math.max(40, Math.min(W, H * 2.8));
492
- const scale = 2.0 / effectDim;
493
- particles.push({
494
- x: (Math.random() - 0.5) * W * scale, y: -H * scale,
495
- vx: 0.1, vy: 2.0 + Math.random(), char: "|", r: 150, g: 200, b: 255, life: 1.0, type: "rain",
496
- });
576
+ const weather = getWeather();
577
+ const effectDim = Math.max(40, Math.min(W, H * 4));
578
+ const wScale = 2.0 / effectDim;
579
+ if (weather === "rain" && Math.random() < 0.4) {
580
+ 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" });
581
+ }
582
+ if (weather === "storm" && Math.random() < 0.6) {
583
+ 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" });
584
+ // Occasional lightning flash (brief bright particle)
585
+ if (Math.random() < 0.005) {
586
+ 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" });
587
+ }
588
+ }
589
+ if (weather === "snow" && Math.random() < 0.2) {
590
+ 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
591
  }
498
592
 
499
593
  // Ball physics
@@ -639,13 +733,15 @@ function updatePhysics(dt: number) {
639
733
  if (p.type === "z") p.x += Math.sin(p.y * 4.0) * 0.005;
640
734
  if (p.type === "note") p.x += Math.sin(p.y * 6.0) * 0.01;
641
735
  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; }
736
+ if (p.type === "snow") { p.vx += Math.sin(time * 2 + p.x * 5) * 0.01; if (p.y > 0.55) { p.life = 0; } }
737
+ if (p.type === "lightning") { p.life -= dt * 8; }
642
738
  p.life -= dt * 0.8;
643
739
  if (p.life <= 0) particles.splice(i, 1);
644
740
  }
645
741
  }
646
742
 
647
743
  function renderToBuffers() {
648
- const effectDim = Math.max(40, Math.min(W, H * 2.8));
744
+ const effectDim = Math.max(40, Math.min(W, H * 4));
649
745
  const scale = 2.0 / effectDim;
650
746
  const objects = buildObjects();
651
747
  const skyColors = getWeatherAndTime();
@@ -676,7 +772,7 @@ function renderToBuffers() {
676
772
  if (d > maxD) maxD = d;
677
773
  }
678
774
 
679
- if (maxD > 60) {
775
+ if (maxD > 30) {
680
776
  // EDGE CELL — use quadrant character for 2× horizontal detail
681
777
  const lum0 = tl[0] * 77 + tl[1] * 150 + tl[2] * 29;
682
778
  const lum1 = tr[0] * 77 + tr[1] * 150 + tr[2] * 29;
@@ -798,7 +894,16 @@ export function renderPompom(width: number, audioLevel: number, dt: number): str
798
894
  else if (currentState === "peek") stateMsg = "Pompom is peeking back in... hi!";
799
895
  else if (currentState === "offscreen") stateMsg = "Pompom wandered off... they'll be back";
800
896
  else if (isTalking) stateMsg = "Pompom is listening to you speak";
801
- else stateMsg = "Pompom is vibing. Pet, feed, or play!";
897
+ else {
898
+ const w = getWeather(), tod = getTimeOfDay();
899
+ if (w === "storm") stateMsg = "Pompom hides from the thunder!";
900
+ else if (w === "rain") stateMsg = "Pompom watches the rain fall";
901
+ else if (w === "snow") stateMsg = "Pompom catches snowflakes!";
902
+ else if (tod === "dawn") stateMsg = "Pompom watches the sunrise";
903
+ else if (tod === "sunset") stateMsg = "Pompom enjoys the sunset";
904
+ else if (tod === "night") stateMsg = "Pompom stargazes under the night sky";
905
+ else stateMsg = "Pompom is vibing. Pet, feed, or play!";
906
+ }
802
907
 
803
908
  // Build status: "─ ⌥ w·Wake p·Pet ... │ State ───" exactly W visible chars
804
909
  const shortcuts: [string, string][] = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codexstar/pi-pompom",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "A 3D raymarched virtual pet companion for Pi CLI",
5
5
  "type": "module",
6
6
  "author": "codexstar69 <engazedigital@gmail.com>",