@guinetik/gcanvas 1.0.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 (349) hide show
  1. package/.github/workflows/release.yaml +70 -0
  2. package/.jshintrc +4 -0
  3. package/.vscode/settings.json +22 -0
  4. package/CLAUDE.md +310 -0
  5. package/blackhole.jpg +0 -0
  6. package/demo.png +0 -0
  7. package/demos/CNAME +1 -0
  8. package/demos/animations.html +31 -0
  9. package/demos/basic.html +38 -0
  10. package/demos/baskara.html +31 -0
  11. package/demos/bezier.html +35 -0
  12. package/demos/beziersignature.html +29 -0
  13. package/demos/blackhole.html +28 -0
  14. package/demos/blob.html +35 -0
  15. package/demos/demos.css +289 -0
  16. package/demos/easing.html +28 -0
  17. package/demos/events.html +195 -0
  18. package/demos/fluent.html +647 -0
  19. package/demos/fractals.html +36 -0
  20. package/demos/genart.html +26 -0
  21. package/demos/gendream.html +26 -0
  22. package/demos/group.html +36 -0
  23. package/demos/home.html +587 -0
  24. package/demos/index.html +364 -0
  25. package/demos/isometric.html +34 -0
  26. package/demos/js/animations.js +452 -0
  27. package/demos/js/basic.js +204 -0
  28. package/demos/js/baskara.js +751 -0
  29. package/demos/js/bezier.js +692 -0
  30. package/demos/js/beziersignature.js +241 -0
  31. package/demos/js/blackhole/accretiondisk.obj.js +379 -0
  32. package/demos/js/blackhole/blackhole.obj.js +318 -0
  33. package/demos/js/blackhole/index.js +409 -0
  34. package/demos/js/blackhole/particle.js +56 -0
  35. package/demos/js/blackhole/starfield.obj.js +218 -0
  36. package/demos/js/blob.js +2263 -0
  37. package/demos/js/easing.js +477 -0
  38. package/demos/js/fluent.js +183 -0
  39. package/demos/js/fractals.js +931 -0
  40. package/demos/js/fractalworker.js +93 -0
  41. package/demos/js/genart.js +268 -0
  42. package/demos/js/gendream.js +209 -0
  43. package/demos/js/group.js +140 -0
  44. package/demos/js/info-toggle.js +25 -0
  45. package/demos/js/isometric.js +863 -0
  46. package/demos/js/kerr.js +1556 -0
  47. package/demos/js/lavalamp.js +590 -0
  48. package/demos/js/layout.js +354 -0
  49. package/demos/js/mondrian.js +285 -0
  50. package/demos/js/opacity.js +275 -0
  51. package/demos/js/painter.js +484 -0
  52. package/demos/js/particles-showcase.js +514 -0
  53. package/demos/js/particles.js +299 -0
  54. package/demos/js/patterns.js +397 -0
  55. package/demos/js/penrose/artifact.js +69 -0
  56. package/demos/js/penrose/blackhole.js +121 -0
  57. package/demos/js/penrose/constants.js +73 -0
  58. package/demos/js/penrose/game.js +943 -0
  59. package/demos/js/penrose/lore.js +278 -0
  60. package/demos/js/penrose/penrosescene.js +892 -0
  61. package/demos/js/penrose/ship.js +216 -0
  62. package/demos/js/penrose/sounds.js +211 -0
  63. package/demos/js/penrose/voidparticle.js +55 -0
  64. package/demos/js/penrose/voidscene.js +258 -0
  65. package/demos/js/penrose/voidship.js +144 -0
  66. package/demos/js/penrose/wormhole.js +46 -0
  67. package/demos/js/pipeline.js +555 -0
  68. package/demos/js/scene.js +304 -0
  69. package/demos/js/scenes.js +320 -0
  70. package/demos/js/schrodinger.js +410 -0
  71. package/demos/js/schwarzschild.js +1023 -0
  72. package/demos/js/shapes.js +628 -0
  73. package/demos/js/space/alien.js +171 -0
  74. package/demos/js/space/boom.js +98 -0
  75. package/demos/js/space/boss.js +353 -0
  76. package/demos/js/space/buff.js +73 -0
  77. package/demos/js/space/bullet.js +102 -0
  78. package/demos/js/space/constants.js +85 -0
  79. package/demos/js/space/game.js +1884 -0
  80. package/demos/js/space/hud.js +112 -0
  81. package/demos/js/space/laserbeam.js +179 -0
  82. package/demos/js/space/lightning.js +277 -0
  83. package/demos/js/space/minion.js +192 -0
  84. package/demos/js/space/missile.js +212 -0
  85. package/demos/js/space/player.js +430 -0
  86. package/demos/js/space/powerup.js +90 -0
  87. package/demos/js/space/starfield.js +58 -0
  88. package/demos/js/space/starpower.js +90 -0
  89. package/demos/js/spacetime.js +559 -0
  90. package/demos/js/svgtween.js +204 -0
  91. package/demos/js/tde/accretiondisk.js +418 -0
  92. package/demos/js/tde/blackhole.js +219 -0
  93. package/demos/js/tde/blackholescene.js +209 -0
  94. package/demos/js/tde/config.js +59 -0
  95. package/demos/js/tde/index.js +695 -0
  96. package/demos/js/tde/jets.js +290 -0
  97. package/demos/js/tde/lensedstarfield.js +147 -0
  98. package/demos/js/tde/tdestar.js +317 -0
  99. package/demos/js/tde/tidalstream.js +356 -0
  100. package/demos/js/tde_old/blackhole.obj.js +354 -0
  101. package/demos/js/tde_old/debris.obj.js +791 -0
  102. package/demos/js/tde_old/flare.obj.js +239 -0
  103. package/demos/js/tde_old/index.js +448 -0
  104. package/demos/js/tde_old/star.obj.js +812 -0
  105. package/demos/js/tiles.js +312 -0
  106. package/demos/js/tweendemo.js +79 -0
  107. package/demos/js/visibility.js +102 -0
  108. package/demos/kerr.html +28 -0
  109. package/demos/lavalamp.html +27 -0
  110. package/demos/layouts.html +37 -0
  111. package/demos/logo.svg +4 -0
  112. package/demos/loop.html +84 -0
  113. package/demos/mondrian.html +32 -0
  114. package/demos/og_image.png +0 -0
  115. package/demos/opacity.html +36 -0
  116. package/demos/painter.html +39 -0
  117. package/demos/particles-showcase.html +28 -0
  118. package/demos/particles.html +24 -0
  119. package/demos/patterns.html +33 -0
  120. package/demos/penrose-game.html +31 -0
  121. package/demos/pipeline.html +737 -0
  122. package/demos/scene.html +33 -0
  123. package/demos/scenes.html +96 -0
  124. package/demos/schrodinger.html +27 -0
  125. package/demos/schwarzschild.html +27 -0
  126. package/demos/shapes.html +16 -0
  127. package/demos/space.html +85 -0
  128. package/demos/spacetime.html +27 -0
  129. package/demos/svgtween.html +29 -0
  130. package/demos/tde.html +28 -0
  131. package/demos/tiles.html +28 -0
  132. package/demos/transforms.html +400 -0
  133. package/demos/tween.html +45 -0
  134. package/demos/visibility.html +33 -0
  135. package/disk_example.png +0 -0
  136. package/docs/README.md +222 -0
  137. package/docs/concepts/architecture-overview.md +204 -0
  138. package/docs/concepts/lifecycle.md +255 -0
  139. package/docs/concepts/rendering-pipeline.md +279 -0
  140. package/docs/concepts/tde-zorder.md +106 -0
  141. package/docs/concepts/two-layer-architecture.md +229 -0
  142. package/docs/getting-started/first-game.md +354 -0
  143. package/docs/getting-started/hello-world.md +269 -0
  144. package/docs/getting-started/installation.md +157 -0
  145. package/docs/modules/collision/README.md +453 -0
  146. package/docs/modules/fluent/README.md +1075 -0
  147. package/docs/modules/game/README.md +303 -0
  148. package/docs/modules/isometric-camera.md +210 -0
  149. package/docs/modules/isometric.md +275 -0
  150. package/docs/modules/painter/README.md +328 -0
  151. package/docs/modules/particle/README.md +559 -0
  152. package/docs/modules/shapes/README.md +221 -0
  153. package/docs/modules/shapes/base/euclidian.md +123 -0
  154. package/docs/modules/shapes/base/geometry2d.md +204 -0
  155. package/docs/modules/shapes/base/renderable.md +215 -0
  156. package/docs/modules/shapes/base/shape.md +262 -0
  157. package/docs/modules/shapes/base/transformable.md +243 -0
  158. package/docs/modules/shapes/hierarchy.md +218 -0
  159. package/docs/modules/state/README.md +577 -0
  160. package/docs/modules/util/README.md +99 -0
  161. package/docs/modules/util/camera3d.md +412 -0
  162. package/docs/modules/util/scene3d.md +395 -0
  163. package/index.html +17 -0
  164. package/jsdoc.json +50 -0
  165. package/package.json +55 -0
  166. package/readme.md +599 -0
  167. package/scripts/build-demo.js +69 -0
  168. package/scripts/bundle4llm.js +276 -0
  169. package/scripts/clearconsole.js +48 -0
  170. package/src/collision/collision-system.js +332 -0
  171. package/src/collision/collision.js +303 -0
  172. package/src/collision/index.js +10 -0
  173. package/src/fluent/fluent-game.js +430 -0
  174. package/src/fluent/fluent-go.js +1060 -0
  175. package/src/fluent/fluent-layer.js +152 -0
  176. package/src/fluent/fluent-scene.js +291 -0
  177. package/src/fluent/index.js +98 -0
  178. package/src/fluent/sketch.js +380 -0
  179. package/src/game/game.js +467 -0
  180. package/src/game/index.js +49 -0
  181. package/src/game/objects/go.js +220 -0
  182. package/src/game/objects/imagego.js +30 -0
  183. package/src/game/objects/index.js +54 -0
  184. package/src/game/objects/isometric-scene.js +260 -0
  185. package/src/game/objects/layoutscene.js +549 -0
  186. package/src/game/objects/scene.js +175 -0
  187. package/src/game/objects/scene3d.js +118 -0
  188. package/src/game/objects/text.js +221 -0
  189. package/src/game/objects/wrapper.js +232 -0
  190. package/src/game/pipeline.js +243 -0
  191. package/src/game/ui/button.js +396 -0
  192. package/src/game/ui/cursor.js +93 -0
  193. package/src/game/ui/fps.js +91 -0
  194. package/src/game/ui/index.js +5 -0
  195. package/src/game/ui/togglebutton.js +93 -0
  196. package/src/game/ui/tooltip.js +249 -0
  197. package/src/index.js +25 -0
  198. package/src/io/events.js +20 -0
  199. package/src/io/index.js +86 -0
  200. package/src/io/input.js +70 -0
  201. package/src/io/keys.js +152 -0
  202. package/src/io/mouse.js +61 -0
  203. package/src/io/touch.js +39 -0
  204. package/src/logger/debugtab.js +138 -0
  205. package/src/logger/index.js +3 -0
  206. package/src/logger/loggable.js +47 -0
  207. package/src/logger/logger.js +113 -0
  208. package/src/math/complex.js +37 -0
  209. package/src/math/constants.js +1 -0
  210. package/src/math/fractal.js +1271 -0
  211. package/src/math/gr.js +201 -0
  212. package/src/math/heat.js +202 -0
  213. package/src/math/index.js +12 -0
  214. package/src/math/noise.js +433 -0
  215. package/src/math/orbital.js +191 -0
  216. package/src/math/patterns.js +1339 -0
  217. package/src/math/penrose.js +259 -0
  218. package/src/math/quantum.js +115 -0
  219. package/src/math/random.js +195 -0
  220. package/src/math/tensor.js +1009 -0
  221. package/src/mixins/anchor.js +131 -0
  222. package/src/mixins/draggable.js +72 -0
  223. package/src/mixins/index.js +2 -0
  224. package/src/motion/bezier.js +132 -0
  225. package/src/motion/bounce.js +58 -0
  226. package/src/motion/easing.js +349 -0
  227. package/src/motion/float.js +130 -0
  228. package/src/motion/follow.js +125 -0
  229. package/src/motion/hop.js +52 -0
  230. package/src/motion/index.js +82 -0
  231. package/src/motion/motion.js +1124 -0
  232. package/src/motion/orbit.js +49 -0
  233. package/src/motion/oscillate.js +39 -0
  234. package/src/motion/parabolic.js +141 -0
  235. package/src/motion/patrol.js +147 -0
  236. package/src/motion/pendulum.js +48 -0
  237. package/src/motion/pulse.js +88 -0
  238. package/src/motion/shake.js +83 -0
  239. package/src/motion/spiral.js +144 -0
  240. package/src/motion/spring.js +150 -0
  241. package/src/motion/swing.js +47 -0
  242. package/src/motion/tween.js +92 -0
  243. package/src/motion/tweenetik.js +139 -0
  244. package/src/motion/waypoint.js +210 -0
  245. package/src/painter/index.js +8 -0
  246. package/src/painter/painter.colors.js +331 -0
  247. package/src/painter/painter.effects.js +230 -0
  248. package/src/painter/painter.img.js +229 -0
  249. package/src/painter/painter.js +295 -0
  250. package/src/painter/painter.lines.js +189 -0
  251. package/src/painter/painter.opacity.js +41 -0
  252. package/src/painter/painter.shapes.js +277 -0
  253. package/src/painter/painter.text.js +273 -0
  254. package/src/particle/emitter.js +124 -0
  255. package/src/particle/index.js +11 -0
  256. package/src/particle/particle-system.js +322 -0
  257. package/src/particle/particle.js +71 -0
  258. package/src/particle/updaters.js +170 -0
  259. package/src/shapes/arc.js +43 -0
  260. package/src/shapes/arrow.js +33 -0
  261. package/src/shapes/bezier.js +42 -0
  262. package/src/shapes/circle.js +62 -0
  263. package/src/shapes/clouds.js +56 -0
  264. package/src/shapes/cone.js +219 -0
  265. package/src/shapes/cross.js +70 -0
  266. package/src/shapes/cube.js +244 -0
  267. package/src/shapes/cylinder.js +254 -0
  268. package/src/shapes/diamond.js +48 -0
  269. package/src/shapes/euclidian.js +111 -0
  270. package/src/shapes/figure.js +115 -0
  271. package/src/shapes/geometry.js +220 -0
  272. package/src/shapes/group.js +375 -0
  273. package/src/shapes/heart.js +42 -0
  274. package/src/shapes/hexagon.js +26 -0
  275. package/src/shapes/image.js +192 -0
  276. package/src/shapes/index.js +111 -0
  277. package/src/shapes/line.js +29 -0
  278. package/src/shapes/pattern.js +90 -0
  279. package/src/shapes/pin.js +44 -0
  280. package/src/shapes/poly.js +31 -0
  281. package/src/shapes/prism.js +226 -0
  282. package/src/shapes/rect.js +35 -0
  283. package/src/shapes/renderable.js +333 -0
  284. package/src/shapes/ring.js +26 -0
  285. package/src/shapes/roundrect.js +95 -0
  286. package/src/shapes/shape.js +117 -0
  287. package/src/shapes/slice.js +26 -0
  288. package/src/shapes/sphere.js +314 -0
  289. package/src/shapes/sphere3d.js +537 -0
  290. package/src/shapes/square.js +15 -0
  291. package/src/shapes/star.js +99 -0
  292. package/src/shapes/svg.js +408 -0
  293. package/src/shapes/text.js +553 -0
  294. package/src/shapes/traceable.js +83 -0
  295. package/src/shapes/transform.js +357 -0
  296. package/src/shapes/transformable.js +172 -0
  297. package/src/shapes/triangle.js +26 -0
  298. package/src/sound/index.js +17 -0
  299. package/src/sound/sound.js +473 -0
  300. package/src/sound/synth.analyzer.js +149 -0
  301. package/src/sound/synth.effects.js +207 -0
  302. package/src/sound/synth.envelope.js +59 -0
  303. package/src/sound/synth.js +229 -0
  304. package/src/sound/synth.musical.js +160 -0
  305. package/src/sound/synth.noise.js +85 -0
  306. package/src/sound/synth.oscillators.js +293 -0
  307. package/src/state/index.js +10 -0
  308. package/src/state/state-machine.js +371 -0
  309. package/src/util/camera3d.js +438 -0
  310. package/src/util/index.js +6 -0
  311. package/src/util/isometric-camera.js +235 -0
  312. package/src/util/layout.js +317 -0
  313. package/src/util/position.js +147 -0
  314. package/src/util/tasks.js +47 -0
  315. package/src/util/zindex.js +287 -0
  316. package/src/webgl/index.js +9 -0
  317. package/src/webgl/shaders/sphere-shaders.js +994 -0
  318. package/src/webgl/webgl-renderer.js +388 -0
  319. package/tde.png +0 -0
  320. package/test/math/orbital.test.js +61 -0
  321. package/test/math/tensor.test.js +114 -0
  322. package/test/particle/emitter.test.js +204 -0
  323. package/test/particle/particle-system.test.js +310 -0
  324. package/test/particle/particle.test.js +116 -0
  325. package/test/particle/updaters.test.js +386 -0
  326. package/test/setup.js +120 -0
  327. package/test/shapes/euclidian.test.js +44 -0
  328. package/test/shapes/geometry.test.js +86 -0
  329. package/test/shapes/group.test.js +86 -0
  330. package/test/shapes/rectangle.test.js +64 -0
  331. package/test/shapes/transform.test.js +379 -0
  332. package/test/util/camera3d.test.js +428 -0
  333. package/test/util/scene3d.test.js +352 -0
  334. package/types/collision.d.ts +249 -0
  335. package/types/common.d.ts +155 -0
  336. package/types/game.d.ts +497 -0
  337. package/types/index.d.ts +309 -0
  338. package/types/io.d.ts +188 -0
  339. package/types/logger.d.ts +127 -0
  340. package/types/math.d.ts +268 -0
  341. package/types/mixins.d.ts +92 -0
  342. package/types/motion.d.ts +678 -0
  343. package/types/painter.d.ts +378 -0
  344. package/types/shapes.d.ts +864 -0
  345. package/types/sound.d.ts +672 -0
  346. package/types/state.d.ts +251 -0
  347. package/types/util.d.ts +253 -0
  348. package/vite.config.js +50 -0
  349. package/vitest.config.js +13 -0
@@ -0,0 +1,258 @@
1
+ /**
2
+ * VoidScene - Inside the Black Hole
3
+ *
4
+ * The void dimension where you end up after hitting a singularity
5
+ * with an artifact. Collect purple void essence to escape.
6
+ *
7
+ * @extends Scene
8
+ */
9
+
10
+ import { Collision, Painter, Scene } from "../../../src/index.js";
11
+ import { CONFIG } from "./constants.js";
12
+ import { PenroseSounds } from "./sounds.js";
13
+ import { VoidParticle } from "./voidparticle.js";
14
+ import { VoidShip } from "./voidship.js";
15
+
16
+ export class VoidScene extends Scene {
17
+ constructor(game) {
18
+ super(game);
19
+
20
+ // Ship for void navigation
21
+ this.ship = new VoidShip(game);
22
+ this.add(this.ship);
23
+
24
+ // Collectible particles
25
+ this.particles = [];
26
+ this.particleSpawnTimer = 0;
27
+ this.particlesCollected = 0;
28
+
29
+ // Background swirl particles
30
+ this.background = [];
31
+
32
+ // Timer (limited time to escape)
33
+ this.timer = 0;
34
+
35
+ // Callbacks for game state transitions
36
+ this.onEscape = null;
37
+ this.onTimeout = null;
38
+ }
39
+
40
+ /**
41
+ * Reset the void scene for a new entry
42
+ */
43
+ reset() {
44
+ this.ship.reset();
45
+ this.particles = [];
46
+ this.particleSpawnTimer = 0;
47
+ this.particlesCollected = 0;
48
+ this.timer = 0;
49
+
50
+ // Generate swirling background
51
+ this.background = [];
52
+ for (let i = 0; i < 100; i++) {
53
+ this.background.push({
54
+ x: Math.random() * this.game.width,
55
+ y: Math.random() * this.game.height,
56
+ size: 1 + Math.random() * 3,
57
+ speed: 0.5 + Math.random() * 1.5,
58
+ angle: Math.random() * Math.PI * 2,
59
+ });
60
+ }
61
+ }
62
+
63
+ update(dt) {
64
+ super.update(dt);
65
+
66
+ this.timer += dt;
67
+
68
+ // Spawn particles
69
+ this.particleSpawnTimer += dt;
70
+ if (this.particleSpawnTimer >= CONFIG.voidParticleSpawnRate) {
71
+ this.particleSpawnTimer = 0;
72
+ this.spawnParticle();
73
+ }
74
+
75
+ // Update particles and check collection
76
+ const shipCircle = this.ship.getCircle();
77
+
78
+ for (const p of this.particles) {
79
+ p.update(dt);
80
+
81
+ if (p.active && Collision.circleCircle(shipCircle, p.getCircle())) {
82
+ p.collected = true;
83
+ this.particlesCollected++;
84
+ PenroseSounds.voidParticle();
85
+ }
86
+ }
87
+
88
+ // Remove fully animated collected particles
89
+ this.particles = this.particles.filter(
90
+ (p) => !p.collected || p.collectAnimation < 1
91
+ );
92
+
93
+ // Update background swirl
94
+ for (const bg of this.background) {
95
+ bg.angle += bg.speed * dt * 0.5;
96
+ bg.x += Math.cos(bg.angle) * bg.speed;
97
+ bg.y += Math.sin(bg.angle) * bg.speed;
98
+
99
+ // Wrap around
100
+ if (bg.x < 0) bg.x = this.game.width;
101
+ if (bg.x > this.game.width) bg.x = 0;
102
+ if (bg.y < 0) bg.y = this.game.height;
103
+ if (bg.y > this.game.height) bg.y = 0;
104
+ }
105
+
106
+ // Check win condition
107
+ if (this.particlesCollected >= CONFIG.voidParticlesToCollect) {
108
+ if (this.onEscape) this.onEscape();
109
+ }
110
+
111
+ // Check timeout
112
+ if (this.timer >= CONFIG.voidDuration) {
113
+ if (this.onTimeout) this.onTimeout();
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Spawn a particle at random position (not too close to ship)
119
+ */
120
+ spawnParticle() {
121
+ let px, py;
122
+ do {
123
+ px = 50 + Math.random() * (this.game.width - 100);
124
+ py = 50 + Math.random() * (this.game.height - 100);
125
+ } while (Math.hypot(px - this.ship.x, py - this.ship.y) < 100);
126
+
127
+ this.particles.push(new VoidParticle(px, py));
128
+ }
129
+
130
+ render() {
131
+ const ctx = Painter.ctx;
132
+
133
+ // Dark void background
134
+ ctx.fillStyle = "#020008";
135
+ ctx.fillRect(0, 0, this.game.width, this.game.height);
136
+
137
+ // Swirling background particles
138
+ this.renderBackground(ctx);
139
+
140
+ // Void particles
141
+ for (const p of this.particles) {
142
+ this.renderParticle(ctx, p);
143
+ }
144
+
145
+ // Ship (handled by Scene's child rendering)
146
+ super.render();
147
+
148
+ // UI overlay
149
+ this.renderUI(ctx);
150
+ }
151
+
152
+ renderBackground(ctx) {
153
+ for (const bg of this.background) {
154
+ ctx.fillStyle = `rgba(50, 20, 80, ${0.3 + Math.sin(bg.angle) * 0.2})`;
155
+ ctx.beginPath();
156
+ ctx.arc(bg.x, bg.y, bg.size, 0, Math.PI * 2);
157
+ ctx.fill();
158
+ }
159
+ }
160
+
161
+ renderParticle(ctx, p) {
162
+ const pulse = 0.7 + Math.sin(p.pulsePhase) * 0.3;
163
+
164
+ if (p.collected) {
165
+ // Collection animation - expand and fade
166
+ const scale = 1 + p.collectAnimation * 2;
167
+ const alpha = 1 - p.collectAnimation;
168
+
169
+ ctx.fillStyle = `rgba(200, 100, 255, ${alpha * 0.5})`;
170
+ ctx.beginPath();
171
+ ctx.arc(p.x, p.y, p.radius * scale * 2, 0, Math.PI * 2);
172
+ ctx.fill();
173
+ } else {
174
+ // Outer glow
175
+ const gradient = ctx.createRadialGradient(
176
+ p.x, p.y, 0,
177
+ p.x, p.y, p.radius * 2
178
+ );
179
+ gradient.addColorStop(0, `rgba(200, 100, 255, ${0.9 * pulse})`);
180
+ gradient.addColorStop(0.5, `rgba(150, 50, 200, ${0.5 * pulse})`);
181
+ gradient.addColorStop(1, "rgba(100, 0, 150, 0)");
182
+
183
+ ctx.fillStyle = gradient;
184
+ ctx.beginPath();
185
+ ctx.arc(p.x, p.y, p.radius * 2, 0, Math.PI * 2);
186
+ ctx.fill();
187
+
188
+ // Core
189
+ ctx.fillStyle = `rgba(255, 200, 255, ${pulse})`;
190
+ ctx.beginPath();
191
+ ctx.arc(p.x, p.y, p.radius * 0.5, 0, Math.PI * 2);
192
+ ctx.fill();
193
+ }
194
+ }
195
+
196
+ renderUI(ctx) {
197
+ const width = this.game.width;
198
+ const height = this.game.height;
199
+
200
+ // Progress bar at top
201
+ const barWidth = 300;
202
+ const barHeight = 20;
203
+ const barX = (width - barWidth) / 2;
204
+ const barY = 30;
205
+
206
+ // Background
207
+ ctx.fillStyle = "rgba(50, 20, 80, 0.8)";
208
+ ctx.fillRect(barX - 2, barY - 2, barWidth + 4, barHeight + 4);
209
+
210
+ // Progress fill
211
+ const progress = this.particlesCollected / CONFIG.voidParticlesToCollect;
212
+ const progressWidth = barWidth * progress;
213
+
214
+ const gradient = ctx.createLinearGradient(barX, 0, barX + barWidth, 0);
215
+ gradient.addColorStop(0, "#a0f");
216
+ gradient.addColorStop(1, "#f0f");
217
+ ctx.fillStyle = gradient;
218
+ ctx.fillRect(barX, barY, progressWidth, barHeight);
219
+
220
+ // Border
221
+ ctx.strokeStyle = "#c0f";
222
+ ctx.lineWidth = 2;
223
+ ctx.strokeRect(barX, barY, barWidth, barHeight);
224
+
225
+ // Text
226
+ ctx.fillStyle = "#fff";
227
+ ctx.font = "bold 14px monospace";
228
+ ctx.textAlign = "center";
229
+ ctx.fillText(
230
+ `VOID ESSENCE: ${this.particlesCollected} / ${CONFIG.voidParticlesToCollect}`,
231
+ width / 2,
232
+ barY + barHeight + 20
233
+ );
234
+
235
+ // Timer warning
236
+ const timeLeft = CONFIG.voidDuration - this.timer;
237
+ if (timeLeft < 10) {
238
+ const flash = Math.sin(Date.now() / 100) > 0;
239
+ ctx.fillStyle = flash ? "#f00" : "#800";
240
+ ctx.font = "bold 18px monospace";
241
+ ctx.fillText(`TIME: ${timeLeft.toFixed(1)}s`, width / 2, barY + barHeight + 50);
242
+ }
243
+
244
+ // Instructions
245
+ ctx.fillStyle = "#808";
246
+ ctx.font = "12px monospace";
247
+ ctx.fillText(
248
+ "WASD / Arrows to move — Collect purple essence to escape!",
249
+ width / 2,
250
+ height - 30
251
+ );
252
+
253
+ // Title
254
+ ctx.fillStyle = "#c0f";
255
+ ctx.font = "bold 24px monospace";
256
+ ctx.fillText("INSIDE THE SINGULARITY", width / 2, 80);
257
+ }
258
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * VoidShip - Player ship in the void dimension (inside black hole)
3
+ *
4
+ * A purple-tinted ship for navigating the singularity void.
5
+ * Uses direct position control (no physics) - the void doesn't
6
+ * follow normal spacetime rules.
7
+ *
8
+ * @extends GameObject
9
+ */
10
+
11
+ import { GameObject, Group, Keys, Rectangle } from "../../../src/index.js";
12
+ import { CONFIG } from "./constants.js";
13
+
14
+ const COLLISION_RADIUS = 15;
15
+
16
+ export class VoidShip extends GameObject {
17
+ constructor(game) {
18
+ super(game);
19
+
20
+ // Screen coordinates (not Penrose - we're inside the singularity)
21
+ this.x = 0;
22
+ this.y = 0;
23
+
24
+ // Build ship visual (purple-tinted version of main ship)
25
+ this.shipGroup = new Group({});
26
+
27
+ // Main body
28
+ const body = new Rectangle({
29
+ width: 12,
30
+ height: 16,
31
+ color: "#a0f",
32
+ });
33
+
34
+ // Nose
35
+ const nose = new Rectangle({
36
+ width: 4,
37
+ height: 8,
38
+ y: -10,
39
+ color: "#a0f",
40
+ });
41
+
42
+ // Wings
43
+ const leftWing = new Rectangle({
44
+ width: 8,
45
+ height: 6,
46
+ x: -10,
47
+ y: 4,
48
+ color: "#80a",
49
+ });
50
+ const rightWing = new Rectangle({
51
+ width: 8,
52
+ height: 6,
53
+ x: 10,
54
+ y: 4,
55
+ color: "#80a",
56
+ });
57
+
58
+ // Engine glow (purple flames)
59
+ this.engineLeft = new Rectangle({
60
+ width: 4,
61
+ height: 4,
62
+ x: -4,
63
+ y: 10,
64
+ color: "#f0f",
65
+ });
66
+ this.engineRight = new Rectangle({
67
+ width: 4,
68
+ height: 4,
69
+ x: 4,
70
+ y: 10,
71
+ color: "#c0f",
72
+ });
73
+
74
+ this.shipGroup.add(body);
75
+ this.shipGroup.add(nose);
76
+ this.shipGroup.add(leftWing);
77
+ this.shipGroup.add(rightWing);
78
+ this.shipGroup.add(this.engineLeft);
79
+ this.shipGroup.add(this.engineRight);
80
+
81
+ this.engineTimer = 0;
82
+ }
83
+
84
+ /**
85
+ * Reset ship to center of screen
86
+ */
87
+ reset() {
88
+ this.x = this.game.width / 2;
89
+ this.y = this.game.height / 2;
90
+ }
91
+
92
+ /**
93
+ * Update ship position based on input
94
+ * Direct position control - no velocity/physics in the void
95
+ * @param {number} dt - Delta time in seconds
96
+ */
97
+ update(dt) {
98
+ super.update(dt);
99
+
100
+ const leftPressed = Keys.isDown("a") || Keys.isDown("A") || Keys.isDown(Keys.LEFT);
101
+ const rightPressed = Keys.isDown("d") || Keys.isDown("D") || Keys.isDown(Keys.RIGHT);
102
+ const upPressed = Keys.isDown("w") || Keys.isDown("W") || Keys.isDown(Keys.UP);
103
+ const downPressed = Keys.isDown("s") || Keys.isDown("S") || Keys.isDown(Keys.DOWN);
104
+
105
+ const speed = CONFIG.voidShipSpeed;
106
+
107
+ if (leftPressed) this.x -= speed * dt;
108
+ if (rightPressed) this.x += speed * dt;
109
+ if (upPressed) this.y -= speed * dt;
110
+ if (downPressed) this.y += speed * dt;
111
+
112
+ // Clamp to screen bounds (with margin)
113
+ const margin = 30;
114
+ this.x = Math.max(margin, Math.min(this.game.width - margin, this.x));
115
+ this.y = Math.max(margin, Math.min(this.game.height - margin, this.y));
116
+
117
+ // Engine flicker
118
+ this.engineTimer += dt * 20;
119
+ const flicker = Math.sin(this.engineTimer) > 0;
120
+ this.engineLeft.color = flicker ? "#f0f" : "#c0f";
121
+ this.engineRight.color = flicker ? "#c0f" : "#f0f";
122
+ }
123
+
124
+ /**
125
+ * Render the void ship
126
+ */
127
+ render() {
128
+ this.shipGroup.x = this.x;
129
+ this.shipGroup.y = this.y;
130
+ this.shipGroup.render();
131
+ }
132
+
133
+ /**
134
+ * Get collision circle for this ship (screen coordinates)
135
+ * @returns {{ x: number, y: number, radius: number }}
136
+ */
137
+ getCircle() {
138
+ return {
139
+ x: this.x,
140
+ y: this.y,
141
+ radius: COLLISION_RADIUS,
142
+ };
143
+ }
144
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Penrose Wormhole
3
+ *
4
+ * A rare object that teleports the player back to the start of the level,
5
+ * allowing them to farm more points while keeping their score and multiplier!
6
+ */
7
+
8
+ import { CONFIG } from "./constants.js";
9
+
10
+ export class PenroseWormhole {
11
+ constructor(u, v) {
12
+ this.u = u;
13
+ this.v = v;
14
+
15
+ // Wormhole size
16
+ this.radius = CONFIG.wormholeRadius;
17
+
18
+ // Animation
19
+ this.rotationPhase = Math.random() * Math.PI * 2;
20
+ this.pulsePhase = Math.random() * Math.PI * 2;
21
+
22
+ // State
23
+ this.used = false; // Once used, disappears
24
+ this.spawnTime = 0; // For fade-in effect
25
+ }
26
+
27
+ update(dt) {
28
+ this.rotationPhase += dt * 3; // Faster rotation than black holes
29
+ this.pulsePhase += dt * 5;
30
+ this.spawnTime += dt;
31
+ }
32
+
33
+ /**
34
+ * Get circle bounds for collision detection
35
+ */
36
+ getCircle() {
37
+ return { x: this.u, y: this.v, radius: this.radius };
38
+ }
39
+
40
+ /**
41
+ * Check if wormhole is active (not used)
42
+ */
43
+ get active() {
44
+ return !this.used;
45
+ }
46
+ }