@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,75 @@
1
+ /**
2
+ * @fileoverview Defines the Supernova class for a visual pulse effect.
3
+ */
4
+
5
+ import type { BlackHle } from './black-hole';
6
+
7
+ // --- Supernova Configuration ---
8
+ export const SUPERNOVA_CONFIG = {
9
+ MAX_LIFE: 60, // 1 second in frames
10
+ RADIUS_MULTIPLIER: 2.5,
11
+ INITIAL_RADIUS_MULTIPLIER: 0.2,
12
+ COLOR: 'rgba(255, 255, 255, 0.8)',
13
+ SHADOW_BLUR: 30,
14
+ };
15
+
16
+ export class Supernova {
17
+ x: number;
18
+ y: number;
19
+ radius: number;
20
+ maxRadius: number;
21
+ life: number;
22
+ maxLife: number;
23
+ color: string;
24
+ initialRadius: number;
25
+ parent: BlackHle | null;
26
+
27
+ constructor(x: number, y: number, parentRadius: number, parent: BlackHle | null = null) {
28
+ this.x = x;
29
+ this.y = y;
30
+ this.initialRadius = parentRadius * SUPERNOVA_CONFIG.INITIAL_RADIUS_MULTIPLIER;
31
+ this.radius = this.initialRadius;
32
+ this.maxRadius = parentRadius * SUPERNOVA_CONFIG.RADIUS_MULTIPLIER;
33
+ this.maxLife = SUPERNOVA_CONFIG.MAX_LIFE;
34
+ this.life = this.maxLife;
35
+ this.color = SUPERNOVA_CONFIG.COLOR;
36
+ this.parent = parent;
37
+ }
38
+
39
+ isAlive(): boolean {
40
+ return this.life > 0;
41
+ }
42
+
43
+ update() {
44
+ this.life--;
45
+ if (this.parent) {
46
+ this.x = this.parent.x;
47
+ this.y = this.parent.y;
48
+ }
49
+ const progress = (this.maxLife - this.life) / this.maxLife; // 0 to 1
50
+ // Ease-out cubic function for radius expansion
51
+ this.radius = this.initialRadius + (this.maxRadius - this.initialRadius) * (1 - Math.pow(1 - progress, 3));
52
+ }
53
+
54
+ draw(ctx: CanvasRenderingContext2D) {
55
+ const progress = this.life / this.maxLife; // 1 to 0
56
+ const alpha = progress * 0.8; // Fade out
57
+
58
+ ctx.save();
59
+ ctx.globalAlpha = alpha;
60
+ ctx.shadowColor = this.color;
61
+ ctx.shadowBlur = SUPERNOVA_CONFIG.SHADOW_BLUR;
62
+
63
+ const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.radius);
64
+ gradient.addColorStop(0, 'rgba(255, 255, 255, 0)');
65
+ gradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.5)');
66
+ gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
67
+
68
+ ctx.fillStyle = gradient;
69
+ ctx.beginPath();
70
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
71
+ ctx.fill();
72
+
73
+ ctx.restore();
74
+ }
75
+ }
package/src/index.ts ADDED
@@ -0,0 +1,24 @@
1
+
2
+ export { WhereStarsDrift, type SimulationConfig, type SimulationStats } from './where-stars-drift';
3
+
4
+ export { Star } from './entities/star';
5
+ export { Pulsar } from './entities/pulsar';
6
+ export { RingedPlanet } from './entities/ringed-planet';
7
+ export { BlackHole, BlackHle, type BlackHoleType } from './entities/black-hole';
8
+ export { Starship, type ShipState, type ShipClass } from './entities/starship';
9
+ export { Fleet } from './entities/fleet';
10
+ export { type Clan } from './entities/clans';
11
+ export { formationRegistry, type Formation, type StrictFormation, type FreeFormation } from './entities/formations';
12
+ export { Comet } from './entities/comet';
13
+ export { Nebula } from './entities/nebula';
14
+ export { Supernova } from './entities/supernova';
15
+ export { SectorGrid } from './entities/sector-grid';
16
+
17
+ export { LayerController, type Layer } from './controllers/layer-controller';
18
+ export { PhysicsController } from './controllers/physics-controller';
19
+ export { StarshipController } from './controllers/starship-controller';
20
+ export { StarController } from './controllers/star-controller';
21
+ export { EffectsController } from './controllers/effects-controller';
22
+
23
+ export { SIMULATION_CONFIG, DEBUG_CONFIG } from './config/simulation';
24
+ export { GRID_CONFIG } from './config/grid';
@@ -0,0 +1,127 @@
1
+
2
+ /**
3
+ * @fileoverview Defines the energy stream rendering function.
4
+ */
5
+
6
+ import type { BlackHle } from '../entities/black-hole';
7
+ import { BLACK_HOLE_CONFIG } from '../entities/black-hole';
8
+ import type { Star } from '../entities/star';
9
+
10
+ export const drawEnergyStream = (ctx: CanvasRenderingContext2D, hole: BlackHle, target: BlackHle, pulseTime: number, frameCount: number) => {
11
+ const dx = target.x - hole.x;
12
+ const dy = target.y - hole.y;
13
+ const dist = Math.sqrt(dx * dx + dy * dy);
14
+ const angle = Math.atan2(dy, dx);
15
+ const perpAngle = angle + Math.PI / 2;
16
+
17
+ let glowColor: string;
18
+ let baseR: number, baseG: number, baseB: number;
19
+
20
+ switch(hole.type) {
21
+ case 'VORTEX':
22
+ glowColor = BLACK_HOLE_CONFIG.VORTEX.VORTEX_COLOR_INNER;
23
+ [baseR, baseG, baseB] = [255, 100, 0];
24
+ break;
25
+ case 'WARPED_DISK':
26
+ glowColor = BLACK_HOLE_CONFIG.WARPED_DISK.DISK_COLOR_INNER;
27
+ [baseR, baseG, baseB] = [255, 220, 150];
28
+ break;
29
+ case 'ACCRETION_DISK':
30
+ glowColor = `rgba(200, 220, 255, 0.8)`;
31
+ [baseR, baseG, baseB] = [200, 220, 255];
32
+ break;
33
+ case 'LENSING':
34
+ default:
35
+ glowColor = `rgba(255, 255, 255, 0.7)`;
36
+ [baseR, baseG, baseB] = [255, 255, 255];
37
+ break;
38
+ }
39
+
40
+ const pulse = (Math.sin(pulseTime * hole.pulseFrequency + hole.pulsePhase) + 1) / 2; // 0 to 1
41
+
42
+ const startX = hole.x;
43
+ const startY = hole.y;
44
+
45
+ ctx.save();
46
+ ctx.globalCompositeOperation = 'lighter';
47
+ ctx.lineCap = 'round';
48
+
49
+ // --- Emission Nozzle ---
50
+ if (frameCount % 5 === 0) {
51
+ const nozzleLengthMultiplier = Math.random() * 7 + 3;
52
+ const potentialNozzleLength = hole.radius * nozzleLengthMultiplier;
53
+ hole.nozzleLength = Math.min(potentialNozzleLength, (4 / 5) * dist);
54
+
55
+ const nozzleTipX = startX + Math.cos(angle) * hole.nozzleLength;
56
+ const nozzleTipY = startY + Math.sin(angle) * hole.nozzleLength;
57
+
58
+ const nozzleGradient = ctx.createLinearGradient(
59
+ startX, startY,
60
+ nozzleTipX, nozzleTipY
61
+ );
62
+ nozzleGradient.addColorStop(0, `rgba(${baseR}, ${baseG}, ${baseB}, ${0.1 + pulse * 0.2})`);
63
+ nozzleGradient.addColorStop(1, `rgba(${baseR}, ${baseG}, ${baseB}, 0)`);
64
+ hole.nozzleColor = nozzleGradient;
65
+ }
66
+
67
+ if (hole.nozzleLength > 0 && hole.nozzleColor) {
68
+ const nozzleTipX = startX + Math.cos(angle) * hole.nozzleLength;
69
+ const nozzleTipY = startY + Math.sin(angle) * hole.nozzleLength;
70
+ const nozzleBaseWidth = hole.radius * 2;
71
+
72
+ ctx.beginPath();
73
+ ctx.moveTo(nozzleTipX, nozzleTipY);
74
+ ctx.lineTo(startX + Math.cos(perpAngle) * nozzleBaseWidth / 2, startY + Math.sin(perpAngle) * nozzleBaseWidth / 2);
75
+ ctx.lineTo(startX - Math.cos(perpAngle) * nozzleBaseWidth / 2, startY - Math.sin(perpAngle) * nozzleBaseWidth / 2);
76
+ ctx.closePath();
77
+
78
+ ctx.fillStyle = hole.nozzleColor;
79
+ ctx.shadowColor = glowColor;
80
+ ctx.shadowBlur = 30;
81
+ ctx.fill();
82
+ }
83
+
84
+
85
+ // --- Main Stream ---
86
+ const endX = startX + dx;
87
+ const endY = startY + dy;
88
+
89
+ const minThicknessFactor = 0.3;
90
+ const maxThicknessFactor = 3.5;
91
+ const distanceFactor = Math.max(0, 1 - dist / BLACK_HOLE_CONFIG.ENERGY_STREAM_DISTANCE);
92
+ const thicknessMultiplier = minThicknessFactor + (maxThicknessFactor - minThicknessFactor) * distanceFactor;
93
+
94
+ // --- Outer Glow ---
95
+ const outerGlowAlpha = 0.2 + pulse * 0.3;
96
+ ctx.shadowColor = glowColor;
97
+ ctx.shadowBlur = 25;
98
+
99
+ const gradientOuter = ctx.createLinearGradient(startX, startY, endX, endY);
100
+ gradientOuter.addColorStop(0, `rgba(${baseR}, ${baseG}, ${baseB}, ${outerGlowAlpha * 0.7})`);
101
+ gradientOuter.addColorStop(0.95, `rgba(${baseR}, ${baseG}, ${baseB}, 0)`);
102
+ ctx.strokeStyle = gradientOuter;
103
+ ctx.lineWidth = hole.radius * (0.2 + pulse * 0.15) * thicknessMultiplier;
104
+
105
+ ctx.beginPath();
106
+ ctx.moveTo(startX, startY);
107
+ ctx.lineTo(endX, endY);
108
+ ctx.stroke();
109
+
110
+ // --- Inner Core ---
111
+ const innerGlowAlpha = 0.4 + pulse * 0.6;
112
+ ctx.shadowColor = `rgba(${baseR}, ${baseG}, ${baseB}, ${0.5 + pulse * 0.5})`;
113
+ ctx.shadowBlur = 12;
114
+
115
+ const gradientInner = ctx.createLinearGradient(startX, startY, endX, endY);
116
+ gradientInner.addColorStop(0, `rgba(${baseR}, ${baseG}, ${baseB}, ${innerGlowAlpha})`);
117
+ gradientInner.addColorStop(0.95, `rgba(${baseR}, ${baseG}, ${baseB}, 0)`);
118
+ ctx.strokeStyle = gradientInner;
119
+ ctx.lineWidth = hole.radius * 0.1 * thicknessMultiplier;
120
+
121
+ ctx.beginPath();
122
+ ctx.moveTo(startX, startY);
123
+ ctx.lineTo(endX, endY);
124
+ ctx.stroke();
125
+
126
+ ctx.restore();
127
+ };
@@ -0,0 +1,159 @@
1
+
2
+ /**
3
+ * @fileoverview Defines the Quadtree class for spatial partitioning.
4
+ */
5
+ import type { BoundedBody } from '../base/bounded-body';
6
+
7
+ // --- Quadtree Configuration ---
8
+ export const QUADTREE_CONFIG = {
9
+ MAX_OBJECTS: 10,
10
+ MAX_LEVELS: 5,
11
+ };
12
+
13
+ interface Rectangle {
14
+ x: number;
15
+ y: number;
16
+ width: number;
17
+ height: number;
18
+ }
19
+
20
+ export class Quadtree {
21
+ private maxObjects: number;
22
+ private maxLevels: number;
23
+ private level: number;
24
+ private bounds: Rectangle;
25
+ private objects: BoundedBody[];
26
+ private nodes: Quadtree[];
27
+
28
+ constructor(bounds: Rectangle, level: number = 0) {
29
+ this.maxObjects = QUADTREE_CONFIG.MAX_OBJECTS;
30
+ this.maxLevels = QUADTREE_CONFIG.MAX_LEVELS;
31
+ this.level = level;
32
+ this.bounds = bounds;
33
+ this.objects = [];
34
+ this.nodes = [];
35
+ }
36
+
37
+ public clear() {
38
+ this.objects = [];
39
+ this.nodes = [];
40
+ }
41
+
42
+ private split() {
43
+ const nextLevel = this.level + 1;
44
+ const subWidth = this.bounds.width / 2;
45
+ const subHeight = this.bounds.height / 2;
46
+ const x = this.bounds.x;
47
+ const y = this.bounds.y;
48
+
49
+ // Top right
50
+ this.nodes[0] = new Quadtree({ x: x + subWidth, y: y, width: subWidth, height: subHeight }, nextLevel);
51
+ // Top left
52
+ this.nodes[1] = new Quadtree({ x: x, y: y, width: subWidth, height: subHeight }, nextLevel);
53
+ // Bottom left
54
+ this.nodes[2] = new Quadtree({ x: x, y: y + subHeight, width: subWidth, height: subHeight }, nextLevel);
55
+ // Bottom right
56
+ this.nodes[3] = new Quadtree({ x: x + subWidth, y: y + subHeight, width: subWidth, height: subHeight }, nextLevel);
57
+ }
58
+
59
+ private getIndex(body: BoundedBody): number {
60
+ let index = -1;
61
+ const verticalMidpoint = this.bounds.x + (this.bounds.width / 2);
62
+ const horizontalMidpoint = this.bounds.y + (this.bounds.height / 2);
63
+
64
+ const topQuadrant = body.y < horizontalMidpoint && body.y + body.boundingRadius < horizontalMidpoint;
65
+ const bottomQuadrant = body.y > horizontalMidpoint;
66
+
67
+ if (body.x < verticalMidpoint && body.x + body.boundingRadius < verticalMidpoint) {
68
+ if (topQuadrant) {
69
+ index = 1; // Top left
70
+ } else if (bottomQuadrant) {
71
+ index = 2; // Bottom left
72
+ }
73
+ } else if (body.x > verticalMidpoint) {
74
+ if (topQuadrant) {
75
+ index = 0; // Top right
76
+ } else if (bottomQuadrant) {
77
+ index = 3; // Bottom right
78
+ }
79
+ }
80
+ return index;
81
+ }
82
+
83
+ public insert(body: BoundedBody) {
84
+ if (this.nodes.length) {
85
+ const index = this.getIndex(body);
86
+ if (index !== -1) {
87
+ this.nodes[index].insert(body);
88
+ return;
89
+ }
90
+ }
91
+
92
+ this.objects.push(body);
93
+
94
+ if (this.objects.length > this.maxObjects && this.level < this.maxLevels) {
95
+ if (!this.nodes.length) {
96
+ this.split();
97
+ }
98
+
99
+ let i = 0;
100
+ while (i < this.objects.length) {
101
+ const index = this.getIndex(this.objects[i]);
102
+ if (index !== -1) {
103
+ this.nodes[index].insert(this.objects.splice(i, 1)[0]);
104
+ } else {
105
+ i++;
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ public query(range: BoundedBody): BoundedBody[] {
112
+ let found: BoundedBody[] = [];
113
+ const { x, y, boundingRadius } = range;
114
+ const queryBounds = {
115
+ x: x - boundingRadius,
116
+ y: y - boundingRadius,
117
+ width: boundingRadius * 2,
118
+ height: boundingRadius * 2,
119
+ };
120
+
121
+ if (!this.intersects(this.bounds, queryBounds)) {
122
+ return [];
123
+ }
124
+
125
+ found = found.concat(this.objects);
126
+
127
+ if (this.nodes.length) {
128
+ const index = this.getIndex(range);
129
+ if (index !== -1) {
130
+ found = found.concat(this.nodes[index].query(range));
131
+ } else {
132
+ for (let i = 0; i < this.nodes.length; i++) {
133
+ found = found.concat(this.nodes[i].query(range));
134
+ }
135
+ }
136
+ }
137
+
138
+ return found;
139
+ }
140
+
141
+ private intersects(rect1: Rectangle, rect2: Rectangle): boolean {
142
+ return (
143
+ rect1.x < rect2.x + rect2.width &&
144
+ rect1.x + rect1.width > rect2.x &&
145
+ rect1.y < rect2.y + rect2.height &&
146
+ rect1.y + rect1.height > rect2.y
147
+ );
148
+ }
149
+
150
+ public draw(ctx: CanvasRenderingContext2D) {
151
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
152
+ ctx.strokeRect(this.bounds.x, this.bounds.y, this.bounds.width, this.bounds.height);
153
+ if (this.nodes.length) {
154
+ for (let i = 0; i < this.nodes.length; i++) {
155
+ this.nodes[i].draw(ctx);
156
+ }
157
+ }
158
+ }
159
+ }
@@ -0,0 +1,28 @@
1
+
2
+ /**
3
+ * @fileoverview Defines a simplified, performant energy stream for star-to-hole effects.
4
+ */
5
+ import type { Star } from '../entities/star';
6
+ import type { BlackHle } from '../entities/black-hole';
7
+
8
+ export const drawSimpleStarStream = (ctx: CanvasRenderingContext2D, star: Star, hole: BlackHle) => {
9
+ const dx = hole.x - star.x;
10
+ const dy = hole.y - star.y;
11
+ const dist = Math.sqrt(dx * dx + dy * dy);
12
+
13
+ // Fade the stream as it gets closer to the hole
14
+ const maxDist = hole.radius + 50;
15
+ const alpha = Math.max(0, 1 - (dist / maxDist)) * 0.5;
16
+
17
+ if (alpha <= 0) return;
18
+
19
+ ctx.save();
20
+ ctx.beginPath();
21
+ ctx.moveTo(star.x, star.y);
22
+ ctx.lineTo(hole.x, hole.y);
23
+ ctx.strokeStyle = star.color;
24
+ ctx.globalAlpha = alpha;
25
+ ctx.lineWidth = star.radius * 0.8;
26
+ ctx.stroke();
27
+ ctx.restore();
28
+ };
package/src/types.ts ADDED
@@ -0,0 +1,16 @@
1
+
2
+ import type { Starship } from './entities/starship';
3
+ import type { Formation } from './entities/formations';
4
+ import type { Clan } from './entities/clans';
5
+ import type { Fleet } from './entities/fleet';
6
+ import type { Star } from './entities/star';
7
+ import type { BlackHle } from './entities/black-hole';
8
+
9
+ export type CatalogData = {
10
+ starships: Starship[];
11
+ formations: Formation[];
12
+ clans: Clan[];
13
+ fleets: Fleet[];
14
+ stars: Star[];
15
+ blackHoles: BlackHle[];
16
+ };
@@ -0,0 +1,91 @@
1
+
2
+ import { BlackHole } from '../entities/black-hole';
3
+ import { PANEL_CONFIG } from './panel-config';
4
+
5
+ const DESCRIPTIONS: Record<BlackHole['type'], string> = {
6
+ STANDARD: 'A simple black hole with a soft, ethereal glow.',
7
+ LENSING: 'Features a bright, sharp ring of light caused by gravitational lensing.',
8
+ ACCRETION_DISK: 'Characterized by a stable, flat disk of matter with a bright central line and relativistic jets.',
9
+ WARPED_DISK: 'Shows a dynamic, warped accretion disk that gives a 3D perspective.',
10
+ VORTEX: 'A highly active black hole with swirling arms of super-heated gas.',
11
+ };
12
+
13
+ function drawBlackHoleParameters(ctx: CanvasRenderingContext2D, hole: BlackHole, x: number, y: number) {
14
+ ctx.font = 'bold 12px "Roboto Mono", monospace';
15
+ ctx.fillStyle = '#fff';
16
+ ctx.textAlign = 'left';
17
+ ctx.textBaseline = 'top';
18
+ ctx.fillText(hole.type, x, y);
19
+
20
+ // Draw description
21
+ ctx.font = '11px "Roboto Mono", monospace';
22
+ ctx.fillStyle = PANEL_CONFIG.ACCENT_COLOR;
23
+ const description = DESCRIPTIONS[hole.type] || 'A mysterious gravitational anomaly.';
24
+ const words = description.split(' ');
25
+ let line = '';
26
+ let currentY = y + 20; // Start description lower
27
+ for (let n = 0; n < words.length; n++) {
28
+ const testLine = line + words[n] + ' ';
29
+ const metrics = ctx.measureText(testLine);
30
+ if (metrics.width > 200 && n > 0) { // Wrap text
31
+ ctx.fillText(line, x, currentY);
32
+ line = words[n] + ' ';
33
+ currentY += 12;
34
+ } else {
35
+ line = testLine;
36
+ }
37
+ }
38
+ ctx.fillText(line, x, currentY);
39
+ currentY += 20;
40
+
41
+ // Draw parameters
42
+ ctx.font = '10px "Roboto Mono", monospace';
43
+ const lineHeight = 12;
44
+
45
+ const params: { label: string; value: string }[] = [
46
+ { label: 'Type', value: hole.type },
47
+ { label: 'Base Radius', value: hole.baseRadius.toFixed(2) },
48
+ { label: 'Mass', value: hole.mass.toFixed(2) },
49
+ { label: 'Merge State', value: `${hole.mergeState}` },
50
+ ];
51
+
52
+ params.forEach(param => {
53
+ ctx.fillStyle = PANEL_CONFIG.ACCENT_COLOR;
54
+ ctx.fillText(`${param.label}:`, x, currentY);
55
+ ctx.fillStyle = PANEL_CONFIG.TEXT_COLOR;
56
+ ctx.fillText(param.value, x + 70, currentY);
57
+ currentY += lineHeight;
58
+ });
59
+ }
60
+
61
+
62
+ export function drawBlackHoleCatalog(ctx: CanvasRenderingContext2D, startY: number, blackHoleCatalog: BlackHole[], pulseTime: number, frameCount: number): number {
63
+ let currentY = startY + PANEL_CONFIG.PADDING;
64
+
65
+ ctx.font = PANEL_CONFIG.GROUP_FONT;
66
+ ctx.fillStyle = PANEL_CONFIG.TEXT_COLOR;
67
+ ctx.textAlign = 'left';
68
+ ctx.textBaseline = 'top';
69
+ ctx.fillText('All Black Hole Types', PANEL_CONFIG.X + PANEL_CONFIG.PADDING, currentY);
70
+ currentY += PANEL_CONFIG.GROUP_HEADER_HEIGHT;
71
+
72
+ blackHoleCatalog.forEach(sampleHole => {
73
+ const drawingAreaHeight = 80;
74
+ const rowY = currentY + drawingAreaHeight / 2;
75
+ const xPos = PANEL_CONFIG.X + PANEL_CONFIG.PADDING + 30;
76
+
77
+ ctx.save();
78
+ // Position the drawing relative to the catalog panel
79
+ ctx.translate(xPos, rowY);
80
+ // sampleHole.update(ctx, 0, 0, frameCount); // This is expensive, disable animation in catalog
81
+ sampleHole.draw(ctx, 0, 0); // Pass static pulseTime and frameCount
82
+ ctx.restore();
83
+
84
+ // Draw Title
85
+ drawBlackHoleParameters(ctx, sampleHole, xPos + 70, rowY - 35);
86
+
87
+ currentY += drawingAreaHeight + 45; // Add padding between entries
88
+ });
89
+
90
+ return currentY - startY;
91
+ }
@@ -0,0 +1,27 @@
1
+
2
+ import type { Clan } from '../entities/clans';
3
+ import { PANEL_CONFIG } from './panel-config';
4
+
5
+ export function drawClanCatalog(ctx: CanvasRenderingContext2D, startY: number, clans: Clan[]): number {
6
+ ctx.font = PANEL_CONFIG.ROW_FONT;
7
+
8
+ clans.forEach((clan, i) => {
9
+ const y = startY + (i * PANEL_CONFIG.ROW_HEIGHT) + PANEL_CONFIG.PADDING + 15;
10
+
11
+ // Draw clan icon
12
+ ctx.save();
13
+ ctx.translate(PANEL_CONFIG.X + PANEL_CONFIG.PADDING + 10, y);
14
+ ctx.fillStyle = clan.color;
15
+ ctx.strokeStyle = clan.color;
16
+ clan.icon(ctx, 12);
17
+ ctx.restore();
18
+
19
+ // Draw text
20
+ ctx.fillStyle = PANEL_CONFIG.TEXT_COLOR;
21
+ ctx.textAlign = 'left';
22
+ ctx.textBaseline = 'middle';
23
+ ctx.fillText(`${clan.name} (Fleets: ${clan.fleets.length}, Ships: ${clan.starships.length})`, PANEL_CONFIG.X + PANEL_CONFIG.PADDING + 30, y);
24
+ });
25
+
26
+ return (clans.length * PANEL_CONFIG.ROW_HEIGHT) + (PANEL_CONFIG.PADDING * 2);
27
+ }