nova64 0.2.5 → 0.2.6

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 (140) hide show
  1. package/README.md +25 -8
  2. package/bin/nova64.js +165 -0
  3. package/dist/assets/console-CY_kygm3.js +14 -0
  4. package/dist/assets/console-CY_kygm3.js.map +1 -0
  5. package/dist/assets/main-l0sNRNKZ.js.map +1 -0
  6. package/dist/assets/sky/studio/nx.png +0 -0
  7. package/dist/assets/sky/studio/ny.png +0 -0
  8. package/dist/assets/sky/studio/nz.png +0 -0
  9. package/dist/assets/sky/studio/px.png +0 -0
  10. package/dist/assets/sky/studio/py.png +0 -0
  11. package/dist/assets/sky/studio/pz.png +0 -0
  12. package/dist/assets/vanilla-Dcuy32gi.js +2 -0
  13. package/dist/assets/vanilla-Dcuy32gi.js.map +1 -0
  14. package/dist/console.html +899 -0
  15. package/dist/docs/BENCHMARK.md +77 -0
  16. package/dist/docs/CHEATSHEET.md +255 -0
  17. package/dist/docs/EFFECTS_API_GUIDE.md +577 -0
  18. package/dist/docs/EFFECTS_QUICK_REFERENCE.md +331 -0
  19. package/dist/docs/FONT_CHARACTER_REFERENCE.md +219 -0
  20. package/dist/docs/FREE_GLB_ASSETS.md +330 -0
  21. package/dist/docs/FULLSCREEN_BUTTON_FEATURE.md +296 -0
  22. package/dist/docs/GAMEPAD_SUPPORT.md +348 -0
  23. package/dist/docs/GAME_IMPROVEMENTS.md +278 -0
  24. package/dist/docs/GAME_QUALITY_STATUS.md +300 -0
  25. package/dist/docs/MIGRATION_GUIDE.md +553 -0
  26. package/dist/docs/NOVA64_3D_API.md +356 -0
  27. package/dist/docs/NOVA64_API_REFERENCE.md +1406 -0
  28. package/dist/docs/NOVA64_UI_API.md +503 -0
  29. package/dist/docs/UI_SYSTEM_SUMMARY.md +445 -0
  30. package/dist/docs/VOXEL_ENGINE_GUIDE.md +662 -0
  31. package/dist/docs/VOXEL_QUICK_REFERENCE.md +386 -0
  32. package/dist/docs/api-3d.html +750 -0
  33. package/dist/docs/api-effects.html +385 -0
  34. package/dist/docs/api-improvements.md +121 -0
  35. package/dist/docs/api-skybox.html +407 -0
  36. package/dist/docs/api-sprites.html +321 -0
  37. package/dist/docs/api-voxel.html +337 -0
  38. package/dist/docs/api.html +543 -0
  39. package/dist/docs/assets.html +306 -0
  40. package/dist/docs/audio.html +340 -0
  41. package/dist/docs/blogs.html +286 -0
  42. package/dist/docs/collision.html +316 -0
  43. package/dist/docs/console.html +247 -0
  44. package/dist/docs/editor.html +297 -0
  45. package/dist/docs/font.html +247 -0
  46. package/dist/docs/framebuffer.html +247 -0
  47. package/dist/docs/fullscreen-button.html +297 -0
  48. package/dist/docs/gpu-systems.html +247 -0
  49. package/dist/docs/index.html +580 -0
  50. package/dist/docs/input.html +491 -0
  51. package/dist/docs/physics.html +311 -0
  52. package/dist/docs/screens.html +311 -0
  53. package/dist/docs/storage.html +311 -0
  54. package/dist/docs/textinput.html +332 -0
  55. package/dist/docs/ui.html +488 -0
  56. package/dist/examples/3d-advanced/code.js +695 -0
  57. package/dist/examples/adventure-comic-3d/code.js +342 -0
  58. package/dist/examples/audio-lab/code.js +150 -0
  59. package/dist/examples/boids-flocking/code.js +270 -0
  60. package/dist/examples/crystal-cathedral-3d/code.js +706 -0
  61. package/dist/examples/cyberpunk-city-3d/code.js +1383 -0
  62. package/dist/examples/demoscene/README.md +192 -0
  63. package/dist/examples/demoscene/code.js +1081 -0
  64. package/dist/examples/demoscene/meta.json +21 -0
  65. package/dist/examples/dungeon-crawler-3d/code.js +1117 -0
  66. package/dist/examples/f-zero-nova-3d/code.js +865 -0
  67. package/dist/examples/f-zero-nova-3d/code_old.js +1555 -0
  68. package/dist/examples/fps-demo-3d/code.js +744 -0
  69. package/dist/examples/game-of-life-3d/code.js +338 -0
  70. package/dist/examples/generative-art/code.js +632 -0
  71. package/dist/examples/hello-3d/code.js +325 -0
  72. package/dist/examples/hello-skybox/code.js +183 -0
  73. package/dist/examples/hello-world/code.js +19 -0
  74. package/dist/examples/input-showcase/code.js +109 -0
  75. package/dist/examples/instancing-demo/code.js +315 -0
  76. package/dist/examples/minecraft-demo/code.js +387 -0
  77. package/dist/examples/model-viewer-3d/code.js +114 -0
  78. package/dist/examples/mystical-realm-3d/code.js +1203 -0
  79. package/dist/examples/nature-explorer-3d/code.js +1318 -0
  80. package/dist/examples/particles-demo/code.js +522 -0
  81. package/dist/examples/pbr-showcase/code.js +140 -0
  82. package/dist/examples/physics-demo-3d/code.js +948 -0
  83. package/dist/examples/screen-demo/code.js +267 -0
  84. package/dist/examples/shooter-demo-3d/code.js +1286 -0
  85. package/dist/examples/space-combat-3d/IMPLEMENTATION_SUMMARY.md +109 -0
  86. package/dist/examples/space-combat-3d/README.md +135 -0
  87. package/dist/examples/space-combat-3d/code.js +1332 -0
  88. package/dist/examples/space-harrier-3d/code.js +923 -0
  89. package/dist/examples/star-fox-nova-3d/code.js +1116 -0
  90. package/dist/examples/star-fox-nova-3d/code_backup.js +410 -0
  91. package/dist/examples/star-fox-nova-3d/code_broken.js +1821 -0
  92. package/dist/examples/storage-quest/code.js +209 -0
  93. package/dist/examples/strider-demo-3d/IMPROVEMENT_OPTIONS.md +285 -0
  94. package/dist/examples/strider-demo-3d/cache-test.html +132 -0
  95. package/dist/examples/strider-demo-3d/code-fixed.js +582 -0
  96. package/dist/examples/strider-demo-3d/code-old.js +1537 -0
  97. package/dist/examples/strider-demo-3d/code.js +1462 -0
  98. package/dist/examples/strider-demo-3d/code.js.bak2 +1169 -0
  99. package/dist/examples/strider-demo-3d/fix-game.sh +53 -0
  100. package/dist/examples/super-plumber-64/README.md +128 -0
  101. package/dist/examples/super-plumber-64/code.js +1185 -0
  102. package/dist/examples/super-plumber-64/index.html +88 -0
  103. package/dist/examples/test-2d-overlay/code.js +32 -0
  104. package/dist/examples/test-font/code.js +51 -0
  105. package/dist/examples/test-minimal/code.js +21 -0
  106. package/dist/examples/ui-demo/code.js +306 -0
  107. package/dist/examples/wing-commander-space/README.md +180 -0
  108. package/dist/examples/wing-commander-space/code.js +1285 -0
  109. package/dist/examples/wizardry-3d/CHANGELOG.md +366 -0
  110. package/dist/examples/wizardry-3d/code.js +3928 -0
  111. package/dist/index.html +666 -0
  112. package/dist/os9-shell/assets/index-DIHfrTaW.css +1 -0
  113. package/dist/os9-shell/assets/index-KchE_ngx.js +483 -0
  114. package/dist/os9-shell/assets/index-KchE_ngx.js.map +1 -0
  115. package/dist/os9-shell/index.html +23 -0
  116. package/dist/os9-shell/nova-icon.svg +12 -0
  117. package/index.html +6 -1
  118. package/package.json +37 -32
  119. package/public/assets/sky/studio/nx.png +0 -0
  120. package/public/assets/sky/studio/ny.png +0 -0
  121. package/public/assets/sky/studio/nz.png +0 -0
  122. package/public/assets/sky/studio/px.png +0 -0
  123. package/public/assets/sky/studio/py.png +0 -0
  124. package/public/assets/sky/studio/pz.png +0 -0
  125. package/public/os9-shell/assets/index-KchE_ngx.js +483 -0
  126. package/public/os9-shell/assets/index-KchE_ngx.js.map +1 -0
  127. package/public/os9-shell/index.html +10 -1
  128. package/runtime/api-2d.js +301 -21
  129. package/runtime/api-3d/pbr.js +45 -1
  130. package/runtime/api-3d.js +1 -0
  131. package/runtime/api-effects.js +90 -3
  132. package/runtime/api-gameutils.js +476 -0
  133. package/runtime/api-generative.js +610 -0
  134. package/runtime/api-skybox.js +54 -0
  135. package/runtime/api-voxel.js +139 -28
  136. package/runtime/gpu-threejs.js +13 -9
  137. package/runtime/ui.js +2 -2
  138. package/src/main.js +20 -0
  139. package/public/os9-shell/assets/index-B1Uvacma.js +0 -32825
  140. package/public/os9-shell/assets/index-B1Uvacma.js.map +0 -1
@@ -0,0 +1,632 @@
1
+ // Generative Art Studio — Nova64 Fantasy Console
2
+ // 6 interactive Processing-style sketches. Press LEFT/RIGHT to switch.
3
+ // All rendering happens in draw() (framebuffer is cleared between update & draw).
4
+
5
+ let currentSketch = 0;
6
+ const SKETCH_COUNT = 6;
7
+ const sketchNames = [
8
+ 'FLOW FIELD',
9
+ 'PERLIN LANDSCAPE',
10
+ 'SPIRAL GALAXY',
11
+ 'PARTICLE GARDEN',
12
+ 'WAVE INTERFERENCE',
13
+ 'NEON GEOMETRY',
14
+ ];
15
+
16
+ let time = 0;
17
+ let particles = [];
18
+ let field = null;
19
+ let switchCooldown = 0;
20
+ let started = false;
21
+
22
+ // ─── Shared state ──────────────────────────────────────────────────────────
23
+ const W = 640;
24
+ const H = 360;
25
+
26
+ export function init() {
27
+ createSolidSkybox(0x000000);
28
+ noiseSeed(42);
29
+ _initSketch(currentSketch);
30
+ }
31
+
32
+ // ─── UPDATE: state/logic only, NO drawing ─────────────────────────────────
33
+ export function update(dt) {
34
+ if (!started) {
35
+ if (btnp(4) || btnp(5) || keyp('Space') || keyp('Enter')) {
36
+ started = true;
37
+ }
38
+ return;
39
+ }
40
+
41
+ time += dt;
42
+ switchCooldown -= dt;
43
+
44
+ // Switch sketches
45
+ if ((keyp('ArrowRight') || btnp(1)) && switchCooldown <= 0) {
46
+ currentSketch = (currentSketch + 1) % SKETCH_COUNT;
47
+ _initSketch(currentSketch);
48
+ switchCooldown = 0.3;
49
+ }
50
+ if ((keyp('ArrowLeft') || btnp(0)) && switchCooldown <= 0) {
51
+ currentSketch = (currentSketch - 1 + SKETCH_COUNT) % SKETCH_COUNT;
52
+ _initSketch(currentSketch);
53
+ switchCooldown = 0.3;
54
+ }
55
+
56
+ // Update state (physics, particles) — no drawing
57
+ switch (currentSketch) {
58
+ case 0:
59
+ _updateFlowField(dt);
60
+ break;
61
+ case 1:
62
+ _updatePerlinLandscape(dt);
63
+ break;
64
+ case 3:
65
+ _updateParticleGarden(dt);
66
+ break;
67
+ }
68
+ }
69
+
70
+ // ─── DRAW: all rendering happens here ─────────────────────────────────────
71
+ export function draw() {
72
+ if (!started) {
73
+ _drawStartScreen();
74
+ return;
75
+ }
76
+
77
+ // Draw current sketch
78
+ switch (currentSketch) {
79
+ case 0:
80
+ _drawFlowField();
81
+ break;
82
+ case 1:
83
+ _drawPerlinLandscape();
84
+ break;
85
+ case 2:
86
+ _drawSpiralGalaxy();
87
+ break;
88
+ case 3:
89
+ _drawParticleGarden();
90
+ break;
91
+ case 4:
92
+ _drawWaveInterference();
93
+ break;
94
+ case 5:
95
+ _drawNeonGeometry();
96
+ break;
97
+ }
98
+
99
+ // HUD on top
100
+ _drawHUD();
101
+ }
102
+
103
+ // ─── Start Screen ─────────────────────────────────────────────────────────
104
+ function _drawStartScreen() {
105
+ cls(rgba8(10, 8, 20));
106
+
107
+ // Animated background circles
108
+ const t = performance.now() / 1000;
109
+ for (let i = 0; i < 12; i++) {
110
+ const angle = (i / 12) * Math.PI * 2 + t * 0.3;
111
+ const r = 80 + Math.sin(t * 0.5 + i) * 30;
112
+ const x = W / 2 + Math.cos(angle) * r;
113
+ const y = H / 2 + Math.sin(angle) * r;
114
+ const hue = (i * 30 + t * 40) % 360;
115
+ ellipse(x, y, 8 + Math.sin(t + i) * 4, 8 + Math.cos(t + i) * 4, hsb(hue, 0.8, 0.6, 80), true);
116
+ }
117
+
118
+ drawGlowTextCentered(
119
+ 'GENERATIVE ART STUDIO',
120
+ W / 2,
121
+ 80,
122
+ rgba8(255, 200, 255),
123
+ rgba8(180, 80, 220)
124
+ );
125
+ drawGlowTextCentered('N O V A 6 4', W / 2, 120, rgba8(100, 200, 255), rgba8(40, 100, 180));
126
+
127
+ printCentered('6 interactive Processing-style sketches', W / 2, 170, rgba8(180, 180, 200));
128
+ printCentered(
129
+ 'Flow Fields / Perlin Landscapes / Spiral Galaxies',
130
+ W / 2,
131
+ 195,
132
+ rgba8(140, 160, 200)
133
+ );
134
+ printCentered(
135
+ 'Particle Systems / Wave Interference / Neon Geometry',
136
+ W / 2,
137
+ 215,
138
+ rgba8(140, 160, 200)
139
+ );
140
+
141
+ const pulse = Math.sin(t * 3) * 0.3 + 0.7;
142
+ const a = Math.floor(pulse * 255);
143
+ printCentered('PRESS SPACE TO BEGIN', W / 2, 280, rgba8(255, 255, 255, a));
144
+ printCentered('LEFT / RIGHT to switch sketches', W / 2, 305, rgba8(120, 120, 150));
145
+ }
146
+
147
+ // ─── HUD ──────────────────────────────────────────────────────────────────
148
+ function _drawHUD() {
149
+ const name = sketchNames[currentSketch];
150
+ drawPanel(8, 4, 200, 28, {
151
+ bgColor: rgba8(0, 0, 0, 140),
152
+ borderLight: rgba8(80, 80, 120, 100),
153
+ borderDark: rgba8(40, 40, 60, 100),
154
+ });
155
+ print(`${currentSketch + 1}/${SKETCH_COUNT} ${name}`, 16, 12, rgba8(200, 200, 255));
156
+ print('[< >] SWITCH', W - 130, H - 18, rgba8(120, 120, 150));
157
+ }
158
+
159
+ // ─── Sketch init ──────────────────────────────────────────────────────────
160
+ function _initSketch(idx) {
161
+ time = 0;
162
+ particles = [];
163
+ field = null;
164
+
165
+ switch (idx) {
166
+ case 0:
167
+ _initFlowField();
168
+ break;
169
+ case 1:
170
+ _initPerlinLandscape();
171
+ break;
172
+ case 2:
173
+ _initSpiralGalaxy();
174
+ break;
175
+ case 3:
176
+ _initParticleGarden();
177
+ break;
178
+ case 5:
179
+ _initNeonGeometry();
180
+ break;
181
+ }
182
+ }
183
+
184
+ // ════════════════════════════════════════════════════════════════════════════
185
+ // SKETCH 0: FLOW FIELD
186
+ // Particles follow Perlin noise flow vectors. Trail history drawn each frame.
187
+ // ════════════════════════════════════════════════════════════════════════════
188
+
189
+ const FLOW_COLS = 64;
190
+ const FLOW_ROWS = 36;
191
+ const FLOW_CELL = 10;
192
+ const TRAIL_LEN = 12;
193
+
194
+ function _initFlowField() {
195
+ particles = [];
196
+ for (let i = 0; i < 600; i++) {
197
+ particles.push({
198
+ x: Math.random() * W,
199
+ y: Math.random() * H,
200
+ speed: 0.5 + Math.random() * 1.5,
201
+ hue: Math.random() * 360,
202
+ life: 200 + Math.random() * 300,
203
+ trail: [],
204
+ });
205
+ }
206
+ }
207
+
208
+ function _updateFlowField(dt) {
209
+ field = flowField(FLOW_COLS, FLOW_ROWS, 0.08, time * 0.15);
210
+
211
+ for (const p of particles) {
212
+ const col = Math.floor(p.x / FLOW_CELL);
213
+ const row = Math.floor(p.y / FLOW_CELL);
214
+
215
+ if (col >= 0 && col < FLOW_COLS && row >= 0 && row < FLOW_ROWS) {
216
+ const angle = field[row * FLOW_COLS + col];
217
+ // Store trail point before moving
218
+ p.trail.push({ x: p.x, y: p.y });
219
+ if (p.trail.length > TRAIL_LEN) p.trail.shift();
220
+
221
+ p.x += Math.cos(angle) * p.speed * 60 * dt;
222
+ p.y += Math.sin(angle) * p.speed * 60 * dt;
223
+ p.hue = (p.hue + 15 * dt) % 360;
224
+ p.life -= dt * 60;
225
+ }
226
+
227
+ if (p.x < 0 || p.x > W || p.y < 0 || p.y > H || p.life <= 0) {
228
+ p.x = Math.random() * W;
229
+ p.y = Math.random() * H;
230
+ p.life = 200 + Math.random() * 300;
231
+ p.hue = Math.random() * 360;
232
+ p.trail = [];
233
+ }
234
+ }
235
+ }
236
+
237
+ function _drawFlowField() {
238
+ cls(rgba8(5, 3, 12));
239
+
240
+ for (const p of particles) {
241
+ const trail = p.trail;
242
+ for (let j = 1; j < trail.length; j++) {
243
+ const t = j / trail.length;
244
+ const alpha = Math.floor(t * 200);
245
+ const c = hsb(p.hue, 0.85, 0.9, Math.max(15, alpha));
246
+ line(
247
+ Math.round(trail[j - 1].x),
248
+ Math.round(trail[j - 1].y),
249
+ Math.round(trail[j].x),
250
+ Math.round(trail[j].y),
251
+ c
252
+ );
253
+ }
254
+ // Head
255
+ if (trail.length > 0) {
256
+ const last = trail[trail.length - 1];
257
+ const c = hsb(p.hue, 0.9, 1.0, 255);
258
+ line(Math.round(last.x), Math.round(last.y), Math.round(p.x), Math.round(p.y), c);
259
+ }
260
+ }
261
+ }
262
+
263
+ // ════════════════════════════════════════════════════════════════════════════
264
+ // SKETCH 1: PERLIN LANDSCAPE
265
+ // ════════════════════════════════════════════════════════════════════════════
266
+
267
+ let landscapeOffset = 0;
268
+
269
+ function _initPerlinLandscape() {
270
+ landscapeOffset = 0;
271
+ }
272
+
273
+ function _updatePerlinLandscape(dt) {
274
+ landscapeOffset += dt * 30;
275
+ }
276
+
277
+ function _drawPerlinLandscape() {
278
+ cls(rgba8(8, 6, 18));
279
+
280
+ const layers = [
281
+ { scale: 0.005, amp: 60, base: 240, color1: [30, 20, 60], color2: [60, 30, 90], speed: 0.2 },
282
+ { scale: 0.008, amp: 80, base: 260, color1: [20, 40, 60], color2: [40, 80, 120], speed: 0.5 },
283
+ { scale: 0.012, amp: 100, base: 280, color1: [30, 60, 40], color2: [60, 140, 80], speed: 0.8 },
284
+ { scale: 0.02, amp: 70, base: 300, color1: [40, 30, 20], color2: [100, 70, 40], speed: 1.0 },
285
+ ];
286
+
287
+ // Stars
288
+ noiseSeed(7);
289
+ for (let i = 0; i < 80; i++) {
290
+ const sx = (noise(i * 100) * W * 1.5) % W;
291
+ const sy = noise(i * 200) * 180;
292
+ const brightness = 100 + Math.floor(noise(i * 300 + time * 0.5) * 155);
293
+ pset(Math.round(sx), Math.round(sy), rgba8(brightness, brightness, brightness + 40));
294
+ }
295
+ noiseSeed(42);
296
+
297
+ // Moon
298
+ ellipse(500, 60, 25, 25, rgba8(240, 230, 200, 180), true);
299
+ ellipse(506, 57, 22, 22, rgba8(8, 6, 18), true);
300
+
301
+ // Mountain layers
302
+ for (let layerIdx = 0; layerIdx < layers.length; layerIdx++) {
303
+ const L = layers[layerIdx];
304
+ const off = landscapeOffset * L.speed;
305
+
306
+ for (let x = 0; x < W; x++) {
307
+ const n = noise((x + off) * L.scale, layerIdx * 100);
308
+ const h = L.base - n * L.amp;
309
+
310
+ for (let y = Math.floor(h); y < H; y++) {
311
+ const t = Math.min(1, (y - h) / L.amp);
312
+ const r = L.color1[0] + (L.color2[0] - L.color1[0]) * t;
313
+ const g = L.color1[1] + (L.color2[1] - L.color1[1]) * t;
314
+ const b = L.color1[2] + (L.color2[2] - L.color1[2]) * t;
315
+ pset(x, y, rgba8(r, g, b));
316
+ }
317
+ }
318
+ }
319
+
320
+ // Water reflection
321
+ for (let x = 0; x < W; x++) {
322
+ for (let y = H - 30; y < H; y++) {
323
+ const wave = Math.sin(x * 0.05 + time * 2) * 3;
324
+ const alpha = 80 - (y - (H - 30)) * 2;
325
+ if (alpha > 0) pset(x, y, rgba8(20, 40, 80, alpha));
326
+ }
327
+ }
328
+ }
329
+
330
+ // ════════════════════════════════════════════════════════════════════════════
331
+ // SKETCH 2: SPIRAL GALAXY
332
+ // ════════════════════════════════════════════════════════════════════════════
333
+
334
+ let galaxyStars = [];
335
+
336
+ function _initSpiralGalaxy() {
337
+ galaxyStars = [];
338
+ const cx = W / 2,
339
+ cy = H / 2;
340
+
341
+ for (let i = 0; i < 2000; i++) {
342
+ const arm = Math.floor(Math.random() * 4);
343
+ const dist = Math.random() * 160;
344
+ const baseAngle = (arm / 4) * Math.PI * 2;
345
+ const spiralAngle = baseAngle + dist * 0.04;
346
+ const spread = (Math.random() - 0.5) * (dist * 0.15);
347
+
348
+ galaxyStars.push({
349
+ dist,
350
+ angle: spiralAngle,
351
+ spread,
352
+ arm,
353
+ hue: 200 + arm * 40 + Math.random() * 30,
354
+ brightness: 0.3 + Math.random() * 0.7,
355
+ size: Math.random() < 0.05 ? 2 : 1,
356
+ });
357
+ }
358
+ }
359
+
360
+ function _drawSpiralGalaxy() {
361
+ cls(rgba8(4, 2, 10));
362
+
363
+ const cx = W / 2,
364
+ cy = H / 2;
365
+ const rotSpeed = time * 0.15;
366
+
367
+ // Core glow
368
+ drawRadialGradient(cx, cy, 30, rgba8(255, 220, 180, 60), rgba8(255, 220, 180, 0));
369
+ drawRadialGradient(cx, cy, 12, rgba8(255, 240, 220, 120), rgba8(255, 200, 150, 0));
370
+
371
+ for (const star of galaxyStars) {
372
+ const angle = star.angle + rotSpeed;
373
+ const wobble = Math.sin(time * 0.5 + star.dist * 0.1) * 2;
374
+ const x = cx + Math.cos(angle) * star.dist + Math.sin(angle) * star.spread + wobble;
375
+ const y = cy + Math.sin(angle) * star.dist * 0.55 + Math.cos(angle) * star.spread * 0.55;
376
+
377
+ const alpha = Math.floor(star.brightness * 255 * (1 - star.dist / 200));
378
+ if (alpha < 10) continue;
379
+ const c = hsb(star.hue, 0.6, star.brightness, Math.min(255, alpha));
380
+
381
+ if (star.size > 1) {
382
+ pset(Math.round(x), Math.round(y), c);
383
+ pset(Math.round(x + 1), Math.round(y), c);
384
+ pset(Math.round(x), Math.round(y + 1), c);
385
+ } else {
386
+ pset(Math.round(x), Math.round(y), c);
387
+ }
388
+ }
389
+
390
+ // Nebula clouds
391
+ for (let i = 0; i < 5; i++) {
392
+ const na = (i / 5) * Math.PI * 2 + time * 0.08;
393
+ const nd = 80 + Math.sin(time * 0.3 + i) * 20;
394
+ const nx = cx + Math.cos(na) * nd;
395
+ const ny = cy + Math.sin(na) * nd * 0.55;
396
+ const hue = (200 + i * 60 + time * 10) % 360;
397
+ drawRadialGradient(nx, ny, 25, hsb(hue, 0.7, 0.4, 25), hsb(hue, 0.3, 0.1, 0));
398
+ }
399
+ }
400
+
401
+ // ════════════════════════════════════════════════════════════════════════════
402
+ // SKETCH 3: PARTICLE GARDEN
403
+ // ════════════════════════════════════════════════════════════════════════════
404
+
405
+ let emitters = [];
406
+
407
+ function _initParticleGarden() {
408
+ particles = [];
409
+ emitters = [
410
+ { x: W * 0.2, y: H * 0.7, hue: 120, type: 'fountain' },
411
+ { x: W * 0.5, y: H * 0.5, hue: 280, type: 'spiral' },
412
+ { x: W * 0.8, y: H * 0.7, hue: 30, type: 'fountain' },
413
+ { x: W * 0.35, y: H * 0.3, hue: 200, type: 'burst' },
414
+ { x: W * 0.65, y: H * 0.3, hue: 340, type: 'burst' },
415
+ ];
416
+ }
417
+
418
+ function _updateParticleGarden(dt) {
419
+ // Spawn from emitters
420
+ for (const em of emitters) {
421
+ if (particles.length < 1500) {
422
+ for (let i = 0; i < 3; i++) {
423
+ const p = {
424
+ x: em.x,
425
+ y: em.y,
426
+ life: 120 + Math.random() * 180,
427
+ maxLife: 0,
428
+ hue: em.hue + (Math.random() - 0.5) * 40,
429
+ };
430
+ p.maxLife = p.life;
431
+
432
+ if (em.type === 'fountain') {
433
+ p.vx = (Math.random() - 0.5) * 40;
434
+ p.vy = -80 - Math.random() * 60;
435
+ p.gravity = 50;
436
+ } else if (em.type === 'spiral') {
437
+ const a = Math.random() * Math.PI * 2;
438
+ const spd = 20 + Math.random() * 30;
439
+ p.vx = Math.cos(a) * spd;
440
+ p.vy = Math.sin(a) * spd;
441
+ p.gravity = 0;
442
+ p.spiral = 2 + Math.random();
443
+ } else {
444
+ const a = Math.random() * Math.PI * 2;
445
+ const spd = 30 + Math.random() * 50;
446
+ p.vx = Math.cos(a) * spd;
447
+ p.vy = Math.sin(a) * spd;
448
+ p.gravity = 15;
449
+ }
450
+ particles.push(p);
451
+ }
452
+ }
453
+ }
454
+
455
+ // Update physics
456
+ for (let i = particles.length - 1; i >= 0; i--) {
457
+ const p = particles[i];
458
+ p.life -= dt * 60;
459
+ if (p.life <= 0) {
460
+ particles.splice(i, 1);
461
+ continue;
462
+ }
463
+
464
+ if (p.spiral) {
465
+ const angle = Math.atan2(p.vy, p.vx) + p.spiral * dt;
466
+ const speed = Math.sqrt(p.vx * p.vx + p.vy * p.vy);
467
+ p.vx = Math.cos(angle) * speed;
468
+ p.vy = Math.sin(angle) * speed;
469
+ }
470
+ if (p.gravity) p.vy += p.gravity * dt;
471
+ p.x += p.vx * dt;
472
+ p.y += p.vy * dt;
473
+ }
474
+ }
475
+
476
+ function _drawParticleGarden() {
477
+ cls(rgba8(5, 8, 5));
478
+
479
+ for (const p of particles) {
480
+ const t = p.life / p.maxLife;
481
+ const alpha = Math.floor(t * 220);
482
+ const brightness = 0.5 + t * 0.5;
483
+ const c = hsb(p.hue, 0.8, brightness, Math.max(10, alpha));
484
+ pset(Math.round(p.x), Math.round(p.y), c);
485
+
486
+ if (t > 0.7) {
487
+ pset(
488
+ Math.round(p.x + 1),
489
+ Math.round(p.y),
490
+ hsb(p.hue, 0.5, brightness, Math.floor(alpha * 0.3))
491
+ );
492
+ pset(
493
+ Math.round(p.x - 1),
494
+ Math.round(p.y),
495
+ hsb(p.hue, 0.5, brightness, Math.floor(alpha * 0.3))
496
+ );
497
+ }
498
+ }
499
+
500
+ // Emitter markers
501
+ for (const em of emitters) {
502
+ ellipse(Math.round(em.x), Math.round(em.y), 4, 4, hsb(em.hue, 0.6, 0.4, 100), true);
503
+ }
504
+
505
+ line(0, Math.floor(H * 0.75), W, Math.floor(H * 0.75), rgba8(30, 50, 30, 100));
506
+ }
507
+
508
+ // ════════════════════════════════════════════════════════════════════════════
509
+ // SKETCH 4: WAVE INTERFERENCE
510
+ // ════════════════════════════════════════════════════════════════════════════
511
+
512
+ function _drawWaveInterference() {
513
+ cls(rgba8(0, 0, 0));
514
+
515
+ const sources = [
516
+ { x: W * 0.3, y: H * 0.4, freq: 0.04, phase: time * 3 },
517
+ { x: W * 0.7, y: H * 0.4, freq: 0.035, phase: time * 2.5 + 1 },
518
+ { x: W * 0.5, y: H * 0.7, freq: 0.045, phase: time * 3.5 + 2 },
519
+ ];
520
+
521
+ // Sample every 2 pixels for performance
522
+ for (let y = 0; y < H; y += 2) {
523
+ for (let x = 0; x < W; x += 2) {
524
+ let val = 0;
525
+ for (const src of sources) {
526
+ const dx = x - src.x;
527
+ const dy = y - src.y;
528
+ const d = Math.sqrt(dx * dx + dy * dy);
529
+ val += Math.sin(d * src.freq - src.phase);
530
+ }
531
+ val = (val + 3) / 6;
532
+
533
+ const hue = val * 270;
534
+ const brightness = 0.3 + val * 0.6;
535
+ const c = hsb(hue, 0.9, brightness);
536
+
537
+ pset(x, y, c);
538
+ pset(x + 1, y, c);
539
+ pset(x, y + 1, c);
540
+ pset(x + 1, y + 1, c);
541
+ }
542
+ }
543
+
544
+ for (const src of sources) {
545
+ ellipse(Math.round(src.x), Math.round(src.y), 4, 4, rgba8(255, 255, 255, 200), true);
546
+ ellipse(Math.round(src.x), Math.round(src.y), 8, 8, rgba8(255, 255, 255, 80), false);
547
+ }
548
+ }
549
+
550
+ // ════════════════════════════════════════════════════════════════════════════
551
+ // SKETCH 5: NEON GEOMETRY
552
+ // ════════════════════════════════════════════════════════════════════════════
553
+
554
+ let geoShapes = [];
555
+
556
+ function _initNeonGeometry() {
557
+ geoShapes = [];
558
+ for (let i = 0; i < 8; i++) {
559
+ geoShapes.push({
560
+ cx: W * 0.2 + Math.random() * W * 0.6,
561
+ cy: H * 0.2 + Math.random() * H * 0.6,
562
+ type: ['ellipse', 'bezier', 'spiral', 'arc'][Math.floor(Math.random() * 4)],
563
+ size: 20 + Math.random() * 60,
564
+ hue: Math.random() * 360,
565
+ rotSpeed: (Math.random() - 0.5) * 2,
566
+ pulseSpeed: 1 + Math.random() * 2,
567
+ });
568
+ }
569
+ }
570
+
571
+ function _drawNeonGeometry() {
572
+ cls(rgba8(4, 2, 8));
573
+
574
+ // Connecting lines first (behind shapes)
575
+ for (let i = 0; i < geoShapes.length; i++) {
576
+ for (let j = i + 1; j < geoShapes.length; j++) {
577
+ const a = geoShapes[i],
578
+ b = geoShapes[j];
579
+ const d = Math.sqrt((a.cx - b.cx) ** 2 + (a.cy - b.cy) ** 2);
580
+ if (d < 200) {
581
+ const alpha = Math.floor((1 - d / 200) * 40);
582
+ line(
583
+ Math.round(a.cx),
584
+ Math.round(a.cy),
585
+ Math.round(b.cx),
586
+ Math.round(b.cy),
587
+ rgba8(100, 80, 160, alpha)
588
+ );
589
+ }
590
+ }
591
+ }
592
+
593
+ for (const shape of geoShapes) {
594
+ const pulsing = 0.6 + Math.sin(time * shape.pulseSpeed) * 0.4;
595
+ const s = shape.size * pulsing;
596
+ const hue = (shape.hue + time * 20) % 360;
597
+ const c = hsb(hue, 0.9, 0.9, 200);
598
+ const glow = hsb(hue, 0.6, 0.5, 60);
599
+
600
+ pushMatrix();
601
+ translate(shape.cx, shape.cy);
602
+ rotate(time * shape.rotSpeed);
603
+
604
+ switch (shape.type) {
605
+ case 'ellipse':
606
+ ellipse(0, 0, s, s * 0.6, glow, true);
607
+ ellipse(0, 0, s, s * 0.6, c, false);
608
+ break;
609
+ case 'bezier':
610
+ bezier(-s, 0, -s * 0.5, -s, s * 0.5, s, s, 0, c, 40);
611
+ bezier(-s, 0, -s * 0.5, s, s * 0.5, -s, s, 0, glow, 40);
612
+ break;
613
+ case 'spiral':
614
+ for (let i = 0; i < 200; i++) {
615
+ const t = i / 200;
616
+ const a = t * Math.PI * 6;
617
+ const r = t * s;
618
+ const px = Math.cos(a) * r;
619
+ const py = Math.sin(a) * r;
620
+ const sc = hsb((hue + t * 120) % 360, 0.9, 0.8, Math.floor(200 * (1 - t * 0.5)));
621
+ pset(Math.round(px), Math.round(py), sc);
622
+ }
623
+ break;
624
+ case 'arc':
625
+ arc(0, 0, s, s * 0.7, time, time + Math.PI * 1.5, c, false);
626
+ arc(0, 0, s * 0.6, s * 0.4, -time, -time + Math.PI, glow, false);
627
+ break;
628
+ }
629
+
630
+ popMatrix();
631
+ }
632
+ }