@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.
- package/.github/workflows/release.yaml +70 -0
- package/.jshintrc +4 -0
- package/.vscode/settings.json +22 -0
- package/CLAUDE.md +310 -0
- package/blackhole.jpg +0 -0
- package/demo.png +0 -0
- package/demos/CNAME +1 -0
- package/demos/animations.html +31 -0
- package/demos/basic.html +38 -0
- package/demos/baskara.html +31 -0
- package/demos/bezier.html +35 -0
- package/demos/beziersignature.html +29 -0
- package/demos/blackhole.html +28 -0
- package/demos/blob.html +35 -0
- package/demos/demos.css +289 -0
- package/demos/easing.html +28 -0
- package/demos/events.html +195 -0
- package/demos/fluent.html +647 -0
- package/demos/fractals.html +36 -0
- package/demos/genart.html +26 -0
- package/demos/gendream.html +26 -0
- package/demos/group.html +36 -0
- package/demos/home.html +587 -0
- package/demos/index.html +364 -0
- package/demos/isometric.html +34 -0
- package/demos/js/animations.js +452 -0
- package/demos/js/basic.js +204 -0
- package/demos/js/baskara.js +751 -0
- package/demos/js/bezier.js +692 -0
- package/demos/js/beziersignature.js +241 -0
- package/demos/js/blackhole/accretiondisk.obj.js +379 -0
- package/demos/js/blackhole/blackhole.obj.js +318 -0
- package/demos/js/blackhole/index.js +409 -0
- package/demos/js/blackhole/particle.js +56 -0
- package/demos/js/blackhole/starfield.obj.js +218 -0
- package/demos/js/blob.js +2263 -0
- package/demos/js/easing.js +477 -0
- package/demos/js/fluent.js +183 -0
- package/demos/js/fractals.js +931 -0
- package/demos/js/fractalworker.js +93 -0
- package/demos/js/genart.js +268 -0
- package/demos/js/gendream.js +209 -0
- package/demos/js/group.js +140 -0
- package/demos/js/info-toggle.js +25 -0
- package/demos/js/isometric.js +863 -0
- package/demos/js/kerr.js +1556 -0
- package/demos/js/lavalamp.js +590 -0
- package/demos/js/layout.js +354 -0
- package/demos/js/mondrian.js +285 -0
- package/demos/js/opacity.js +275 -0
- package/demos/js/painter.js +484 -0
- package/demos/js/particles-showcase.js +514 -0
- package/demos/js/particles.js +299 -0
- package/demos/js/patterns.js +397 -0
- package/demos/js/penrose/artifact.js +69 -0
- package/demos/js/penrose/blackhole.js +121 -0
- package/demos/js/penrose/constants.js +73 -0
- package/demos/js/penrose/game.js +943 -0
- package/demos/js/penrose/lore.js +278 -0
- package/demos/js/penrose/penrosescene.js +892 -0
- package/demos/js/penrose/ship.js +216 -0
- package/demos/js/penrose/sounds.js +211 -0
- package/demos/js/penrose/voidparticle.js +55 -0
- package/demos/js/penrose/voidscene.js +258 -0
- package/demos/js/penrose/voidship.js +144 -0
- package/demos/js/penrose/wormhole.js +46 -0
- package/demos/js/pipeline.js +555 -0
- package/demos/js/scene.js +304 -0
- package/demos/js/scenes.js +320 -0
- package/demos/js/schrodinger.js +410 -0
- package/demos/js/schwarzschild.js +1023 -0
- package/demos/js/shapes.js +628 -0
- package/demos/js/space/alien.js +171 -0
- package/demos/js/space/boom.js +98 -0
- package/demos/js/space/boss.js +353 -0
- package/demos/js/space/buff.js +73 -0
- package/demos/js/space/bullet.js +102 -0
- package/demos/js/space/constants.js +85 -0
- package/demos/js/space/game.js +1884 -0
- package/demos/js/space/hud.js +112 -0
- package/demos/js/space/laserbeam.js +179 -0
- package/demos/js/space/lightning.js +277 -0
- package/demos/js/space/minion.js +192 -0
- package/demos/js/space/missile.js +212 -0
- package/demos/js/space/player.js +430 -0
- package/demos/js/space/powerup.js +90 -0
- package/demos/js/space/starfield.js +58 -0
- package/demos/js/space/starpower.js +90 -0
- package/demos/js/spacetime.js +559 -0
- package/demos/js/svgtween.js +204 -0
- package/demos/js/tde/accretiondisk.js +418 -0
- package/demos/js/tde/blackhole.js +219 -0
- package/demos/js/tde/blackholescene.js +209 -0
- package/demos/js/tde/config.js +59 -0
- package/demos/js/tde/index.js +695 -0
- package/demos/js/tde/jets.js +290 -0
- package/demos/js/tde/lensedstarfield.js +147 -0
- package/demos/js/tde/tdestar.js +317 -0
- package/demos/js/tde/tidalstream.js +356 -0
- package/demos/js/tde_old/blackhole.obj.js +354 -0
- package/demos/js/tde_old/debris.obj.js +791 -0
- package/demos/js/tde_old/flare.obj.js +239 -0
- package/demos/js/tde_old/index.js +448 -0
- package/demos/js/tde_old/star.obj.js +812 -0
- package/demos/js/tiles.js +312 -0
- package/demos/js/tweendemo.js +79 -0
- package/demos/js/visibility.js +102 -0
- package/demos/kerr.html +28 -0
- package/demos/lavalamp.html +27 -0
- package/demos/layouts.html +37 -0
- package/demos/logo.svg +4 -0
- package/demos/loop.html +84 -0
- package/demos/mondrian.html +32 -0
- package/demos/og_image.png +0 -0
- package/demos/opacity.html +36 -0
- package/demos/painter.html +39 -0
- package/demos/particles-showcase.html +28 -0
- package/demos/particles.html +24 -0
- package/demos/patterns.html +33 -0
- package/demos/penrose-game.html +31 -0
- package/demos/pipeline.html +737 -0
- package/demos/scene.html +33 -0
- package/demos/scenes.html +96 -0
- package/demos/schrodinger.html +27 -0
- package/demos/schwarzschild.html +27 -0
- package/demos/shapes.html +16 -0
- package/demos/space.html +85 -0
- package/demos/spacetime.html +27 -0
- package/demos/svgtween.html +29 -0
- package/demos/tde.html +28 -0
- package/demos/tiles.html +28 -0
- package/demos/transforms.html +400 -0
- package/demos/tween.html +45 -0
- package/demos/visibility.html +33 -0
- package/disk_example.png +0 -0
- package/docs/README.md +222 -0
- package/docs/concepts/architecture-overview.md +204 -0
- package/docs/concepts/lifecycle.md +255 -0
- package/docs/concepts/rendering-pipeline.md +279 -0
- package/docs/concepts/tde-zorder.md +106 -0
- package/docs/concepts/two-layer-architecture.md +229 -0
- package/docs/getting-started/first-game.md +354 -0
- package/docs/getting-started/hello-world.md +269 -0
- package/docs/getting-started/installation.md +157 -0
- package/docs/modules/collision/README.md +453 -0
- package/docs/modules/fluent/README.md +1075 -0
- package/docs/modules/game/README.md +303 -0
- package/docs/modules/isometric-camera.md +210 -0
- package/docs/modules/isometric.md +275 -0
- package/docs/modules/painter/README.md +328 -0
- package/docs/modules/particle/README.md +559 -0
- package/docs/modules/shapes/README.md +221 -0
- package/docs/modules/shapes/base/euclidian.md +123 -0
- package/docs/modules/shapes/base/geometry2d.md +204 -0
- package/docs/modules/shapes/base/renderable.md +215 -0
- package/docs/modules/shapes/base/shape.md +262 -0
- package/docs/modules/shapes/base/transformable.md +243 -0
- package/docs/modules/shapes/hierarchy.md +218 -0
- package/docs/modules/state/README.md +577 -0
- package/docs/modules/util/README.md +99 -0
- package/docs/modules/util/camera3d.md +412 -0
- package/docs/modules/util/scene3d.md +395 -0
- package/index.html +17 -0
- package/jsdoc.json +50 -0
- package/package.json +55 -0
- package/readme.md +599 -0
- package/scripts/build-demo.js +69 -0
- package/scripts/bundle4llm.js +276 -0
- package/scripts/clearconsole.js +48 -0
- package/src/collision/collision-system.js +332 -0
- package/src/collision/collision.js +303 -0
- package/src/collision/index.js +10 -0
- package/src/fluent/fluent-game.js +430 -0
- package/src/fluent/fluent-go.js +1060 -0
- package/src/fluent/fluent-layer.js +152 -0
- package/src/fluent/fluent-scene.js +291 -0
- package/src/fluent/index.js +98 -0
- package/src/fluent/sketch.js +380 -0
- package/src/game/game.js +467 -0
- package/src/game/index.js +49 -0
- package/src/game/objects/go.js +220 -0
- package/src/game/objects/imagego.js +30 -0
- package/src/game/objects/index.js +54 -0
- package/src/game/objects/isometric-scene.js +260 -0
- package/src/game/objects/layoutscene.js +549 -0
- package/src/game/objects/scene.js +175 -0
- package/src/game/objects/scene3d.js +118 -0
- package/src/game/objects/text.js +221 -0
- package/src/game/objects/wrapper.js +232 -0
- package/src/game/pipeline.js +243 -0
- package/src/game/ui/button.js +396 -0
- package/src/game/ui/cursor.js +93 -0
- package/src/game/ui/fps.js +91 -0
- package/src/game/ui/index.js +5 -0
- package/src/game/ui/togglebutton.js +93 -0
- package/src/game/ui/tooltip.js +249 -0
- package/src/index.js +25 -0
- package/src/io/events.js +20 -0
- package/src/io/index.js +86 -0
- package/src/io/input.js +70 -0
- package/src/io/keys.js +152 -0
- package/src/io/mouse.js +61 -0
- package/src/io/touch.js +39 -0
- package/src/logger/debugtab.js +138 -0
- package/src/logger/index.js +3 -0
- package/src/logger/loggable.js +47 -0
- package/src/logger/logger.js +113 -0
- package/src/math/complex.js +37 -0
- package/src/math/constants.js +1 -0
- package/src/math/fractal.js +1271 -0
- package/src/math/gr.js +201 -0
- package/src/math/heat.js +202 -0
- package/src/math/index.js +12 -0
- package/src/math/noise.js +433 -0
- package/src/math/orbital.js +191 -0
- package/src/math/patterns.js +1339 -0
- package/src/math/penrose.js +259 -0
- package/src/math/quantum.js +115 -0
- package/src/math/random.js +195 -0
- package/src/math/tensor.js +1009 -0
- package/src/mixins/anchor.js +131 -0
- package/src/mixins/draggable.js +72 -0
- package/src/mixins/index.js +2 -0
- package/src/motion/bezier.js +132 -0
- package/src/motion/bounce.js +58 -0
- package/src/motion/easing.js +349 -0
- package/src/motion/float.js +130 -0
- package/src/motion/follow.js +125 -0
- package/src/motion/hop.js +52 -0
- package/src/motion/index.js +82 -0
- package/src/motion/motion.js +1124 -0
- package/src/motion/orbit.js +49 -0
- package/src/motion/oscillate.js +39 -0
- package/src/motion/parabolic.js +141 -0
- package/src/motion/patrol.js +147 -0
- package/src/motion/pendulum.js +48 -0
- package/src/motion/pulse.js +88 -0
- package/src/motion/shake.js +83 -0
- package/src/motion/spiral.js +144 -0
- package/src/motion/spring.js +150 -0
- package/src/motion/swing.js +47 -0
- package/src/motion/tween.js +92 -0
- package/src/motion/tweenetik.js +139 -0
- package/src/motion/waypoint.js +210 -0
- package/src/painter/index.js +8 -0
- package/src/painter/painter.colors.js +331 -0
- package/src/painter/painter.effects.js +230 -0
- package/src/painter/painter.img.js +229 -0
- package/src/painter/painter.js +295 -0
- package/src/painter/painter.lines.js +189 -0
- package/src/painter/painter.opacity.js +41 -0
- package/src/painter/painter.shapes.js +277 -0
- package/src/painter/painter.text.js +273 -0
- package/src/particle/emitter.js +124 -0
- package/src/particle/index.js +11 -0
- package/src/particle/particle-system.js +322 -0
- package/src/particle/particle.js +71 -0
- package/src/particle/updaters.js +170 -0
- package/src/shapes/arc.js +43 -0
- package/src/shapes/arrow.js +33 -0
- package/src/shapes/bezier.js +42 -0
- package/src/shapes/circle.js +62 -0
- package/src/shapes/clouds.js +56 -0
- package/src/shapes/cone.js +219 -0
- package/src/shapes/cross.js +70 -0
- package/src/shapes/cube.js +244 -0
- package/src/shapes/cylinder.js +254 -0
- package/src/shapes/diamond.js +48 -0
- package/src/shapes/euclidian.js +111 -0
- package/src/shapes/figure.js +115 -0
- package/src/shapes/geometry.js +220 -0
- package/src/shapes/group.js +375 -0
- package/src/shapes/heart.js +42 -0
- package/src/shapes/hexagon.js +26 -0
- package/src/shapes/image.js +192 -0
- package/src/shapes/index.js +111 -0
- package/src/shapes/line.js +29 -0
- package/src/shapes/pattern.js +90 -0
- package/src/shapes/pin.js +44 -0
- package/src/shapes/poly.js +31 -0
- package/src/shapes/prism.js +226 -0
- package/src/shapes/rect.js +35 -0
- package/src/shapes/renderable.js +333 -0
- package/src/shapes/ring.js +26 -0
- package/src/shapes/roundrect.js +95 -0
- package/src/shapes/shape.js +117 -0
- package/src/shapes/slice.js +26 -0
- package/src/shapes/sphere.js +314 -0
- package/src/shapes/sphere3d.js +537 -0
- package/src/shapes/square.js +15 -0
- package/src/shapes/star.js +99 -0
- package/src/shapes/svg.js +408 -0
- package/src/shapes/text.js +553 -0
- package/src/shapes/traceable.js +83 -0
- package/src/shapes/transform.js +357 -0
- package/src/shapes/transformable.js +172 -0
- package/src/shapes/triangle.js +26 -0
- package/src/sound/index.js +17 -0
- package/src/sound/sound.js +473 -0
- package/src/sound/synth.analyzer.js +149 -0
- package/src/sound/synth.effects.js +207 -0
- package/src/sound/synth.envelope.js +59 -0
- package/src/sound/synth.js +229 -0
- package/src/sound/synth.musical.js +160 -0
- package/src/sound/synth.noise.js +85 -0
- package/src/sound/synth.oscillators.js +293 -0
- package/src/state/index.js +10 -0
- package/src/state/state-machine.js +371 -0
- package/src/util/camera3d.js +438 -0
- package/src/util/index.js +6 -0
- package/src/util/isometric-camera.js +235 -0
- package/src/util/layout.js +317 -0
- package/src/util/position.js +147 -0
- package/src/util/tasks.js +47 -0
- package/src/util/zindex.js +287 -0
- package/src/webgl/index.js +9 -0
- package/src/webgl/shaders/sphere-shaders.js +994 -0
- package/src/webgl/webgl-renderer.js +388 -0
- package/tde.png +0 -0
- package/test/math/orbital.test.js +61 -0
- package/test/math/tensor.test.js +114 -0
- package/test/particle/emitter.test.js +204 -0
- package/test/particle/particle-system.test.js +310 -0
- package/test/particle/particle.test.js +116 -0
- package/test/particle/updaters.test.js +386 -0
- package/test/setup.js +120 -0
- package/test/shapes/euclidian.test.js +44 -0
- package/test/shapes/geometry.test.js +86 -0
- package/test/shapes/group.test.js +86 -0
- package/test/shapes/rectangle.test.js +64 -0
- package/test/shapes/transform.test.js +379 -0
- package/test/util/camera3d.test.js +428 -0
- package/test/util/scene3d.test.js +352 -0
- package/types/collision.d.ts +249 -0
- package/types/common.d.ts +155 -0
- package/types/game.d.ts +497 -0
- package/types/index.d.ts +309 -0
- package/types/io.d.ts +188 -0
- package/types/logger.d.ts +127 -0
- package/types/math.d.ts +268 -0
- package/types/mixins.d.ts +92 -0
- package/types/motion.d.ts +678 -0
- package/types/painter.d.ts +378 -0
- package/types/shapes.d.ts +864 -0
- package/types/sound.d.ts +672 -0
- package/types/state.d.ts +251 -0
- package/types/util.d.ts +253 -0
- package/vite.config.js +50 -0
- package/vitest.config.js +13 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { GameObject, Group, Keys, Rectangle } from "../../../src/index.js";
|
|
2
|
+
import { CONFIG } from "./constants.js";
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// PENROSE SHIP
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
export class PenroseShip extends GameObject {
|
|
8
|
+
constructor(game) {
|
|
9
|
+
super(game);
|
|
10
|
+
|
|
11
|
+
// Position in Penrose coordinates (u = space, v = time)
|
|
12
|
+
this.u = 0;
|
|
13
|
+
this.v = CONFIG.shipStartV;
|
|
14
|
+
|
|
15
|
+
// Heading angle (direction ship is traveling, 0 = straight up/forward)
|
|
16
|
+
this.heading = 0;
|
|
17
|
+
|
|
18
|
+
// Velocity is now derived from heading for compatibility
|
|
19
|
+
this.velocity = 0;
|
|
20
|
+
|
|
21
|
+
// Time progression speed (accelerates)
|
|
22
|
+
this.timeSpeed = CONFIG.shipTimeSpeed;
|
|
23
|
+
|
|
24
|
+
// Worldline trail
|
|
25
|
+
this.worldline = [];
|
|
26
|
+
|
|
27
|
+
// Ship visual (simple triangle pointing up)
|
|
28
|
+
this.shipGroup = new Group({});
|
|
29
|
+
|
|
30
|
+
// Main body
|
|
31
|
+
const body = new Rectangle({
|
|
32
|
+
width: 12,
|
|
33
|
+
height: 16,
|
|
34
|
+
color: "#0f0",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Nose
|
|
38
|
+
const nose = new Rectangle({
|
|
39
|
+
width: 4,
|
|
40
|
+
height: 8,
|
|
41
|
+
y: -10,
|
|
42
|
+
color: "#0f0",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Wings
|
|
46
|
+
const leftWing = new Rectangle({
|
|
47
|
+
width: 8,
|
|
48
|
+
height: 6,
|
|
49
|
+
x: -10,
|
|
50
|
+
y: 4,
|
|
51
|
+
color: "#0a0",
|
|
52
|
+
});
|
|
53
|
+
const rightWing = new Rectangle({
|
|
54
|
+
width: 8,
|
|
55
|
+
height: 6,
|
|
56
|
+
x: 10,
|
|
57
|
+
y: 4,
|
|
58
|
+
color: "#0a0",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Engine glow
|
|
62
|
+
this.engineLeft = new Rectangle({
|
|
63
|
+
width: 4,
|
|
64
|
+
height: 4,
|
|
65
|
+
x: -4,
|
|
66
|
+
y: 10,
|
|
67
|
+
color: "#fa0",
|
|
68
|
+
});
|
|
69
|
+
this.engineRight = new Rectangle({
|
|
70
|
+
width: 4,
|
|
71
|
+
height: 4,
|
|
72
|
+
x: 4,
|
|
73
|
+
y: 10,
|
|
74
|
+
color: "#f60",
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
this.shipGroup.add(body);
|
|
78
|
+
this.shipGroup.add(nose);
|
|
79
|
+
this.shipGroup.add(leftWing);
|
|
80
|
+
this.shipGroup.add(rightWing);
|
|
81
|
+
this.shipGroup.add(this.engineLeft);
|
|
82
|
+
this.shipGroup.add(this.engineRight);
|
|
83
|
+
|
|
84
|
+
this.engineTimer = 0;
|
|
85
|
+
this.alive = true;
|
|
86
|
+
|
|
87
|
+
// Death animation
|
|
88
|
+
this.deathProgress = 0;
|
|
89
|
+
this.deathBlackHole = null;
|
|
90
|
+
this.deathFade = 0; // 0 to 1, when 1 = fully faded
|
|
91
|
+
|
|
92
|
+
// Ergosphere status (set by game)
|
|
93
|
+
this.inErgosphere = false;
|
|
94
|
+
|
|
95
|
+
// Boost status (set by game)
|
|
96
|
+
this.boostMultiplier = 1;
|
|
97
|
+
|
|
98
|
+
// Edge detection
|
|
99
|
+
this.hitSpatialInfinity = false;
|
|
100
|
+
|
|
101
|
+
// Collision radius (for circle collision)
|
|
102
|
+
this.radius = 0.02;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get circle bounds for collision detection (Penrose coordinates)
|
|
107
|
+
*/
|
|
108
|
+
getCircle() {
|
|
109
|
+
return { x: this.u, y: this.v, radius: this.radius };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
reset() {
|
|
113
|
+
this.u = 0;
|
|
114
|
+
this.v = CONFIG.shipStartV;
|
|
115
|
+
this.heading = 0;
|
|
116
|
+
this.velocity = 0;
|
|
117
|
+
this.timeSpeed = CONFIG.shipTimeSpeed;
|
|
118
|
+
this.worldline = [];
|
|
119
|
+
this.alive = true;
|
|
120
|
+
this.deathProgress = 0;
|
|
121
|
+
this.deathBlackHole = null;
|
|
122
|
+
this.deathFade = 0;
|
|
123
|
+
this.inErgosphere = false;
|
|
124
|
+
this.boostMultiplier = 1;
|
|
125
|
+
this.hitSpatialInfinity = false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
update(dt) {
|
|
129
|
+
if (!this.alive) {
|
|
130
|
+
// Death animation - get pulled to singularity SLOWLY
|
|
131
|
+
this.deathProgress += dt * CONFIG.deathAttractionSpeed;
|
|
132
|
+
if (this.deathBlackHole) {
|
|
133
|
+
const t = Math.min(this.deathProgress, 1);
|
|
134
|
+
const eased = t * t * t; // Cubic easing - starts slow, speeds up
|
|
135
|
+
this.u += (this.deathBlackHole.u - this.u) * eased * dt * 1.5;
|
|
136
|
+
this.v += (this.deathBlackHole.v - this.v) * eased * dt * 1.5;
|
|
137
|
+
|
|
138
|
+
// Check if we've reached the singularity (close to center)
|
|
139
|
+
const dist = Math.sqrt(
|
|
140
|
+
Math.pow(this.u - this.deathBlackHole.u, 2) +
|
|
141
|
+
Math.pow(this.v - this.deathBlackHole.v, 2),
|
|
142
|
+
);
|
|
143
|
+
if (dist < 0.02) {
|
|
144
|
+
// Start fading out
|
|
145
|
+
this.deathFade += dt / CONFIG.deathFadeDuration;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Time always advances (accelerating) - boosted when using Kerr energy
|
|
152
|
+
this.timeSpeed *= Math.pow(CONFIG.shipAcceleration, dt);
|
|
153
|
+
this.v += this.timeSpeed * this.boostMultiplier * dt;
|
|
154
|
+
|
|
155
|
+
// Steering input - changes heading (direction you're traveling)
|
|
156
|
+
const leftPressed =
|
|
157
|
+
Keys.isDown("a") || Keys.isDown("A") || Keys.isDown(Keys.LEFT);
|
|
158
|
+
const rightPressed =
|
|
159
|
+
Keys.isDown("d") || Keys.isDown("D") || Keys.isDown(Keys.RIGHT);
|
|
160
|
+
|
|
161
|
+
if (leftPressed) {
|
|
162
|
+
this.heading -= CONFIG.shipSteering * dt;
|
|
163
|
+
}
|
|
164
|
+
if (rightPressed) {
|
|
165
|
+
this.heading += CONFIG.shipSteering * dt;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// When not steering, heading springs back to center
|
|
169
|
+
if (!leftPressed && !rightPressed) {
|
|
170
|
+
this.heading *= 0.92; // Decay toward 0
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Clamp heading (can't go more than ~60 degrees off forward - would be spacelike!)
|
|
174
|
+
const maxHeading = Math.PI / 3; // 60 degrees
|
|
175
|
+
this.heading = Math.max(-maxHeading, Math.min(maxHeading, this.heading));
|
|
176
|
+
|
|
177
|
+
// Update velocity for compatibility (used by camera rotation)
|
|
178
|
+
this.velocity = this.heading;
|
|
179
|
+
|
|
180
|
+
// Movement based on heading direction
|
|
181
|
+
// sin(heading) gives lateral movement, heading=0 means straight up
|
|
182
|
+
this.u += Math.sin(this.heading) * this.timeSpeed * dt;
|
|
183
|
+
|
|
184
|
+
// Clamp to diamond bounds (|u| + |v| <= 1)
|
|
185
|
+
// Hitting the edge = spatial infinity (i0) - you can never reach it!
|
|
186
|
+
const maxU = Math.max(0, 1 - Math.abs(this.v));
|
|
187
|
+
const prevU = this.u;
|
|
188
|
+
this.u = Math.max(-maxU, Math.min(maxU, this.u));
|
|
189
|
+
|
|
190
|
+
// If we hit spatial infinity, kill velocity - you can't escape to infinity
|
|
191
|
+
if (this.u !== prevU) {
|
|
192
|
+
this.velocity *= 0.3; // Bounce back effect
|
|
193
|
+
this.hitSpatialInfinity = true;
|
|
194
|
+
} else {
|
|
195
|
+
this.hitSpatialInfinity = false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Record worldline
|
|
199
|
+
this.worldline.push({ u: this.u, v: this.v });
|
|
200
|
+
if (this.worldline.length > CONFIG.worldlineMaxLength) {
|
|
201
|
+
this.worldline.shift();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Engine flicker
|
|
205
|
+
this.engineTimer += dt * 20;
|
|
206
|
+
const flicker = Math.sin(this.engineTimer) > 0;
|
|
207
|
+
this.engineLeft.color = flicker ? "#fa0" : "#f60";
|
|
208
|
+
this.engineRight.color = flicker ? "#f60" : "#fa0";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
die(blackHole) {
|
|
212
|
+
this.alive = false;
|
|
213
|
+
this.deathBlackHole = blackHole;
|
|
214
|
+
this.deathProgress = 0;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Penrose Game Sound Effects
|
|
3
|
+
*
|
|
4
|
+
* Procedural audio using the Synth system.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Synth } from "../../../src/index.js";
|
|
8
|
+
|
|
9
|
+
let initialized = false;
|
|
10
|
+
|
|
11
|
+
export const PenroseSounds = {
|
|
12
|
+
/**
|
|
13
|
+
* Initialize the sound system (must be called after user interaction)
|
|
14
|
+
*/
|
|
15
|
+
init() {
|
|
16
|
+
if (initialized) return;
|
|
17
|
+
Synth.init({ masterVolume: 0.4 });
|
|
18
|
+
initialized = true;
|
|
19
|
+
console.log("Sound initialized");
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if sound is initialized
|
|
24
|
+
*/
|
|
25
|
+
get isReady() {
|
|
26
|
+
return initialized;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Wormhole appears - ethereal rising sweep
|
|
31
|
+
*/
|
|
32
|
+
wormholeSpawn() {
|
|
33
|
+
if (!initialized) return;
|
|
34
|
+
|
|
35
|
+
// Ethereal rising sweep with shimmer
|
|
36
|
+
Synth.osc.sweep(200, 800, 0.8, { type: "sine", volume: 0.3 });
|
|
37
|
+
Synth.osc.sweep(300, 1200, 0.6, { type: "triangle", volume: 0.15 });
|
|
38
|
+
|
|
39
|
+
// Sparkle effect
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
Synth.osc.tone(1200, 0.2, { type: "sine", volume: 0.1, attack: 0.01, release: 0.15 });
|
|
42
|
+
}, 200);
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
Synth.osc.tone(1600, 0.15, { type: "sine", volume: 0.08, attack: 0.01, release: 0.1 });
|
|
45
|
+
}, 350);
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Enter wormhole - whoosh teleport
|
|
50
|
+
*/
|
|
51
|
+
wormholeEnter() {
|
|
52
|
+
if (!initialized) return;
|
|
53
|
+
|
|
54
|
+
// Whoosh down + warble
|
|
55
|
+
Synth.osc.sweep(600, 100, 0.5, { type: "sine", volume: 0.4 });
|
|
56
|
+
Synth.osc.sweep(800, 150, 0.4, { type: "triangle", volume: 0.2 });
|
|
57
|
+
Synth.osc.fm(200, 8, 100, 0.6, { volume: 0.15 });
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Artifact appears - mysterious chime
|
|
62
|
+
*/
|
|
63
|
+
artifactSpawn() {
|
|
64
|
+
if (!initialized) return;
|
|
65
|
+
|
|
66
|
+
// Mysterious chime
|
|
67
|
+
Synth.osc.tone(523, 0.4, { type: "sine", volume: 0.25, attack: 0.01, release: 0.3 }); // C5
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
Synth.osc.tone(659, 0.3, { type: "sine", volume: 0.2, attack: 0.01, release: 0.25 }); // E5
|
|
70
|
+
}, 100);
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
Synth.osc.tone(784, 0.5, { type: "sine", volume: 0.25, attack: 0.01, release: 0.4 }); // G5
|
|
73
|
+
}, 200);
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Collect artifact - triumphant arpeggio
|
|
78
|
+
*/
|
|
79
|
+
artifactCollect() {
|
|
80
|
+
if (!initialized) return;
|
|
81
|
+
|
|
82
|
+
// Triumphant arpeggio
|
|
83
|
+
const notes = [523, 659, 784, 1047]; // C E G C
|
|
84
|
+
notes.forEach((freq, i) => {
|
|
85
|
+
setTimeout(() => {
|
|
86
|
+
Synth.osc.tone(freq, 0.4, { type: "triangle", volume: 0.2, attack: 0.01, release: 0.3 });
|
|
87
|
+
}, i * 80);
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Kerr energy harvested - quick blip
|
|
93
|
+
*/
|
|
94
|
+
kerrCollect() {
|
|
95
|
+
if (!initialized) return;
|
|
96
|
+
|
|
97
|
+
// Quick energetic blip
|
|
98
|
+
Synth.osc.sweep(400, 1000, 0.15, { type: "square", volume: 0.15 });
|
|
99
|
+
Synth.osc.tone(800, 0.1, { type: "sine", volume: 0.1 });
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Enter void dimension - deep ominous
|
|
104
|
+
*/
|
|
105
|
+
voidEnter() {
|
|
106
|
+
if (!initialized) return;
|
|
107
|
+
|
|
108
|
+
// Deep ominous rumble + descending
|
|
109
|
+
Synth.osc.sweep(400, 50, 1.5, { type: "sawtooth", volume: 0.25 });
|
|
110
|
+
Synth.osc.fm(60, 2, 30, 2, { volume: 0.3 });
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Collect void particle - sparkle
|
|
115
|
+
*/
|
|
116
|
+
voidParticle() {
|
|
117
|
+
if (!initialized) return;
|
|
118
|
+
|
|
119
|
+
// Quick sparkle
|
|
120
|
+
Synth.osc.tone(600 + Math.random() * 400, 0.15, {
|
|
121
|
+
type: "sine",
|
|
122
|
+
volume: 0.12,
|
|
123
|
+
attack: 0.01,
|
|
124
|
+
release: 0.1
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Escape the void - triumphant rising
|
|
130
|
+
*/
|
|
131
|
+
voidEscape() {
|
|
132
|
+
if (!initialized) return;
|
|
133
|
+
|
|
134
|
+
// Rising triumphant escape
|
|
135
|
+
Synth.osc.sweep(100, 800, 1, { type: "sine", volume: 0.3 });
|
|
136
|
+
Synth.osc.sweep(150, 1200, 0.8, { type: "triangle", volume: 0.2 });
|
|
137
|
+
|
|
138
|
+
// Fanfare
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
Synth.osc.tone(523, 0.3, { type: "triangle", volume: 0.2 });
|
|
141
|
+
Synth.osc.tone(659, 0.3, { type: "triangle", volume: 0.15 });
|
|
142
|
+
}, 400);
|
|
143
|
+
setTimeout(() => {
|
|
144
|
+
Synth.osc.tone(784, 0.5, { type: "triangle", volume: 0.25 });
|
|
145
|
+
}, 600);
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Death by singularity - descending doom
|
|
150
|
+
*/
|
|
151
|
+
death() {
|
|
152
|
+
if (!initialized) return;
|
|
153
|
+
|
|
154
|
+
// Descending doom
|
|
155
|
+
Synth.osc.sweep(400, 60, 1.5, { type: "sawtooth", volume: 0.2 });
|
|
156
|
+
Synth.osc.fm(100, 3, 50, 2, { volume: 0.15 });
|
|
157
|
+
|
|
158
|
+
this.stopEngine();
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Continuous engine drone
|
|
163
|
+
*/
|
|
164
|
+
engine: null,
|
|
165
|
+
|
|
166
|
+
startEngine() {
|
|
167
|
+
if (!initialized || this.engine) return;
|
|
168
|
+
|
|
169
|
+
// Create a rich droning sound using FM or a simple oscillator
|
|
170
|
+
// We use a low-frequency sawtooth for that 'engine' feel
|
|
171
|
+
this.engine = Synth.osc.continuous({
|
|
172
|
+
type: "sawtooth",
|
|
173
|
+
frequency: 60,
|
|
174
|
+
volume: 0, // Start silent, ramp up
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
this.engine.setVolume(0.1, 0.5);
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
updateEngine(timeSpeed, isBoosting) {
|
|
181
|
+
if (!this.engine) return;
|
|
182
|
+
|
|
183
|
+
// Base frequency increases with ship's time acceleration
|
|
184
|
+
const baseFreq = 60 + timeSpeed * 50;
|
|
185
|
+
// Frequency jumps when boosting
|
|
186
|
+
const targetFreq = isBoosting ? baseFreq * 1.5 : baseFreq;
|
|
187
|
+
// Volume also increases with boost
|
|
188
|
+
const targetVolume = isBoosting ? 0.25 : 0.12;
|
|
189
|
+
|
|
190
|
+
this.engine.setFrequency(targetFreq, 0.1);
|
|
191
|
+
this.engine.setVolume(targetVolume, 0.1);
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
stopEngine() {
|
|
195
|
+
if (this.engine) {
|
|
196
|
+
this.engine.stop(0.5);
|
|
197
|
+
this.engine = null;
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Boost activation sound
|
|
203
|
+
*/
|
|
204
|
+
boost() {
|
|
205
|
+
if (!initialized) return;
|
|
206
|
+
|
|
207
|
+
// Short energetic burst
|
|
208
|
+
Synth.osc.sweep(400, 1200, 0.2, { type: "square", volume: 0.2 });
|
|
209
|
+
Synth.osc.tone(800, 0.1, { type: "sine", volume: 0.15 });
|
|
210
|
+
},
|
|
211
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Void Particle
|
|
3
|
+
*
|
|
4
|
+
* Purple particles that appear inside the black hole void.
|
|
5
|
+
* Collect 10 to escape back to the origin.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { CONFIG } from "./constants.js";
|
|
9
|
+
|
|
10
|
+
export class VoidParticle {
|
|
11
|
+
constructor(x, y) {
|
|
12
|
+
// Screen coordinates (not Penrose - we're in the void!)
|
|
13
|
+
this.x = x;
|
|
14
|
+
this.y = y;
|
|
15
|
+
|
|
16
|
+
// Animation
|
|
17
|
+
this.pulsePhase = Math.random() * Math.PI * 2;
|
|
18
|
+
this.driftX = (Math.random() - 0.5) * 20; // Slow drift
|
|
19
|
+
this.driftY = (Math.random() - 0.5) * 20;
|
|
20
|
+
|
|
21
|
+
// Size
|
|
22
|
+
this.radius = CONFIG.voidParticleRadius;
|
|
23
|
+
|
|
24
|
+
// State
|
|
25
|
+
this.collected = false;
|
|
26
|
+
this.collectAnimation = 0; // For collection effect
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
update(dt) {
|
|
30
|
+
this.pulsePhase += dt * 5;
|
|
31
|
+
|
|
32
|
+
// Slow drift
|
|
33
|
+
this.x += this.driftX * dt;
|
|
34
|
+
this.y += this.driftY * dt;
|
|
35
|
+
|
|
36
|
+
// Collection animation
|
|
37
|
+
if (this.collected) {
|
|
38
|
+
this.collectAnimation += dt * 3;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get circle bounds for collision detection (screen coordinates)
|
|
44
|
+
*/
|
|
45
|
+
getCircle() {
|
|
46
|
+
return { x: this.x, y: this.y, radius: this.radius };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if particle is active (not collected)
|
|
51
|
+
*/
|
|
52
|
+
get active() {
|
|
53
|
+
return !this.collected;
|
|
54
|
+
}
|
|
55
|
+
}
|