@where-stars-drift/core 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 (160) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +143 -0
  3. package/dist/src/base/bounded-body.d.ts +8 -0
  4. package/dist/src/base/bounded-body.js +10 -0
  5. package/dist/src/base/celestial-body.d.ts +12 -0
  6. package/dist/src/base/celestial-body.js +11 -0
  7. package/dist/src/base/hoverable.d.ts +10 -0
  8. package/dist/src/base/hoverable.js +4 -0
  9. package/dist/src/base/massive-body.d.ts +8 -0
  10. package/dist/src/base/massive-body.js +10 -0
  11. package/dist/src/config/effects.d.ts +14 -0
  12. package/dist/src/config/effects.js +14 -0
  13. package/dist/src/config/grid.d.ts +10 -0
  14. package/dist/src/config/grid.js +10 -0
  15. package/dist/src/config/panel.d.ts +26 -0
  16. package/dist/src/config/panel.js +26 -0
  17. package/dist/src/config/simulation.d.ts +21 -0
  18. package/dist/src/config/simulation.js +23 -0
  19. package/dist/src/controllers/clan-controller.d.ts +7 -0
  20. package/dist/src/controllers/clan-controller.js +12 -0
  21. package/dist/src/controllers/debug-controller.d.ts +45 -0
  22. package/dist/src/controllers/debug-controller.js +82 -0
  23. package/dist/src/controllers/effects-controller.d.ts +17 -0
  24. package/dist/src/controllers/effects-controller.js +71 -0
  25. package/dist/src/controllers/hover-controller.d.ts +11 -0
  26. package/dist/src/controllers/hover-controller.js +107 -0
  27. package/dist/src/controllers/layer-controller.d.ts +16 -0
  28. package/dist/src/controllers/layer-controller.js +64 -0
  29. package/dist/src/controllers/physics-controller.d.ts +14 -0
  30. package/dist/src/controllers/physics-controller.js +152 -0
  31. package/dist/src/controllers/star-controller.d.ts +12 -0
  32. package/dist/src/controllers/star-controller.js +38 -0
  33. package/dist/src/controllers/starship-controller.d.ts +17 -0
  34. package/dist/src/controllers/starship-controller.js +58 -0
  35. package/dist/src/draw-debug-line.d.ts +9 -0
  36. package/dist/src/draw-debug-line.js +29 -0
  37. package/dist/src/entities/black-hole-factory.d.ts +15 -0
  38. package/dist/src/entities/black-hole-factory.js +23 -0
  39. package/dist/src/entities/black-hole-shapes.d.ts +9 -0
  40. package/dist/src/entities/black-hole-shapes.js +224 -0
  41. package/dist/src/entities/black-hole.d.ts +69 -0
  42. package/dist/src/entities/black-hole.js +210 -0
  43. package/dist/src/entities/clan-manager.d.ts +12 -0
  44. package/dist/src/entities/clan-manager.js +22 -0
  45. package/dist/src/entities/clans.d.ts +15 -0
  46. package/dist/src/entities/clans.js +76 -0
  47. package/dist/src/entities/comet.d.ts +27 -0
  48. package/dist/src/entities/comet.js +81 -0
  49. package/dist/src/entities/docking-point.d.ts +20 -0
  50. package/dist/src/entities/docking-point.js +22 -0
  51. package/dist/src/entities/fleet.d.ts +45 -0
  52. package/dist/src/entities/fleet.js +374 -0
  53. package/dist/src/entities/formations.d.ts +51 -0
  54. package/dist/src/entities/formations.js +340 -0
  55. package/dist/src/entities/meteor.d.ts +26 -0
  56. package/dist/src/entities/meteor.js +48 -0
  57. package/dist/src/entities/nebula.d.ts +18 -0
  58. package/dist/src/entities/nebula.js +43 -0
  59. package/dist/src/entities/orbit.d.ts +23 -0
  60. package/dist/src/entities/orbit.js +43 -0
  61. package/dist/src/entities/pulsar.d.ts +18 -0
  62. package/dist/src/entities/pulsar.js +41 -0
  63. package/dist/src/entities/ring.d.ts +13 -0
  64. package/dist/src/entities/ring.js +26 -0
  65. package/dist/src/entities/ringed-planet.d.ts +21 -0
  66. package/dist/src/entities/ringed-planet.js +68 -0
  67. package/dist/src/entities/sector-grid.d.ts +16 -0
  68. package/dist/src/entities/sector-grid.js +70 -0
  69. package/dist/src/entities/star-factory.d.ts +29 -0
  70. package/dist/src/entities/star-factory.js +47 -0
  71. package/dist/src/entities/star.d.ts +48 -0
  72. package/dist/src/entities/star.js +167 -0
  73. package/dist/src/entities/starship-classes.d.ts +0 -0
  74. package/dist/src/entities/starship-classes.js +2 -0
  75. package/dist/src/entities/starship.d.ts +91 -0
  76. package/dist/src/entities/starship.js +760 -0
  77. package/dist/src/entities/supernova.d.ts +26 -0
  78. package/dist/src/entities/supernova.js +54 -0
  79. package/dist/src/index.d.ts +20 -0
  80. package/dist/src/index.js +19 -0
  81. package/dist/src/lib/energy-stream.d.ts +5 -0
  82. package/dist/src/lib/energy-stream.js +98 -0
  83. package/dist/src/lib/quadtree.d.ts +31 -0
  84. package/dist/src/lib/quadtree.js +124 -0
  85. package/dist/src/lib/simplified-stream.d.ts +6 -0
  86. package/dist/src/lib/simplified-stream.js +19 -0
  87. package/dist/src/types.d.ts +14 -0
  88. package/dist/src/types.js +1 -0
  89. package/dist/src/ui/black-holes-panel.d.ts +2 -0
  90. package/dist/src/ui/black-holes-panel.js +76 -0
  91. package/dist/src/ui/clans-panel.d.ts +2 -0
  92. package/dist/src/ui/clans-panel.js +20 -0
  93. package/dist/src/ui/debug-panel-controller.d.ts +41 -0
  94. package/dist/src/ui/debug-panel-controller.js +285 -0
  95. package/dist/src/ui/fleets-panel.d.ts +2 -0
  96. package/dist/src/ui/fleets-panel.js +127 -0
  97. package/dist/src/ui/formations-panel.d.ts +3 -0
  98. package/dist/src/ui/formations-panel.js +129 -0
  99. package/dist/src/ui/panel-config.d.ts +26 -0
  100. package/dist/src/ui/panel-config.js +26 -0
  101. package/dist/src/ui/ships-panel.d.ts +12 -0
  102. package/dist/src/ui/ships-panel.js +61 -0
  103. package/dist/src/ui/stars-panel.d.ts +2 -0
  104. package/dist/src/ui/stars-panel.js +120 -0
  105. package/dist/src/where-stars-drift.d.ts +71 -0
  106. package/dist/src/where-stars-drift.js +440 -0
  107. package/dist/tsconfig.tsbuildinfo +1 -0
  108. package/package.json +35 -0
  109. package/src/base/bounded-body.ts +14 -0
  110. package/src/base/celestial-body.ts +20 -0
  111. package/src/base/hoverable.ts +11 -0
  112. package/src/base/massive-body.ts +14 -0
  113. package/src/config/effects.ts +15 -0
  114. package/src/config/grid.ts +11 -0
  115. package/src/config/panel.ts +26 -0
  116. package/src/config/simulation.ts +25 -0
  117. package/src/controllers/clan-controller.ts +19 -0
  118. package/src/controllers/debug-controller.ts +112 -0
  119. package/src/controllers/effects-controller.ts +86 -0
  120. package/src/controllers/hover-controller.ts +128 -0
  121. package/src/controllers/layer-controller.ts +78 -0
  122. package/src/controllers/physics-controller.ts +173 -0
  123. package/src/controllers/star-controller.ts +51 -0
  124. package/src/controllers/starship-controller.ts +76 -0
  125. package/src/draw-debug-line.ts +37 -0
  126. package/src/entities/black-hole-factory.ts +28 -0
  127. package/src/entities/black-hole-shapes.ts +276 -0
  128. package/src/entities/black-hole.ts +246 -0
  129. package/src/entities/clan-manager.ts +33 -0
  130. package/src/entities/clans.ts +98 -0
  131. package/src/entities/comet.ts +102 -0
  132. package/src/entities/docking-point.ts +34 -0
  133. package/src/entities/fleet.ts +446 -0
  134. package/src/entities/formations.ts +423 -0
  135. package/src/entities/meteor.ts +59 -0
  136. package/src/entities/nebula.ts +50 -0
  137. package/src/entities/orbit.ts +53 -0
  138. package/src/entities/pulsar.ts +64 -0
  139. package/src/entities/ring.ts +42 -0
  140. package/src/entities/ringed-planet.ts +85 -0
  141. package/src/entities/sector-grid.ts +81 -0
  142. package/src/entities/star-factory.ts +59 -0
  143. package/src/entities/star.ts +222 -0
  144. package/src/entities/starship-classes.ts +1 -0
  145. package/src/entities/starship.ts +906 -0
  146. package/src/entities/supernova.ts +75 -0
  147. package/src/index.ts +24 -0
  148. package/src/lib/energy-stream.ts +127 -0
  149. package/src/lib/quadtree.ts +159 -0
  150. package/src/lib/simplified-stream.ts +28 -0
  151. package/src/types.ts +16 -0
  152. package/src/ui/black-holes-panel.ts +91 -0
  153. package/src/ui/clans-panel.ts +27 -0
  154. package/src/ui/debug-panel-controller.ts +339 -0
  155. package/src/ui/fleets-panel.ts +153 -0
  156. package/src/ui/formations-panel.ts +155 -0
  157. package/src/ui/panel-config.ts +26 -0
  158. package/src/ui/ships-panel.ts +85 -0
  159. package/src/ui/stars-panel.ts +146 -0
  160. package/src/where-stars-drift.ts +542 -0
@@ -0,0 +1,224 @@
1
+ /**
2
+ * @fileoverview Defines all black hole shape rendering functions.
3
+ */
4
+ import { BLACK_HOLE_CONFIG } from './black-hole';
5
+ export const drawStandardShape = (ctx, hole) => {
6
+ // A simple white glow from behind
7
+ const ringRadius = hole.radius * 1.5;
8
+ ctx.save();
9
+ ctx.shadowColor = `rgba(255, 255, 255, 0.7)`;
10
+ ctx.shadowBlur = 20;
11
+ // Draw the black hole itself, the shadow will create the glow
12
+ ctx.beginPath();
13
+ ctx.fillStyle = hole.color;
14
+ ctx.arc(hole.x, hole.y, hole.radius, 0, Math.PI * 2);
15
+ ctx.fill();
16
+ ctx.restore();
17
+ };
18
+ export const drawWarpedDiskShape = (ctx, hole) => {
19
+ const config = BLACK_HOLE_CONFIG.WARPED_DISK;
20
+ const diskRadius = hole.radius * 2.5;
21
+ // 1. Draw outer, faint, warped lines
22
+ ctx.save();
23
+ ctx.translate(hole.x, hole.y);
24
+ ctx.globalCompositeOperation = 'lighter';
25
+ for (let i = 0; i < config.WARPED_LINE_COUNT; i++) {
26
+ const progress = i / config.WARPED_LINE_COUNT;
27
+ const startRadius = diskRadius * (1.5 + progress * 2);
28
+ const endRadius = diskRadius * (1.1 - progress * 0.2);
29
+ const startAngle = hole.warpAngle + progress * Math.PI * 0.4;
30
+ const endAngle = hole.warpAngle + Math.PI * 0.2 + progress * Math.PI * 0.8;
31
+ const startX = Math.cos(startAngle) * startRadius;
32
+ const startY = Math.sin(startAngle) * startRadius;
33
+ const endX = Math.cos(endAngle) * endRadius;
34
+ const endY = Math.sin(endAngle) * endRadius;
35
+ const cp1x = Math.cos(startAngle + 0.5) * startRadius * 0.8;
36
+ const cp1y = Math.sin(startAngle + 0.5) * startRadius * 0.8;
37
+ const cp2x = Math.cos(endAngle - 0.5) * endRadius * 1.2;
38
+ const cp2y = Math.sin(endAngle - 0.5) * endRadius * 1.2;
39
+ ctx.beginPath();
40
+ ctx.moveTo(startX, startY);
41
+ ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endX, endY);
42
+ ctx.strokeStyle = `rgba(255, 200, 150, ${0.05 + progress * 0.1})`;
43
+ ctx.lineWidth = 0.5 + progress * 1.0;
44
+ ctx.stroke();
45
+ }
46
+ ctx.restore();
47
+ // 2. Draw main accretion disk
48
+ ctx.save();
49
+ ctx.translate(hole.x, hole.y);
50
+ const gradient = ctx.createRadialGradient(0, 0, hole.radius, 0, 0, diskRadius);
51
+ gradient.addColorStop(0.3, config.DISK_COLOR_INNER);
52
+ gradient.addColorStop(0.6, config.DISK_COLOR_OUTER);
53
+ gradient.addColorStop(1, 'rgba(255, 150, 0, 0)');
54
+ ctx.strokeStyle = gradient;
55
+ ctx.lineWidth = hole.radius * 1.5;
56
+ ctx.shadowColor = config.DISK_COLOR_INNER;
57
+ ctx.shadowBlur = 30;
58
+ ctx.beginPath();
59
+ ctx.arc(0, 0, diskRadius, 0, Math.PI * 2);
60
+ ctx.stroke();
61
+ ctx.restore();
62
+ // 3. Draw the black hole itself
63
+ ctx.beginPath();
64
+ ctx.fillStyle = hole.color;
65
+ ctx.arc(hole.x, hole.y, hole.radius, 0, Math.PI * 2);
66
+ ctx.fill();
67
+ // 4. Draw the diagonal "cut" which creates the 3D effect
68
+ ctx.save();
69
+ ctx.translate(hole.x, hole.y);
70
+ ctx.fillStyle = 'rgba(0,0,0,1)';
71
+ ctx.shadowColor = 'black';
72
+ ctx.shadowBlur = 5;
73
+ ctx.fillRect(-diskRadius * 1.5, -hole.radius * 0.1, diskRadius * 3, hole.radius * 0.2);
74
+ ctx.restore();
75
+ ctx.shadowBlur = 0;
76
+ };
77
+ export const drawLensingShape = (ctx, hole) => {
78
+ // Gravitational Lensing Effect
79
+ const ringRadius = hole.radius * 1.5;
80
+ ctx.save();
81
+ ctx.shadowColor = `rgba(255, 255, 255, 0.5)`;
82
+ ctx.shadowBlur = 50;
83
+ ctx.beginPath();
84
+ ctx.strokeStyle = `rgba(255, 255, 255, 0.9)`;
85
+ ctx.lineWidth = hole.radius * 0.4;
86
+ ctx.arc(hole.x, hole.y, ringRadius, 0, Math.PI * 2);
87
+ ctx.stroke();
88
+ ctx.restore();
89
+ // Draw the black hole itself
90
+ ctx.beginPath();
91
+ ctx.fillStyle = hole.color;
92
+ ctx.arc(hole.x, hole.y, hole.radius, 0, Math.PI * 2);
93
+ ctx.fill();
94
+ };
95
+ export const drawAccretionDiskShape = (ctx, hole) => {
96
+ const diskRadius = hole.radius * 2.5;
97
+ const glowRadius = diskRadius * 0.6;
98
+ const config = BLACK_HOLE_CONFIG.ACCRETION_DISK;
99
+ // --- Draw Relativistic Jets ---
100
+ ctx.save();
101
+ ctx.translate(hole.x, hole.y);
102
+ ctx.rotate(hole.accretionDiskAngle);
103
+ ctx.globalCompositeOperation = 'lighter';
104
+ // Animate the nozzle length only every 5 frames for performance
105
+ if (hole.frameCount % 5 === 0) {
106
+ const maxNozzleLength = diskRadius * 1.1;
107
+ // nozzleLengthMultiplier will be between 0.5 (50%) and 1.0 (100%)
108
+ const nozzleLengthMultiplier = Math.random() * 0.5 + 0.5;
109
+ hole.nozzleLength = maxNozzleLength * nozzleLengthMultiplier;
110
+ }
111
+ // Always draw the jets using the cached nozzleLength
112
+ if (hole.nozzleLength > 0) {
113
+ ctx.shadowColor = 'white';
114
+ ctx.shadowBlur = 15;
115
+ const nozzleBaseWidth = hole.radius * 2; // Base is 2x the hole's radius
116
+ const jetColor = `rgba(200, 220, 255, ${0.1 + (hole.nozzleLength / (diskRadius * 1.1)) * 0.3})`;
117
+ ctx.fillStyle = jetColor;
118
+ // Jet 1 (positive x direction)
119
+ ctx.beginPath();
120
+ ctx.moveTo(hole.nozzleLength, 0); // Tip of the jet
121
+ ctx.lineTo(0, nozzleBaseWidth / 2); // Base centered at the hole
122
+ ctx.lineTo(0, -nozzleBaseWidth / 2); // Base centered at the hole
123
+ ctx.closePath();
124
+ ctx.fill();
125
+ // Jet 2 (negative x direction)
126
+ ctx.beginPath();
127
+ ctx.moveTo(-hole.nozzleLength, 0); // Tip of the jet
128
+ ctx.lineTo(0, nozzleBaseWidth / 2); // Base centered at the hole
129
+ ctx.lineTo(0, -nozzleBaseWidth / 2); // Base centered at the hole
130
+ ctx.closePath();
131
+ ctx.fill();
132
+ }
133
+ ctx.restore();
134
+ // 1. Draw the outer glow
135
+ ctx.save();
136
+ ctx.translate(hole.x, hole.y);
137
+ const glowGradient = ctx.createRadialGradient(0, 0, hole.radius, 0, 0, glowRadius);
138
+ glowGradient.addColorStop(0.2, config.GLOW_COLOR);
139
+ glowGradient.addColorStop(1, 'rgba(180, 210, 255, 0)');
140
+ ctx.fillStyle = glowGradient;
141
+ ctx.shadowColor = config.GLOW_COLOR;
142
+ ctx.shadowBlur = 30;
143
+ ctx.beginPath();
144
+ ctx.arc(0, 0, glowRadius, 0, Math.PI * 2);
145
+ ctx.fill();
146
+ ctx.restore();
147
+ // 2. Draw the black hole event horizon
148
+ ctx.beginPath();
149
+ ctx.fillStyle = hole.color;
150
+ ctx.arc(hole.x, hole.y, hole.radius, 0, Math.PI * 2);
151
+ ctx.fill();
152
+ // 3. Draw the flat white line crossing the front
153
+ ctx.save();
154
+ ctx.translate(hole.x, hole.y);
155
+ ctx.rotate(hole.accretionDiskAngle);
156
+ const lineGradient = ctx.createLinearGradient(-diskRadius * 1.1, 0, diskRadius * 1.1, 0);
157
+ lineGradient.addColorStop(0, 'rgba(255, 255, 255, 0)');
158
+ lineGradient.addColorStop(0.25, 'rgba(255, 255, 255, 0.95)');
159
+ lineGradient.addColorStop(0.75, 'rgba(255, 255, 255, 0.95)');
160
+ lineGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
161
+ ctx.fillStyle = lineGradient;
162
+ ctx.shadowColor = 'white';
163
+ ctx.shadowBlur = 15;
164
+ const lineHeight = hole.radius * 0.35;
165
+ ctx.fillRect(-diskRadius * 1.1, -lineHeight / 2, diskRadius * 2.2, lineHeight);
166
+ ctx.restore();
167
+ ctx.shadowBlur = 0;
168
+ };
169
+ export const drawVortexShape = (ctx, hole) => {
170
+ const config = BLACK_HOLE_CONFIG.VORTEX;
171
+ const outerRadius = hole.radius * 5;
172
+ // 1. Draw the vast, faint outer glow
173
+ ctx.save();
174
+ ctx.translate(hole.x, hole.y);
175
+ const outerGradient = ctx.createRadialGradient(0, 0, hole.radius, 0, 0, outerRadius);
176
+ outerGradient.addColorStop(0.3, config.VORTEX_COLOR_MID);
177
+ outerGradient.addColorStop(1, config.VORTEX_COLOR_OUTER);
178
+ ctx.fillStyle = outerGradient;
179
+ ctx.beginPath();
180
+ ctx.arc(0, 0, outerRadius, 0, Math.PI * 2);
181
+ ctx.fill();
182
+ ctx.restore();
183
+ // 2. Draw the swirling, fiery arms
184
+ ctx.save();
185
+ ctx.translate(hole.x, hole.y);
186
+ ctx.globalCompositeOperation = 'lighter';
187
+ for (let i = 0; i < config.PARTICLE_COUNT; i++) {
188
+ const progress = i / config.PARTICLE_COUNT;
189
+ const angle = hole.vortexAngle + progress * Math.PI * 2;
190
+ const startRadius = hole.radius * (1.2 + progress * 1.5);
191
+ const endRadius = hole.radius * (1.0 + progress * 1.0);
192
+ const flameColor = i % 2 === 0 ? `rgba(255, 220, 150, 0.15)` : `rgba(255, 180, 50, 0.1)`;
193
+ ctx.beginPath();
194
+ ctx.arc(0, 0, startRadius, angle, angle + Math.PI * 0.8);
195
+ ctx.arc(0, 0, endRadius, angle + Math.PI * 0.8, angle, true);
196
+ ctx.closePath();
197
+ ctx.fillStyle = flameColor;
198
+ ctx.shadowColor = 'rgba(255, 150, 0, 0.5)';
199
+ ctx.shadowBlur = 45;
200
+ ctx.fill();
201
+ }
202
+ ctx.restore();
203
+ // 3. Draw the bright inner ring
204
+ ctx.save();
205
+ ctx.translate(hole.x, hole.y);
206
+ const innerRingRadius = hole.radius * 1.2;
207
+ const innerGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, innerRingRadius);
208
+ innerGradient.addColorStop(0, 'rgba(255, 255, 255, 0.9)');
209
+ innerGradient.addColorStop(0.7, config.VORTEX_COLOR_INNER);
210
+ innerGradient.addColorStop(1, 'rgba(255, 200, 0, 0)');
211
+ ctx.fillStyle = innerGradient;
212
+ ctx.shadowColor = 'rgba(255, 255, 255, 0.7)';
213
+ ctx.shadowBlur = 20;
214
+ ctx.beginPath();
215
+ ctx.arc(0, 0, innerRingRadius, 0, Math.PI * 2);
216
+ ctx.fill();
217
+ ctx.restore();
218
+ // 4. Draw the central black hole
219
+ ctx.beginPath();
220
+ ctx.fillStyle = hole.color;
221
+ ctx.arc(hole.x, hole.y, hole.radius, 0, Math.PI * 2);
222
+ ctx.fill();
223
+ ctx.shadowBlur = 0;
224
+ };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @fileoverview Defines the BlackHle class for the night sky simulation.
3
+ */
4
+ import { MassiveBody } from '../base/massive-body';
5
+ import type { Hoverable } from '../base/hoverable';
6
+ import { Star } from './star';
7
+ export declare const BLACK_HOLE_CONFIG: {
8
+ RADIUS_BASE: number;
9
+ RADIUS_VARIANCE: number;
10
+ MASS_MULTIPLIER: number;
11
+ HOVER_TIMEOUT: number;
12
+ INITIAL_VELOCITY_RANGE: number;
13
+ ATTRACTION_RADIUS: number;
14
+ STAR_ATTRACTION_RADIUS: number;
15
+ DECELERATION_DURATION: number;
16
+ PAUSE_DURATION: number;
17
+ ENERGY_STREAM_DISTANCE: number;
18
+ PULSE_FREQUENCY_MIN: number;
19
+ PULSE_FREQUENCY_VARIANCE: number;
20
+ ACCRETION_DISK: {
21
+ GLOW_COLOR: string;
22
+ };
23
+ VORTEX: {
24
+ PARTICLE_COUNT: number;
25
+ HOTSPOT_COUNT: number;
26
+ VORTEX_COLOR_INNER: string;
27
+ VORTEX_COLOR_MID: string;
28
+ VORTEX_COLOR_OUTER: string;
29
+ };
30
+ WARPED_DISK: {
31
+ WARPED_LINE_COUNT: number;
32
+ DISK_COLOR_INNER: string;
33
+ DISK_COLOR_OUTER: string;
34
+ };
35
+ };
36
+ export type BlackHoleType = 'LENSING' | 'ACCRETION_DISK' | 'VORTEX' | 'WARPED_DISK' | 'STANDARD';
37
+ type MergeState = 'IDLE' | 'DECELERATING' | 'PAUSED';
38
+ export declare class BlackHole extends MassiveBody implements Hoverable {
39
+ id: number;
40
+ name: string;
41
+ radius: number;
42
+ baseRadius: number;
43
+ color: string;
44
+ isHovered: boolean;
45
+ hoverRadius: number;
46
+ hoverTimer: number;
47
+ eatenStars: number;
48
+ type: BlackHoleType;
49
+ vortexAngle: number;
50
+ warpAngle: number;
51
+ accretionDiskAngle: number;
52
+ mergeState: MergeState;
53
+ mergeStateTimer: number;
54
+ preDecelerationVx: number;
55
+ preDecelerationVy: number;
56
+ attractionTargets: BlackHole[];
57
+ pulseFrequency: number;
58
+ pulsePhase: number;
59
+ nozzleLength: number;
60
+ nozzleColor: string | CanvasGradient | null;
61
+ frameCount: number;
62
+ constructor(x: number, y: number, type: BlackHoleType);
63
+ absorb(body: Star | BlackHole): void;
64
+ reset(x: number, y: number, vx: number, vy: number): void;
65
+ update(ctx: CanvasRenderingContext2D, width: number, height: number, frameCount: number): void;
66
+ private drawDebugInfo;
67
+ draw(ctx: CanvasRenderingContext2D, pulseTime: number, frameCount: number): void;
68
+ }
69
+ export { BlackHole as BlackHle };
@@ -0,0 +1,210 @@
1
+ /**
2
+ * @fileoverview Defines the BlackHle class for the night sky simulation.
3
+ */
4
+ import { MassiveBody } from '../base/massive-body';
5
+ import { DEBUG_CONFIG } from '../config/simulation';
6
+ import { Star } from './star';
7
+ import { drawAccretionDiskShape, drawLensingShape, drawVortexShape, drawWarpedDiskShape, drawStandardShape } from './black-hole-shapes';
8
+ import { drawEnergyStream } from '../lib/energy-stream';
9
+ import { EFFECTS_CONFIG } from '../config/effects';
10
+ // --- Black Hole Configuration ---
11
+ export const BLACK_HOLE_CONFIG = {
12
+ RADIUS_BASE: 8,
13
+ RADIUS_VARIANCE: 4,
14
+ MASS_MULTIPLIER: 500,
15
+ HOVER_TIMEOUT: 120,
16
+ INITIAL_VELOCITY_RANGE: 0.025,
17
+ ATTRACTION_RADIUS: 100, // Only attract other black holes within this distance
18
+ STAR_ATTRACTION_RADIUS: 100, // Only attract stars within this distance
19
+ DECELERATION_DURATION: 180, // 3 seconds at 60fps
20
+ PAUSE_DURATION: 1200, // 20 seconds at 60fps
21
+ ENERGY_STREAM_DISTANCE: 300,
22
+ PULSE_FREQUENCY_MIN: 0.5,
23
+ PULSE_FREQUENCY_VARIANCE: 1.0,
24
+ ACCRETION_DISK: {
25
+ GLOW_COLOR: 'rgba(180, 210, 255, 0.4)',
26
+ },
27
+ VORTEX: {
28
+ PARTICLE_COUNT: 40,
29
+ HOTSPOT_COUNT: 5,
30
+ VORTEX_COLOR_INNER: 'rgba(255, 100, 0, 0.8)',
31
+ VORTEX_COLOR_MID: 'rgba(200, 50, 0, 0.3)',
32
+ VORTEX_COLOR_OUTER: 'rgba(150, 20, 0, 0)',
33
+ },
34
+ WARPED_DISK: {
35
+ WARPED_LINE_COUNT: 10,
36
+ DISK_COLOR_INNER: 'rgba(255, 220, 150, 1)',
37
+ DISK_COLOR_OUTER: 'rgba(255, 150, 0, 0.8)',
38
+ }
39
+ };
40
+ let blackHoleCounter = 0;
41
+ export class BlackHole extends MassiveBody {
42
+ constructor(x, y, type) {
43
+ const radius = Math.random() * BLACK_HOLE_CONFIG.RADIUS_VARIANCE + BLACK_HOLE_CONFIG.RADIUS_BASE;
44
+ const mass = radius * radius * BLACK_HOLE_CONFIG.MASS_MULTIPLIER;
45
+ super(x, y, radius, mass);
46
+ this.isHovered = false;
47
+ this.vortexAngle = 0;
48
+ this.warpAngle = 0;
49
+ this.accretionDiskAngle = 0;
50
+ this.mergeState = 'IDLE';
51
+ this.mergeStateTimer = 0;
52
+ this.preDecelerationVx = 0;
53
+ this.preDecelerationVy = 0;
54
+ this.attractionTargets = [];
55
+ this.nozzleLength = 0; // Cached nozzle length for stream effect
56
+ this.nozzleColor = null; // Cached nozzle color for stream effect
57
+ this.frameCount = 0; // Frame count for animations
58
+ this.id = ++blackHoleCounter;
59
+ this.name = `BH-${this.id}`;
60
+ this.radius = radius;
61
+ this.baseRadius = radius;
62
+ this.color = '#000000';
63
+ this.hoverTimer = 0;
64
+ this.hoverRadius = this.radius * 5;
65
+ this.eatenStars = 0;
66
+ this.type = type;
67
+ this.vx = Math.random() * BLACK_HOLE_CONFIG.INITIAL_VELOCITY_RANGE * 2 - BLACK_HOLE_CONFIG.INITIAL_VELOCITY_RANGE;
68
+ this.vy = Math.random() * BLACK_HOLE_CONFIG.INITIAL_VELOCITY_RANGE * 2 - BLACK_HOLE_CONFIG.INITIAL_VELOCITY_RANGE;
69
+ this.pulseFrequency = Math.random() * BLACK_HOLE_CONFIG.PULSE_FREQUENCY_VARIANCE + BLACK_HOLE_CONFIG.PULSE_FREQUENCY_MIN;
70
+ this.pulsePhase = Math.random() * Math.PI * 2;
71
+ }
72
+ absorb(body) {
73
+ if (body instanceof Star) {
74
+ this.eatenStars++;
75
+ }
76
+ else if (body instanceof BlackHole) {
77
+ this.mergeState = 'DECELERATING';
78
+ this.mergeStateTimer = BLACK_HOLE_CONFIG.DECELERATION_DURATION;
79
+ this.preDecelerationVx = this.vx;
80
+ this.preDecelerationVy = this.vy;
81
+ }
82
+ this.mass += body.mass;
83
+ this.radius = Math.sqrt(this.mass / BLACK_HOLE_CONFIG.MASS_MULTIPLIER);
84
+ this.boundingRadius = this.radius;
85
+ this.hoverRadius = this.radius * 5;
86
+ }
87
+ reset(x, y, vx, vy) {
88
+ const newRadius = Math.random() * BLACK_HOLE_CONFIG.RADIUS_VARIANCE + BLACK_HOLE_CONFIG.RADIUS_BASE;
89
+ this.mass = newRadius * newRadius * BLACK_HOLE_CONFIG.MASS_MULTIPLIER;
90
+ this.radius = newRadius;
91
+ this.x = x;
92
+ this.y = y;
93
+ this.vx = vx;
94
+ this.vy = vy;
95
+ this.eatenStars = 0;
96
+ this.mergeState = 'IDLE';
97
+ this.mergeStateTimer = 0;
98
+ }
99
+ update(ctx, width, height, frameCount) {
100
+ this.frameCount = frameCount;
101
+ // State machine for post-merger behavior
102
+ if (this.mergeState === 'DECELERATING') {
103
+ const decelerationFactor = Math.max(0, this.mergeStateTimer / BLACK_HOLE_CONFIG.DECELERATION_DURATION);
104
+ this.vx = this.preDecelerationVx * decelerationFactor;
105
+ this.vy = this.preDecelerationVy * decelerationFactor;
106
+ }
107
+ if (this.mergeState !== 'IDLE') {
108
+ this.mergeStateTimer--;
109
+ if (this.mergeStateTimer <= 0) {
110
+ if (this.mergeState === 'DECELERATING') {
111
+ this.vx = 0;
112
+ this.vy = 0;
113
+ this.mergeState = 'PAUSED';
114
+ this.mergeStateTimer = BLACK_HOLE_CONFIG.PAUSE_DURATION;
115
+ }
116
+ else if (this.mergeState === 'PAUSED') {
117
+ this.mergeState = 'IDLE';
118
+ this.vx = Math.random() * BLACK_HOLE_CONFIG.INITIAL_VELOCITY_RANGE * 2 - BLACK_HOLE_CONFIG.INITIAL_VELOCITY_RANGE;
119
+ this.vy = Math.random() * BLACK_HOLE_CONFIG.INITIAL_VELOCITY_RANGE * 2 - BLACK_HOLE_CONFIG.INITIAL_VELOCITY_RANGE;
120
+ }
121
+ }
122
+ }
123
+ this.x += this.vx;
124
+ this.y += this.vy;
125
+ if (this.x < -this.radius)
126
+ this.x = width + this.radius;
127
+ if (this.x > width + this.radius)
128
+ this.x = -this.radius;
129
+ if (this.y < -this.radius)
130
+ this.y = height + this.radius;
131
+ if (this.y > height + this.radius)
132
+ this.y = -this.radius;
133
+ if (this.type === 'VORTEX') {
134
+ this.vortexAngle += 0.01;
135
+ }
136
+ if (this.type === 'WARPED_DISK') {
137
+ this.warpAngle += 0.002;
138
+ }
139
+ if (this.type === 'ACCRETION_DISK') {
140
+ this.accretionDiskAngle += 0.005 / 3;
141
+ }
142
+ }
143
+ drawDebugInfo(ctx) {
144
+ const startX = this.x + this.hoverRadius + 15;
145
+ const boxPadding = 10;
146
+ const lineHeight = 14;
147
+ const infoLines = [
148
+ `Name: ${this.name}`,
149
+ `Type: ${this.constructor.name} (${this.type})`,
150
+ `Radius: ${this.radius.toFixed(2)}`,
151
+ `Mass: ${this.mass.toFixed(2)}`,
152
+ `Position: (${this.x.toFixed(0)}, ${this.y.toFixed(0)})`,
153
+ `Stars Eaten: ${this.eatenStars}`,
154
+ `Merge State: ${this.mergeState} (${this.mergeStateTimer})`
155
+ ];
156
+ const boxHeight = infoLines.length * lineHeight + boxPadding * 2;
157
+ const boxWidth = Math.max(...infoLines.map(line => ctx.measureText(line).width)) + boxPadding * 2;
158
+ const boxY = this.y - boxHeight / 2;
159
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
160
+ ctx.strokeStyle = '#fff';
161
+ ctx.lineWidth = 1;
162
+ ctx.beginPath();
163
+ ctx.roundRect(startX, boxY, boxWidth, boxHeight, 5);
164
+ ctx.fill();
165
+ ctx.stroke();
166
+ ctx.fillStyle = 'white';
167
+ ctx.font = '10px "Roboto Mono", monospace';
168
+ ctx.textAlign = 'left';
169
+ ctx.textBaseline = 'top';
170
+ infoLines.forEach((line, index) => {
171
+ ctx.fillText(line, startX + boxPadding, boxY + boxPadding + index * lineHeight);
172
+ });
173
+ }
174
+ draw(ctx, pulseTime, frameCount) {
175
+ this.frameCount = frameCount;
176
+ // 1. Draw streams from behind
177
+ if (EFFECTS_CONFIG.RENDER_INTER_HOLE_STREAMS && this.attractionTargets.length > 0) {
178
+ for (const target of this.attractionTargets) {
179
+ drawEnergyStream(ctx, this, target, pulseTime, frameCount);
180
+ }
181
+ }
182
+ // 2. Draw the main black hole shape
183
+ switch (this.type) {
184
+ case 'STANDARD':
185
+ drawStandardShape(ctx, this);
186
+ break;
187
+ case 'ACCRETION_DISK':
188
+ drawAccretionDiskShape(ctx, this);
189
+ break;
190
+ case 'VORTEX':
191
+ drawVortexShape(ctx, this);
192
+ break;
193
+ case 'WARPED_DISK':
194
+ drawWarpedDiskShape(ctx, this);
195
+ break;
196
+ case 'LENSING':
197
+ default:
198
+ drawLensingShape(ctx, this);
199
+ break;
200
+ }
201
+ // 3. Draw hover effect if hovered
202
+ this.isHovered = this.hoverTimer > 0;
203
+ if (this.isHovered) {
204
+ if (DEBUG_CONFIG.SHOW_DEBUG_INFO && DEBUG_CONFIG.SHOW_ENTITY_HOVER_INFO) {
205
+ this.drawDebugInfo(ctx);
206
+ }
207
+ }
208
+ }
209
+ }
210
+ export { BlackHole as BlackHle };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @fileoverview Defines the ClanManager for the night sky simulation.
3
+ */
4
+ import { type Clan } from './clans';
5
+ export declare class ClanManager {
6
+ private clans;
7
+ private lastAssignedClanIndex;
8
+ constructor();
9
+ private initializeClans;
10
+ getClans(): Clan[];
11
+ getClanForNewFleet(): Clan;
12
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @fileoverview Defines the ClanManager for the night sky simulation.
3
+ */
4
+ import { CLANS_CONFIG } from './clans';
5
+ export class ClanManager {
6
+ constructor() {
7
+ this.clans = [];
8
+ this.lastAssignedClanIndex = -1;
9
+ this.initializeClans();
10
+ }
11
+ initializeClans() {
12
+ this.clans = CLANS_CONFIG.map(config => (Object.assign(Object.assign({}, config), { fleets: [], starships: [], fleetsCount: 0, starshipsCount: 0 })));
13
+ }
14
+ getClans() {
15
+ return this.clans;
16
+ }
17
+ getClanForNewFleet() {
18
+ // Simple round-robin assignment for now
19
+ this.lastAssignedClanIndex = (this.lastAssignedClanIndex + 1) % this.clans.length;
20
+ return this.clans[this.lastAssignedClanIndex];
21
+ }
22
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @fileoverview Defines all clan properties for the night sky simulation.
3
+ */
4
+ import type { Fleet } from './fleet';
5
+ import type { Starship } from './starship';
6
+ export interface Clan {
7
+ name: string;
8
+ color: string;
9
+ icon: (ctx: CanvasRenderingContext2D, size: number) => void;
10
+ fleets: Fleet[];
11
+ starships: Starship[];
12
+ fleetsCount: number;
13
+ starshipsCount: number;
14
+ }
15
+ export declare const CLANS_CONFIG: Pick<Clan, 'name' | 'color' | 'icon'>[];
@@ -0,0 +1,76 @@
1
+ const drawIconCrescent = (ctx, size) => {
2
+ ctx.beginPath();
3
+ ctx.arc(0, 0, size / 2, 0, Math.PI * 2);
4
+ ctx.fill();
5
+ ctx.save();
6
+ ctx.globalCompositeOperation = 'destination-out';
7
+ ctx.beginPath();
8
+ ctx.arc(size * 0.2, 0, size / 2.5, 0, Math.PI * 2);
9
+ ctx.fill();
10
+ ctx.restore();
11
+ };
12
+ const drawIconSpikes = (ctx, size) => {
13
+ const points = 6;
14
+ ctx.beginPath();
15
+ for (let i = 0; i < points * 2; i++) {
16
+ const radius = i % 2 === 0 ? size / 2 : size / 4;
17
+ const angle = (i / points) * Math.PI;
18
+ ctx.lineTo(radius * Math.cos(angle), radius * Math.sin(angle));
19
+ }
20
+ ctx.closePath();
21
+ ctx.fill();
22
+ };
23
+ const drawIconTriangle = (ctx, size) => {
24
+ const h = size * (Math.sqrt(3) / 2);
25
+ ctx.beginPath();
26
+ ctx.moveTo(0, -h / 2);
27
+ ctx.lineTo(-size / 2, h / 2);
28
+ ctx.lineTo(size / 2, h / 2);
29
+ ctx.closePath();
30
+ ctx.stroke();
31
+ };
32
+ const drawIconHexagon = (ctx, size) => {
33
+ const r = size / 2;
34
+ ctx.beginPath();
35
+ ctx.moveTo(r * Math.cos(0), r * Math.sin(0));
36
+ for (let i = 1; i <= 6; i++) {
37
+ ctx.lineTo(r * Math.cos(i * 2 * Math.PI / 6), r * Math.sin(i * 2 * Math.PI / 6));
38
+ }
39
+ ctx.stroke();
40
+ };
41
+ const drawIconCross = (ctx, size) => {
42
+ const armLength = size / 2;
43
+ ctx.beginPath();
44
+ ctx.moveTo(-armLength, 0);
45
+ ctx.lineTo(armLength, 0);
46
+ ctx.moveTo(0, -armLength);
47
+ ctx.lineTo(0, armLength);
48
+ ctx.stroke();
49
+ };
50
+ export const CLANS_CONFIG = [
51
+ {
52
+ name: "Starkwood Sovereignty",
53
+ color: "#ADD8E6",
54
+ icon: drawIconCrescent
55
+ },
56
+ {
57
+ name: "Crimson Covenant",
58
+ color: "#FF6B6B",
59
+ icon: drawIconSpikes
60
+ },
61
+ {
62
+ name: "Golden Imperium",
63
+ color: "#FFD700",
64
+ icon: drawIconTriangle
65
+ },
66
+ {
67
+ name: "Azure Assembly",
68
+ color: "#87CEEB",
69
+ icon: drawIconHexagon
70
+ },
71
+ {
72
+ name: "Veridian Syndicate",
73
+ color: "#3D9970",
74
+ icon: drawIconCross
75
+ }
76
+ ];
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @fileoverview Defines the Comet class for the night sky simulation.
3
+ */
4
+ import { BoundedBody } from '../base/bounded-body';
5
+ export declare const COMET_CONFIG: {
6
+ COUNT: number;
7
+ DORMANT_LIFE_MIN: number;
8
+ DORMANT_LIFE_VARIANCE: number;
9
+ MIN_RADIUS: number;
10
+ RADIUS_VARIANCE: number;
11
+ SPEED_MIN: number;
12
+ SPEED_VARIANCE: number;
13
+ MAX_LIFE_MIN: number;
14
+ MAX_LIFE_VARIANCE: number;
15
+ };
16
+ export declare class Comet extends BoundedBody {
17
+ radius: number;
18
+ color: string;
19
+ life: number;
20
+ maxLife: number;
21
+ dormant: boolean;
22
+ dormantLife: number;
23
+ constructor(width: number, height: number);
24
+ reset(width: number, height: number): void;
25
+ update(ctx: CanvasRenderingContext2D, width: number, height: number): void;
26
+ draw(ctx: CanvasRenderingContext2D): void;
27
+ }