/ docs / interactive / ring-layout-visualizer.html
ring-layout-visualizer.html
   1  <!DOCTYPE html>
   2  <html>
   3  <head>
   4      <title>InterBrain Ring Layout Algorithm - Interactive Visualizer</title>
   5      <style>
   6          body { 
   7              font-family: Arial, sans-serif; 
   8              margin: 20px; 
   9              background: #1a1a1a; 
  10              color: white; 
  11          }
  12          canvas { 
  13              border: 1px solid #444; 
  14              background: #000; 
  15              margin: 10px 0; 
  16          }
  17          .controls { 
  18              margin: 20px 0; 
  19              padding: 20px; 
  20              background: #333; 
  21              border-radius: 8px; 
  22          }
  23          input[type="range"] { 
  24              width: 200px; 
  25              margin: 0 10px; 
  26          }
  27          .info { 
  28              background: #444; 
  29              padding: 15px; 
  30              border-radius: 8px; 
  31              margin: 10px 0; 
  32              font-family: monospace; 
  33          }
  34      </style>
  35  </head>
  36  <body>
  37      <h1>Ring Layout Algorithm Visualizer</h1>
  38      
  39      <div style="background: #2d4a22; padding: 15px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #4caf50;">
  40          <strong>Documentation Reference</strong><br>
  41          This interactive tool demonstrates the mathematical precision of InterBrain's honeycomb ring layout algorithm.
  42          It shows how 1-36 nodes are positioned using a 42-coordinate system with intelligent selection patterns.
  43          <br><em>This is for learning and reference only - not part of the active application.</em>
  44      </div>
  45      
  46      <div class="controls">
  47          <label>Node Count: <input type="range" id="nodeCount" min="1" max="36" value="6"> <span id="nodeCountValue">6</span></label>
  48          <br><br>
  49          <button onclick="setNodeCount(1)">1 Node</button>
  50          <button onclick="setNodeCount(6)">6 Nodes</button>
  51          <button onclick="setNodeCount(7)">7 Nodes</button>
  52          <button onclick="setNodeCount(12)">12 Nodes</button>
  53          <button onclick="setNodeCount(18)">18 Nodes</button>
  54          <button onclick="setNodeCount(24)">24 Nodes</button>
  55          <button onclick="setNodeCount(36)">36 Nodes</button>
  56          <br><br>
  57          <button onclick="previousNode()">Previous</button>
  58          <button onclick="nextNode()">Next</button>
  59          <button onclick="visualizeProgression()">Auto Progression (1-36)</button>
  60      </div>
  61      
  62      <div class="info" id="info">
  63          Ring 1: 0 nodes | Ring 2: 0 nodes | Ring 3: 0 nodes
  64      </div>
  65      
  66      <div style="display: flex; gap: 20px;">
  67          <div>
  68              <h3>Dynamic Visualization</h3>
  69              <canvas id="canvas" width="800" height="800"></canvas>
  70          </div>
  71          <div>
  72              <h3>Static 42-Node Coordinate System</h3>
  73              <canvas id="staticCanvas" width="800" height="800"></canvas>
  74              <div style="margin-top: 10px; font-family: monospace; font-size: 12px;">
  75                  <strong>42 Possible Coordinates:</strong><br>
  76                  <strong>Ring 1:</strong> Nodes 1-6 (center hexagon)<br>
  77                  <strong>Ring 2:</strong> Nodes 7-18 (12 nodes: 6 corners + 6 edge midpoints)<br>
  78                  <strong>Ring 3:</strong> Nodes 19-42 (24 nodes: 6 corners + 18 edge positions)<br>
  79                  <em>Up to 36 nodes displayed using intelligent coordinate selection</em>
  80              </div>
  81          </div>
  82      </div>
  83  
  84      <script>
  85          // Improved hexagonal ring positioning algorithm for optimal geometric balance
  86          function calculateHexagonalRingPositions(maxNodes, radius, zDistance) {
  87              if (maxNodes === 0) return [];
  88              
  89              const positions = [];
  90              
  91              if (maxNodes <= 6) {
  92                  // Ring 1: Equidistant spacing on circle (dynamic repositioning)
  93                  let startAngle = -Math.PI / 2; // Default: start at top (point up)
  94                  
  95                  // For exactly 6 nodes, rotate by 30° so flat edge is at top
  96                  if (maxNodes === 6) {
  97                      startAngle = -Math.PI / 2 + Math.PI / 6; // Rotate by 30° (π/6 radians)
  98                  }
  99                  
 100                  for (let i = 0; i < Math.min(maxNodes, 6); i++) {
 101                      const angle = (i / maxNodes) * 2 * Math.PI + startAngle;
 102                      const x = radius * Math.cos(angle);
 103                      const y = radius * Math.sin(angle);
 104                      positions.push([x, y, zDistance]);
 105                  }
 106              } else if (maxNodes <= 12) {
 107                  // Ring 2: Fixed positions - midpoints between inner rays first (more circular)
 108                  
 109                  // Define all 12 fixed positions for Ring 2
 110                  const allRing2Positions = [];
 111                  
 112                  // First 6 positions: specific angles for proper rectangle formation
 113                  // Position 0: -60° (top-right for rectangle)
 114                  // Position 1: +60° (top-left for rectangle)  
 115                  // Position 2: -120° (bottom-right for rectangle)
 116                  // Position 3: +120° (bottom-left for rectangle)
 117                  // Position 4: 0° (top center)
 118                  // Position 5: 180° (bottom center)
 119                  const specificAngles = [
 120                      -Math.PI / 3,        // -60° (top-right)
 121                      Math.PI / 3,         // +60° (top-left)
 122                      -2 * Math.PI / 3,    // -120° (bottom-right) 
 123                      2 * Math.PI / 3,     // +120° (bottom-left)
 124                      0,                   // 0° (top center)
 125                      Math.PI              // 180° (bottom center)
 126                  ];
 127                  
 128                  for (let i = 0; i < 6; i++) {
 129                      const angle = specificAngles[i];
 130                      const x = radius * Math.cos(angle);
 131                      const y = radius * Math.sin(angle);
 132                      allRing2Positions.push([x, y, zDistance]);
 133                  }
 134                  
 135                  // Next 6 positions: aligned with inner rays  
 136                  // Same angles as inner ring: -90°, -30°, 30°, 90°, 150°, 210°
 137                  for (let i = 0; i < 6; i++) {
 138                      const angle = (i / 6) * 2 * Math.PI - Math.PI / 2; // Same as Ring 1
 139                      const x = radius * Math.cos(angle);
 140                      const y = radius * Math.sin(angle);
 141                      allRing2Positions.push([x, y, zDistance]);
 142                  }
 143                  
 144                  // Optimal placement order for rectangle formation
 145                  const placementOrder = [
 146                      5,     // 1st: Bottom center (180°)
 147                      4,     // 2nd: Top center (0°) - vertical line
 148                      2,     // 3rd: Bottom-right (-120°) - triangle
 149                      0,     // 4th: Top-right (-60°) - rectangle!
 150                      3, 1,  // 5th, 6th: Bottom-left (+120°) and Top-left (+60°)
 151                      10, 7, // 7th, 8th: Bottom and top corners (aligned with inner)
 152                      11, 8, // 9th, 10th: Bottom-right and top-left corners
 153                      6, 9   // 11th, 12th: Top-right and bottom-left corners
 154                  ];
 155                  
 156                  // Place nodes according to optimal order (fixed positions)
 157                  for (let i = 0; i < Math.min(maxNodes - 6, 12); i++) {
 158                      const posIndex = placementOrder[i];
 159                      positions.push(allRing2Positions[posIndex]);
 160                  }
 161              } else {
 162                  // Ring 3: Single coherent hexagon - fixed positions at same radius
 163                  // 18 total positions: 6 corners + 12 edge positions (2 between each corner pair)
 164                  
 165                  const allRing3Positions = [];
 166                  
 167                  // Generate all 18 evenly distributed positions around the circle
 168                  for (let i = 0; i < 18; i++) {
 169                      const angle = (i / 18) * 2 * Math.PI; // Every 20 degrees
 170                      const x = radius * Math.cos(angle);
 171                      const y = radius * Math.sin(angle);
 172                      allRing3Positions.push({pos: [x, y, zDistance], angle: angle});
 173                  }
 174                  
 175                  // Optimal placement order for maximum symmetry at each step
 176                  // Start with maximally separated positions, then fill in systematically
 177                  const placementOrder = [
 178                      0, 9,   // Opposite positions (0°, 180°) 
 179                      3, 12,  // 60°, 240° (perpendicular)
 180                      6, 15,  // 120°, 300° (complete 6-fold symmetry)
 181                      1, 10,  // 20°, 200° (fill between)
 182                      4, 13,  // 80°, 260° 
 183                      7, 16,  // 140°, 320°
 184                      2, 11,  // 40°, 220° 
 185                      5, 14,  // 100°, 280°
 186                      8, 17   // 160°, 340° (final positions)
 187                  ];
 188                  
 189                  // Place nodes according to optimal symmetric order (fixed positions)
 190                  for (let i = 0; i < Math.min(maxNodes - 18, 18); i++) {
 191                      const posIndex = placementOrder[i];
 192                      positions.push(allRing3Positions[posIndex].pos);
 193                  }
 194              }
 195              
 196              return positions;
 197          }
 198          
 199          // Generate all 42 static node positions using the same logic as the static coordinate system
 200          function generateAll36StaticPositions() { // Keep function name for compatibility
 201              const allPositions = [];
 202              const scale = 1; // Use unscaled world coordinates
 203              
 204              // Ring 1: Nodes 1-6 (same as existing logic)
 205              const ring1StartAngle = -Math.PI / 2 + Math.PI / 6; // 30° rotation for flat edge at top
 206              for (let i = 0; i < 6; i++) {
 207                  const angle = (i / 6) * 2 * Math.PI + ring1StartAngle;
 208                  const x = RING_RADII[0] * Math.cos(angle);
 209                  const y = RING_RADII[0] * Math.sin(angle);
 210                  allPositions.push([x, y, 0]);
 211              }
 212              
 213              // Ring 2: Nodes 7-18 (6 edge positions + 6 corner positions)
 214              const ring2EdgeRadius = RING_RADII[1] * Math.cos(Math.PI / 6);
 215              // First 6 nodes (7-12): edge positions (reduced radius)
 216              for (let i = 0; i < 6; i++) {
 217                  const angle = (i / 6) * 2 * Math.PI - Math.PI / 2;
 218                  const x = ring2EdgeRadius * Math.cos(angle);
 219                  const y = ring2EdgeRadius * Math.sin(angle);
 220                  allPositions.push([x, y, 0]);
 221              }
 222              // Next 6 nodes (13-18): corner positions (full radius)
 223              for (let i = 0; i < 6; i++) {
 224                  const angle = (i / 6) * 2 * Math.PI - Math.PI / 2 + Math.PI / 6;
 225                  const x = RING_RADII[1] * Math.cos(angle);
 226                  const y = RING_RADII[1] * Math.sin(angle);
 227                  allPositions.push([x, y, 0]);
 228              }
 229              
 230              // Ring 3: Nodes 19-36 (6 vertices + 12 edge nodes using path parameterization)
 231              const hexagonAngles = [30, 90, 150, 210, 270, 330];
 232              const baseRadius = RING_RADII[2];
 233              const vertexPositions = [];
 234              
 235              // Calculate vertex positions (19-24)
 236              for (let i = 0; i < 6; i++) {
 237                  const angleDegrees = hexagonAngles[i];
 238                  const angleRadians = (angleDegrees - 90) * Math.PI / 180;
 239                  const x = baseRadius * Math.cos(angleRadians);
 240                  const y = baseRadius * Math.sin(angleRadians);
 241                  vertexPositions.push([x, y]);
 242                  allPositions.push([x, y, 0]); // Add vertex positions (19-24)
 243              }
 244              
 245              // Path parameterization for edge nodes (25-36)
 246              function lerpPath(pointA, pointB, t) {
 247                  return [
 248                      pointA[0] + t * (pointB[0] - pointA[0]),
 249                      pointA[1] + t * (pointB[1] - pointA[1])
 250                  ];
 251              }
 252              
 253              // Add edge nodes using path parameterization (25-36)
 254              for (let edgeIndex = 0; edgeIndex < 6; edgeIndex++) {
 255                  const startVertex = vertexPositions[edgeIndex];
 256                  const endVertex = vertexPositions[(edgeIndex + 1) % 6];
 257                  
 258                  for (let nodeOnEdge = 0; nodeOnEdge < 2; nodeOnEdge++) {
 259                      const t = (nodeOnEdge + 1) / 3; // t = 1/3, 2/3
 260                      const [worldX, worldY] = lerpPath(startVertex, endVertex, t);
 261                      allPositions.push([worldX, worldY, 0]);
 262                  }
 263              }
 264              
 265              // Add 6 additional edge midpoint nodes (37-42) at t=0.5
 266              for (let edgeIndex = 0; edgeIndex < 6; edgeIndex++) {
 267                  const startVertex = vertexPositions[edgeIndex];
 268                  const endVertex = vertexPositions[(edgeIndex + 1) % 6];
 269                  
 270                  const t = 0.5; // Exact midpoint
 271                  const [worldX, worldY] = lerpPath(startVertex, endVertex, t);
 272                  allPositions.push([worldX, worldY, 0]); // Nodes 37-42
 273              }
 274              
 275              return allPositions;
 276          }
 277          
 278          // Helper function to activate Ring 2 nodes (always full for node counts > 18)
 279          function activateRing2(mask) {
 280              for (let i = 6; i < 18; i++) {
 281                  mask[i] = true; // Nodes 7-18 (indices 6-17)
 282              }
 283          }
 284          
 285          // Helper function to activate specific Ring 3 node sets
 286          function activateRing3Nodes(mask, nodeNumbers) {
 287              nodeNumbers.forEach(nodeNum => {
 288                  mask[nodeNum - 1] = true; // Convert 1-based to 0-based indexing
 289              });
 290          }
 291          
 292          // Define which nodes are active for each total node count (boolean mask approach)
 293          function getActiveMask(totalNodes) {
 294              const mask = new Array(42).fill(false);
 295              
 296              // Always activate Ring 1 (nodes 1-6) for counts > 0
 297              for (let i = 0; i < Math.min(totalNodes, 6); i++) {
 298                  mask[i] = true; // Nodes 1-6 (indices 0-5)
 299              }
 300              
 301              // If totalNodes <= 6, we're done
 302              if (totalNodes <= 6) return mask;
 303              
 304              // Ring 2 specific patterns (nodes 7-18, indices 6-17)
 305              if (totalNodes === 7) {
 306                  mask[6] = true; // Node 7
 307              }
 308              else if (totalNodes === 8) {
 309                  mask[6] = true;  // Node 7
 310                  mask[9] = true;  // Node 10
 311              }
 312              else if (totalNodes === 9) {
 313                  mask[6] = true;  // Node 7
 314                  mask[8] = true;  // Node 9
 315                  mask[10] = true; // Node 11
 316              }
 317              else if (totalNodes === 10) {
 318                  mask[6] = true;  // Node 7 (already in your description but adding for completeness)
 319                  mask[7] = true;  // Node 8
 320                  mask[8] = true;  // Node 9
 321                  mask[10] = true; // Node 11
 322                  mask[11] = true; // Node 12
 323                  // Wait, let me re-read: you want 12, 8, 9, 11 for 10 nodes
 324                  mask[6] = false; mask[7] = false; mask[8] = false; mask[10] = false; mask[11] = false; // Clear first
 325                  mask[11] = true; // Node 12
 326                  mask[7] = true;  // Node 8
 327                  mask[8] = true;  // Node 9
 328                  mask[10] = true; // Node 11
 329              }
 330              else if (totalNodes === 11) {
 331                  mask[6] = true;  // Node 7
 332                  mask[7] = true;  // Node 8
 333                  mask[8] = true;  // Node 9
 334                  mask[10] = true; // Node 11
 335                  mask[11] = true; // Node 12
 336              }
 337              else if (totalNodes === 12) {
 338                  mask[6] = true;  // Node 7
 339                  mask[7] = true;  // Node 8
 340                  mask[8] = true;  // Node 9
 341                  mask[9] = true;  // Node 10
 342                  mask[10] = true; // Node 11
 343                  mask[11] = true; // Node 12
 344              }
 345              else if (totalNodes === 13) {
 346                  mask[6] = true;  // Node 7
 347                  mask[7] = true;  // Node 8
 348                  mask[8] = true;  // Node 9
 349                  mask[14] = true; // Node 15
 350                  mask[15] = true; // Node 16
 351                  mask[10] = true; // Node 11
 352                  mask[11] = true; // Node 12
 353              }
 354              else if (totalNodes === 14) {
 355                  mask[17] = true; // Node 18
 356                  mask[12] = true; // Node 13
 357                  mask[7] = true;  // Node 8
 358                  mask[8] = true;  // Node 9
 359                  mask[14] = true; // Node 15
 360                  mask[15] = true; // Node 16
 361                  mask[10] = true; // Node 11
 362                  mask[11] = true; // Node 12
 363              }
 364              else if (totalNodes === 15) {
 365                  // All nodes from 14-node setup + Node 7
 366                  mask[17] = true; // Node 18
 367                  mask[12] = true; // Node 13
 368                  mask[7] = true;  // Node 8
 369                  mask[8] = true;  // Node 9
 370                  mask[14] = true; // Node 15
 371                  mask[15] = true; // Node 16
 372                  mask[10] = true; // Node 11
 373                  mask[11] = true; // Node 12
 374                  mask[6] = true;  // Node 7 (additional)
 375              }
 376              else if (totalNodes === 16) {
 377                  // All nodes from 15-node setup + Node 10
 378                  mask[17] = true; // Node 18
 379                  mask[12] = true; // Node 13
 380                  mask[7] = true;  // Node 8
 381                  mask[8] = true;  // Node 9
 382                  mask[14] = true; // Node 15
 383                  mask[15] = true; // Node 16
 384                  mask[10] = true; // Node 11
 385                  mask[11] = true; // Node 12
 386                  mask[6] = true;  // Node 7
 387                  mask[9] = true;  // Node 10 (additional)
 388              }
 389              else if (totalNodes === 17) {
 390                  // 16-node setup + Node 17, Node 14, - Node 10
 391                  mask[17] = true; // Node 18
 392                  mask[12] = true; // Node 13
 393                  mask[7] = true;  // Node 8
 394                  mask[8] = true;  // Node 9
 395                  mask[14] = true; // Node 15
 396                  mask[15] = true; // Node 16
 397                  mask[10] = true; // Node 11
 398                  mask[11] = true; // Node 12
 399                  mask[6] = true;  // Node 7
 400                  // mask[9] = false; // Node 10 (turn off) - already false by default
 401                  mask[16] = true; // Node 17 (additional)
 402                  mask[13] = true; // Node 14 (additional)
 403              }
 404              else if (totalNodes === 18) {
 405                  // All Ring 2 nodes (nodes 7-18) active
 406                  for (let i = 6; i < 18; i++) {
 407                      mask[i] = true; // Nodes 7-18 (indices 6-17)
 408                  }
 409              }
 410              
 411              // Ring 3 specific patterns (Ring 1 + Ring 2 always fully active for 19+)
 412              else if (totalNodes === 19) {
 413                  activateRing2(mask);
 414                  activateRing3Nodes(mask, [42]);
 415              }
 416              else if (totalNodes === 20) {
 417                  activateRing2(mask);
 418                  activateRing3Nodes(mask, [42, 39]);
 419              }
 420              else if (totalNodes === 21) {
 421                  activateRing2(mask);
 422                  activateRing3Nodes(mask, [42, 38, 40]);
 423              }
 424              else if (totalNodes === 22) {
 425                  activateRing2(mask);
 426                  activateRing3Nodes(mask, [37, 38, 40, 41]);
 427              }
 428              else if (totalNodes === 23) {
 429                  activateRing2(mask);
 430                  activateRing3Nodes(mask, [37, 38, 40, 41, 42]); // 22-node setup + 42
 431              }
 432              else if (totalNodes === 24) {
 433                  activateRing2(mask);
 434                  activateRing3Nodes(mask, [37, 38, 39, 40, 41, 42]); // 23-node setup + 39
 435              }
 436              else if (totalNodes === 25) {
 437                  activateRing2(mask);
 438                  activateRing3Nodes(mask, [37, 38, 40, 41, 42, 29, 30]); // 24-node setup - 39 + 29,30
 439              }
 440              else if (totalNodes === 26) {
 441                  activateRing2(mask);
 442                  activateRing3Nodes(mask, [37, 38, 40, 41, 29, 30, 35, 36]); // 25-node setup - 42 + 35,36
 443              }
 444              else if (totalNodes === 27) {
 445                  // All Ring 2 nodes (7-18) active
 446                  for (let i = 6; i < 18; i++) {
 447                      mask[i] = true;
 448                  }
 449                  // Ring 3: Complex modifications from 26-node setup
 450                  mask[36] = true; // Node 37 (index 36)
 451                  // mask[37] = false; // Node 38 turned off (already false by default)
 452                  // mask[39] = false; // Node 40 turned off (already false by default)
 453                  mask[40] = true; // Node 41 (index 40)
 454                  // mask[28] = false; // Node 29 turned off (already false by default)
 455                  // mask[29] = false; // Node 30 turned off (already false by default)
 456                  mask[34] = true; // Node 35 (index 34)
 457                  mask[35] = true; // Node 36 (index 35)
 458                  mask[38] = true; // Node 39 (index 38) - turn back on
 459                  mask[26] = true; // Node 27 (index 26) - additional
 460                  mask[27] = true; // Node 28 (index 27) - additional
 461                  mask[30] = true; // Node 31 (index 30) - additional
 462                  mask[31] = true; // Node 32 (index 31) - additional
 463              }
 464              else if (totalNodes === 28) {
 465                  // All Ring 2 nodes (7-18) active
 466                  for (let i = 6; i < 18; i++) {
 467                      mask[i] = true;
 468                  }
 469                  // Ring 3: 27-node setup - Node 39 + Nodes 29, 30
 470                  mask[36] = true; // Node 37 (index 36)
 471                  mask[40] = true; // Node 41 (index 40)
 472                  mask[34] = true; // Node 35 (index 34)
 473                  mask[35] = true; // Node 36 (index 35)
 474                  // mask[38] = false; // Node 39 turned off (already false by default)
 475                  mask[26] = true; // Node 27 (index 26)
 476                  mask[27] = true; // Node 28 (index 27)
 477                  mask[30] = true; // Node 31 (index 30)
 478                  mask[31] = true; // Node 32 (index 31)
 479                  mask[28] = true; // Node 29 (index 28) - additional
 480                  mask[29] = true; // Node 30 (index 29) - additional
 481              }
 482              else if (totalNodes === 29) {
 483                  // All Ring 2 nodes (7-18) active
 484                  for (let i = 6; i < 18; i++) {
 485                      mask[i] = true;
 486                  }
 487                  // Ring 3: 28-node setup with complex modifications
 488                  // mask[36] = false; // Node 37 turned off (already false by default)
 489                  // mask[40] = false; // Node 41 turned off (already false by default)
 490                  mask[34] = true; // Node 35 (index 34)
 491                  mask[35] = true; // Node 36 (index 35)
 492                  mask[26] = true; // Node 27 (index 26)
 493                  mask[27] = true; // Node 28 (index 27)
 494                  mask[30] = true; // Node 31 (index 30)
 495                  mask[31] = true; // Node 32 (index 31)
 496                  // mask[28] = false; // Node 29 turned off (already false by default)
 497                  // mask[29] = false; // Node 30 turned off (already false by default)
 498                  mask[38] = true; // Node 39 (index 38) - additional
 499                  mask[32] = true; // Node 33 (index 32) - additional
 500                  mask[33] = true; // Node 34 (index 33) - additional
 501                  mask[24] = true; // Node 25 (index 24) - additional
 502                  mask[25] = true; // Node 26 (index 25) - additional
 503              }
 504              else if (totalNodes === 30) {
 505                  // All Ring 2 nodes (7-18) active
 506                  for (let i = 6; i < 18; i++) {
 507                      mask[i] = true;
 508                  }
 509                  // Ring 3: 29-node setup - Node 39 + Nodes 29, 30
 510                  mask[34] = true; // Node 35 (index 34)
 511                  mask[35] = true; // Node 36 (index 35)
 512                  mask[26] = true; // Node 27 (index 26)
 513                  mask[27] = true; // Node 28 (index 27)
 514                  mask[30] = true; // Node 31 (index 30)
 515                  mask[31] = true; // Node 32 (index 31)
 516                  // mask[38] = false; // Node 39 turned off (already false by default)
 517                  mask[32] = true; // Node 33 (index 32)
 518                  mask[33] = true; // Node 34 (index 33)
 519                  mask[24] = true; // Node 25 (index 24)
 520                  mask[25] = true; // Node 26 (index 25)
 521                  mask[28] = true; // Node 29 (index 28) - additional
 522                  mask[29] = true; // Node 30 (index 29) - additional
 523              }
 524              else if (totalNodes === 31) {
 525                  // All Ring 2 nodes (7-18) active
 526                  for (let i = 6; i < 18; i++) {
 527                      mask[i] = true;
 528                  }
 529                  // Ring 3: 30-node setup - Nodes 35, 36 + Nodes 24, 42, 19
 530                  // mask[34] = false; // Node 35 turned off (already false by default)
 531                  // mask[35] = false; // Node 36 turned off (already false by default)
 532                  mask[26] = true; // Node 27 (index 26)
 533                  mask[27] = true; // Node 28 (index 27)
 534                  mask[30] = true; // Node 31 (index 30)
 535                  mask[31] = true; // Node 32 (index 31)
 536                  mask[32] = true; // Node 33 (index 32)
 537                  mask[33] = true; // Node 34 (index 33)
 538                  mask[24] = true; // Node 25 (index 24)
 539                  mask[25] = true; // Node 26 (index 25)
 540                  mask[28] = true; // Node 29 (index 28)
 541                  mask[29] = true; // Node 30 (index 29)
 542                  mask[23] = true; // Node 24 (index 23) - additional
 543                  mask[41] = true; // Node 42 (index 41) - additional
 544                  mask[18] = true; // Node 19 (index 18) - additional
 545              }
 546              // For 32+ nodes, use existing logic
 547              else if (totalNodes > 31 && totalNodes < 32) {
 548                  for (let i = 6; i < Math.min(totalNodes, 42); i++) {
 549                      mask[i] = true;
 550                  }
 551              }
 552              else if (totalNodes === 32) {
 553                  // All Ring 2 nodes (7-18) active
 554                  for (let i = 6; i < 18; i++) {
 555                      mask[i] = true;
 556                  }
 557                  // Ring 3: 31-node setup - Nodes 29, 30 + Nodes 21, 39, 20
 558                  mask[26] = true; // Node 27 (index 26)
 559                  mask[27] = true; // Node 28 (index 27)
 560                  mask[30] = true; // Node 31 (index 30)
 561                  mask[31] = true; // Node 32 (index 31)
 562                  mask[32] = true; // Node 33 (index 32)
 563                  mask[33] = true; // Node 34 (index 33)
 564                  mask[24] = true; // Node 25 (index 24)
 565                  mask[25] = true; // Node 26 (index 25)
 566                  // mask[28] = false; // Node 29 turned off (already false by default)
 567                  // mask[29] = false; // Node 30 turned off (already false by default)
 568                  mask[23] = true; // Node 24 (index 23)
 569                  mask[41] = true; // Node 42 (index 41)
 570                  mask[18] = true; // Node 19 (index 18)
 571                  mask[20] = true; // Node 21 (index 20) - additional
 572                  mask[38] = true; // Node 39 (index 38) - additional
 573                  mask[21] = true; // Node 22 (index 21) - additional
 574              }
 575              else if (totalNodes === 33) {
 576                  // All Ring 2 nodes (7-18) active
 577                  for (let i = 6; i < 18; i++) {
 578                      mask[i] = true;
 579                  }
 580                  // Ring 3: 32-node setup - Node 42 + Nodes 35, 36
 581                  mask[26] = true; // Node 27 (index 26)
 582                  mask[27] = true; // Node 28 (index 27)
 583                  mask[30] = true; // Node 31 (index 30)
 584                  mask[31] = true; // Node 32 (index 31)
 585                  mask[32] = true; // Node 33 (index 32)
 586                  mask[33] = true; // Node 34 (index 33)
 587                  mask[24] = true; // Node 25 (index 24)
 588                  mask[25] = true; // Node 26 (index 25)
 589                  mask[23] = true; // Node 24 (index 23)
 590                  // mask[41] = false; // Node 42 turned off (already false by default)
 591                  mask[18] = true; // Node 19 (index 18)
 592                  mask[20] = true; // Node 21 (index 20)
 593                  mask[38] = true; // Node 39 (index 38)
 594                  mask[21] = true; // Node 22 (index 21)
 595                  mask[34] = true; // Node 35 (index 34) - additional
 596                  mask[35] = true; // Node 36 (index 35) - additional
 597              }
 598              else if (totalNodes === 34) {
 599                  // All Ring 2 nodes (7-18) active
 600                  for (let i = 6; i < 18; i++) {
 601                      mask[i] = true;
 602                  }
 603                  // Ring 3: 33-node setup - Node 39 + Nodes 30, 29
 604                  mask[26] = true; // Node 27 (index 26)
 605                  mask[27] = true; // Node 28 (index 27)
 606                  mask[30] = true; // Node 31 (index 30)
 607                  mask[31] = true; // Node 32 (index 31)
 608                  mask[32] = true; // Node 33 (index 32)
 609                  mask[33] = true; // Node 34 (index 33)
 610                  mask[24] = true; // Node 25 (index 24)
 611                  mask[25] = true; // Node 26 (index 25)
 612                  mask[23] = true; // Node 24 (index 23)
 613                  mask[18] = true; // Node 19 (index 18)
 614                  mask[20] = true; // Node 21 (index 20)
 615                  // mask[38] = false; // Node 39 turned off (already false by default)
 616                  mask[21] = true; // Node 22 (index 21)
 617                  mask[34] = true; // Node 35 (index 34)
 618                  mask[35] = true; // Node 36 (index 35)
 619                  mask[29] = true; // Node 30 (index 29) - additional
 620                  mask[28] = true; // Node 29 (index 28) - additional
 621              }
 622              else if (totalNodes === 35) {
 623                  // All Ring 2 nodes (7-18) active
 624                  for (let i = 6; i < 18; i++) {
 625                      mask[i] = true;
 626                  }
 627                  // Ring 3: 34-node setup - Nodes 30, 29 + Nodes 39, 23, 20
 628                  mask[26] = true; // Node 27 (index 26)
 629                  mask[27] = true; // Node 28 (index 27)
 630                  mask[30] = true; // Node 31 (index 30)
 631                  mask[31] = true; // Node 32 (index 31)
 632                  mask[32] = true; // Node 33 (index 32)
 633                  mask[33] = true; // Node 34 (index 33)
 634                  mask[24] = true; // Node 25 (index 24)
 635                  mask[25] = true; // Node 26 (index 25)
 636                  mask[23] = true; // Node 24 (index 23)
 637                  mask[18] = true; // Node 19 (index 18)
 638                  mask[20] = true; // Node 21 (index 20)
 639                  mask[21] = true; // Node 22 (index 21)
 640                  mask[34] = true; // Node 35 (index 34)
 641                  mask[35] = true; // Node 36 (index 35)
 642                  // mask[29] = false; // Node 30 turned off (already false by default)
 643                  // mask[28] = false; // Node 29 turned off (already false by default)
 644                  mask[38] = true; // Node 39 (index 38) - additional
 645                  mask[22] = true; // Node 23 (index 22) - additional
 646                  mask[19] = true; // Node 20 (index 19) - additional
 647              }
 648              else if (totalNodes === 36) {
 649                  // All Ring 2 nodes (7-18) active
 650                  for (let i = 6; i < 18; i++) {
 651                      mask[i] = true;
 652                  }
 653                  // Ring 3: 35-node setup - Node 39 + Nodes 30, 29
 654                  mask[26] = true; // Node 27 (index 26)
 655                  mask[27] = true; // Node 28 (index 27)
 656                  mask[30] = true; // Node 31 (index 30)
 657                  mask[31] = true; // Node 32 (index 31)
 658                  mask[32] = true; // Node 33 (index 32)
 659                  mask[33] = true; // Node 34 (index 33)
 660                  mask[24] = true; // Node 25 (index 24)
 661                  mask[25] = true; // Node 26 (index 25)
 662                  mask[23] = true; // Node 24 (index 23)
 663                  mask[18] = true; // Node 19 (index 18)
 664                  mask[20] = true; // Node 21 (index 20)
 665                  mask[21] = true; // Node 22 (index 21)
 666                  mask[34] = true; // Node 35 (index 34)
 667                  mask[35] = true; // Node 36 (index 35)
 668                  // mask[38] = false; // Node 39 turned off (already false by default)
 669                  mask[22] = true; // Node 23 (index 22)
 670                  mask[19] = true; // Node 20 (index 19)
 671                  mask[29] = true; // Node 30 (index 29) - additional
 672                  mask[28] = true; // Node 29 (index 28) - additional
 673              }
 674              // For other counts > 36, use sequential logic for now
 675              else if (totalNodes > 36) {
 676                  for (let i = 6; i < Math.min(totalNodes, 42); i++) {
 677                      mask[i] = true;
 678                  }
 679              }
 680              
 681              return mask;
 682          }
 683          
 684          // Raw values from actual algorithm
 685          const RING_RADII = [40, 125, 335]; // From RingLayout.ts
 686          
 687          function visualizeLayout(totalNodes) {
 688              const canvas = document.getElementById('canvas');
 689              const ctx = canvas.getContext('2d');
 690              const centerX = canvas.width / 2;
 691              const centerY = canvas.height / 2;
 692              const scale = 0.8; // Scale factor to fit in canvas
 693              
 694              // Clear canvas
 695              ctx.fillStyle = '#000';
 696              ctx.fillRect(0, 0, canvas.width, canvas.height);
 697              
 698              // Draw center point
 699              ctx.fillStyle = '#ff6b6b';
 700              ctx.beginPath();
 701              ctx.arc(centerX, centerY, 8, 0, 2 * Math.PI);
 702              ctx.fill();
 703              
 704              // Add center label
 705              ctx.fillStyle = '#fff';
 706              ctx.font = '12px Arial';
 707              ctx.textAlign = 'center';
 708              ctx.fillText('CENTER', centerX, centerY - 15);
 709              
 710              // Hybrid approach: Original Ring 1 logic + masking for Ring 2/3
 711              const colors = ['#4ecdc4', '#45b7d1', '#96ceb4'];
 712              
 713              // Draw ring circles
 714              for (let ringIndex = 0; ringIndex < 3; ringIndex++) {
 715                  const radius = RING_RADII[ringIndex] * scale;
 716                  ctx.strokeStyle = '#333';
 717                  ctx.lineWidth = 1;
 718                  ctx.beginPath();
 719                  ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
 720                  ctx.stroke();
 721              }
 722              
 723              let nodeDisplayNumber = 1;
 724              let ring1Count = 0, ring2Count = 0, ring3Count = 0;
 725              
 726              // Ring 1: Use original dynamic equidistant spacing (nodes 1-6)
 727              if (totalNodes >= 1) {
 728                  const ring1Nodes = Math.min(totalNodes, 6);
 729                  ring1Count = ring1Nodes;
 730                  
 731                  // Original Ring 1 logic with proper rotation
 732                  let startAngle = -Math.PI / 2; // Default: start at top (point up)
 733                  if (ring1Nodes === 6) {
 734                      startAngle = -Math.PI / 2 + Math.PI / 6; // Rotate by 30° (flat edge at top)
 735                  }
 736                  
 737                  ctx.fillStyle = colors[0]; // Ring 1 color
 738                  for (let i = 0; i < ring1Nodes; i++) {
 739                      const angle = (i / ring1Nodes) * 2 * Math.PI + startAngle;
 740                      const x = centerX + RING_RADII[0] * scale * Math.cos(angle);
 741                      const y = centerY + RING_RADII[0] * scale * Math.sin(angle);
 742                      
 743                      ctx.beginPath();
 744                      ctx.arc(x, y, 6, 0, 2 * Math.PI);
 745                      ctx.fill();
 746                      
 747                      ctx.fillStyle = '#fff';
 748                      ctx.font = '10px Arial';
 749                      ctx.textAlign = 'center';
 750                      ctx.fillText(nodeDisplayNumber.toString(), x, y + 3);
 751                      ctx.fillStyle = colors[0];
 752                      
 753                      nodeDisplayNumber++;
 754                  }
 755              }
 756              
 757              // Ring 2 & Ring 3: Use masking approach for nodes 7+
 758              if (totalNodes > 6) {
 759                  const all42Positions = generateAll36StaticPositions(); // Now returns 42 positions
 760                  const activeMask = getActiveMask(totalNodes);
 761                  
 762                  // Draw Ring 2 and Ring 3 nodes using mask (skip Ring 1 indices 0-5)
 763                  for (let i = 6; i < 42; i++) {
 764                      if (!activeMask[i]) continue;
 765                      
 766                      const pos = all42Positions[i];
 767                      const x = centerX + pos[0] * scale;
 768                      const y = centerY + pos[1] * scale;
 769                      
 770                      // Determine color based on position range
 771                      let color;
 772                      if (i < 18) {
 773                          color = colors[1]; // Ring 2
 774                          ring2Count++;
 775                      } else {
 776                          color = colors[2]; // Ring 3
 777                          ring3Count++;
 778                      }
 779                      
 780                      ctx.fillStyle = color;
 781                      ctx.beginPath();
 782                      ctx.arc(x, y, 6, 0, 2 * Math.PI);
 783                      ctx.fill();
 784                      
 785                      // Add node number
 786                      ctx.fillStyle = '#fff';
 787                      ctx.font = '10px Arial';
 788                      ctx.textAlign = 'center';
 789                      ctx.fillText(nodeDisplayNumber.toString(), x, y + 3);
 790                      
 791                      nodeDisplayNumber++;
 792                  }
 793              }
 794              
 795              // Add ring labels
 796              ctx.fillStyle = '#fff';
 797              ctx.font = '14px Arial';
 798              ctx.textAlign = 'left';
 799              ctx.fillText(`Ring 1: ${ring1Count} nodes`, 20, 30);
 800              ctx.fillText(`Ring 2: ${ring2Count} nodes`, 20, 50);
 801              ctx.fillText(`Ring 3: ${ring3Count} nodes`, 20, 70);
 802              
 803              // Update info
 804              document.getElementById('info').textContent = 
 805                  `Ring 1: ${ring1Count} nodes | Ring 2: ${ring2Count} nodes | Ring 3: ${ring3Count} nodes`;
 806          }
 807          
 808          function setNodeCount(count) {
 809              document.getElementById('nodeCount').value = count;
 810              document.getElementById('nodeCountValue').textContent = count;
 811              visualizeLayout(count);
 812          }
 813          
 814          function nextNode() {
 815              const current = parseInt(document.getElementById('nodeCount').value);
 816              if (current < 36) {
 817                  setNodeCount(current + 1);
 818              }
 819          }
 820          
 821          function previousNode() {
 822              const current = parseInt(document.getElementById('nodeCount').value);
 823              if (current > 1) {
 824                  setNodeCount(current - 1);
 825              }
 826          }
 827          
 828          function visualizeProgression() {
 829              let count = 1;
 830              const interval = setInterval(() => {
 831                  setNodeCount(count);
 832                  count++;
 833                  if (count > 36) {
 834                      clearInterval(interval);
 835                  }
 836              }, 300); // 300ms between frames for better visibility
 837          }
 838          
 839          // Event listeners
 840          document.getElementById('nodeCount').addEventListener('input', (e) => {
 841              const value = parseInt(e.target.value);
 842              document.getElementById('nodeCountValue').textContent = value;
 843              visualizeLayout(value);
 844          });
 845          
 846          // Static 36-node coordinate system
 847          function createStaticCoordinateSystem() {
 848              const canvas = document.getElementById('staticCanvas');
 849              const ctx = canvas.getContext('2d');
 850              const centerX = canvas.width / 2;
 851              const centerY = canvas.height / 2;
 852              const scale = 0.8;
 853              
 854              // Clear canvas
 855              ctx.fillStyle = '#000';
 856              ctx.fillRect(0, 0, canvas.width, canvas.height);
 857              
 858              // Draw center point
 859              ctx.fillStyle = '#ff6b6b';
 860              ctx.beginPath();
 861              ctx.arc(centerX, centerY, 8, 0, 2 * Math.PI);
 862              ctx.fill();
 863              ctx.fillStyle = '#fff';
 864              ctx.font = '12px Arial';
 865              ctx.textAlign = 'center';
 866              ctx.fillText('CENTER', centerX, centerY - 15);
 867              
 868              let nodeNumber = 1;
 869              const colors = ['#4ecdc4', '#45b7d1', '#96ceb4']; // Teal, blue, green
 870              
 871              // Ring 1: 6 nodes (nodes 1-6) - Simple hexagon with flat edge at top
 872              const ring1Radius = RING_RADII[0] * scale;
 873              ctx.strokeStyle = '#333';
 874              ctx.lineWidth = 1;
 875              ctx.beginPath();
 876              ctx.arc(centerX, centerY, ring1Radius, 0, 2 * Math.PI);
 877              ctx.stroke();
 878              
 879              ctx.fillStyle = colors[0];
 880              const ring1StartAngle = -Math.PI / 2 + Math.PI / 6; // 30° rotation for flat edge at top
 881              for (let i = 0; i < 6; i++) {
 882                  const angle = (i / 6) * 2 * Math.PI + ring1StartAngle;
 883                  const x = centerX + ring1Radius * Math.cos(angle);
 884                  const y = centerY + ring1Radius * Math.sin(angle);
 885                  
 886                  ctx.beginPath();
 887                  ctx.arc(x, y, 8, 0, 2 * Math.PI);
 888                  ctx.fill();
 889                  
 890                  ctx.fillStyle = '#fff';
 891                  ctx.font = 'bold 12px Arial';
 892                  ctx.textAlign = 'center';
 893                  ctx.fillText(nodeNumber.toString(), x, y + 4);
 894                  ctx.fillStyle = colors[0];
 895                  
 896                  nodeNumber++;
 897              }
 898              
 899              // Ring 2: 12 nodes (nodes 7-18) - Hexagon corners + edge midpoints
 900              const ring2Radius = RING_RADII[1] * scale;
 901              ctx.strokeStyle = '#333';
 902              ctx.beginPath();
 903              ctx.arc(centerX, centerY, ring2Radius, 0, 2 * Math.PI);
 904              ctx.stroke();
 905              
 906              ctx.fillStyle = colors[1];
 907              
 908              // First 6 nodes: Hexagon corners (aligned with Ring 1 but no rotation) - reduced radius to lie on hexagon edges
 909              const ring2EdgeRadius = ring2Radius * Math.cos(Math.PI / 6); // cos(30°) = √3/2 ≈ 0.866
 910              for (let i = 0; i < 6; i++) {
 911                  const angle = (i / 6) * 2 * Math.PI - Math.PI / 2; // Start at top
 912                  const x = centerX + ring2EdgeRadius * Math.cos(angle);
 913                  const y = centerY + ring2EdgeRadius * Math.sin(angle);
 914                  
 915                  ctx.beginPath();
 916                  ctx.arc(x, y, 8, 0, 2 * Math.PI);
 917                  ctx.fill();
 918                  
 919                  ctx.fillStyle = '#fff';
 920                  ctx.font = 'bold 12px Arial';
 921                  ctx.textAlign = 'center';
 922                  ctx.fillText(nodeNumber.toString(), x, y + 4);
 923                  ctx.fillStyle = colors[1];
 924                  
 925                  nodeNumber++;
 926              }
 927              
 928              // Next 6 nodes: Edge midpoints (30° offset) - full radius (these are the actual hexagon corners)
 929              for (let i = 0; i < 6; i++) {
 930                  const angle = (i / 6) * 2 * Math.PI - Math.PI / 2 + Math.PI / 6; // 30° offset
 931                  const x = centerX + ring2Radius * Math.cos(angle);
 932                  const y = centerY + ring2Radius * Math.sin(angle);
 933                  
 934                  ctx.beginPath();
 935                  ctx.arc(x, y, 8, 0, 2 * Math.PI);
 936                  ctx.fill();
 937                  
 938                  ctx.fillStyle = '#fff';
 939                  ctx.font = 'bold 12px Arial';
 940                  ctx.textAlign = 'center';
 941                  ctx.fillText(nodeNumber.toString(), x, y + 4);
 942                  ctx.fillStyle = colors[1];
 943                  
 944                  nodeNumber++;
 945              }
 946              
 947              // Ring 3: Only nodes 19-25 at hexagon vertices (step-by-step approach)
 948              const ring3Radius = RING_RADII[2] * scale;
 949              ctx.strokeStyle = '#333';
 950              ctx.beginPath();
 951              ctx.arc(centerX, centerY, ring3Radius, 0, 2 * Math.PI);
 952              ctx.stroke();
 953              
 954              ctx.fillStyle = colors[2];
 955              
 956              // Nodes 19-24: Hexagon vertices starting at 30° (0° = top)
 957              const hexagonAngles = [
 958                  30,   // Node 19 at 30°
 959                  90,   // Node 20 at 90° 
 960                  150,  // Node 21 at 150°
 961                  210,  // Node 22 at 210°
 962                  270,  // Node 23 at 270°
 963                  330,  // Node 24 at 330°
 964                  30    // Node 25 at 30° (back to start - this creates 7 nodes with one overlap)
 965              ];
 966              
 967              // Place nodes 19-24 at specified angles  
 968              for (let i = 0; i < 6; i++) { // 6 nodes (19-24)
 969                  const angleDegrees = hexagonAngles[i];
 970                  const angleRadians = (angleDegrees - 90) * Math.PI / 180; // Convert to radians, adjust for 0° = top
 971                  const x = centerX + ring3Radius * Math.cos(angleRadians);
 972                  const y = centerY + ring3Radius * Math.sin(angleRadians);
 973                  
 974                  ctx.beginPath();
 975                  ctx.arc(x, y, 8, 0, 2 * Math.PI);
 976                  ctx.fill();
 977                  
 978                  ctx.fillStyle = '#fff';
 979                  ctx.font = 'bold 10px Arial';
 980                  ctx.textAlign = 'center';
 981                  ctx.fillText((19 + i).toString(), x, y + 3);
 982                  ctx.fillStyle = colors[2];
 983              }
 984              
 985              // Now add edge nodes (25-36) using path parameterization between vertices
 986              const vertexPositions = [];
 987              
 988              // Calculate world coordinates for the 6 vertices (nodes 19-24) - unscaled
 989              const baseRadius = RING_RADII[2]; // Use base radius before scaling
 990              for (let i = 0; i < 6; i++) {
 991                  const angleDegrees = hexagonAngles[i];
 992                  const angleRadians = (angleDegrees - 90) * Math.PI / 180;
 993                  const x = baseRadius * Math.cos(angleRadians); // True world coordinates
 994                  const y = baseRadius * Math.sin(angleRadians);
 995                  vertexPositions.push([x, y]);
 996              }
 997              
 998              // Path parameterization function: lerp between two points
 999              function lerpPath(pointA, pointB, t) {
1000                  return [
1001                      pointA[0] + t * (pointB[0] - pointA[0]),
1002                      pointA[1] + t * (pointB[1] - pointA[1])
1003                  ];
1004              }
1005              
1006              let edgeNodeNumber = 25; // Start with node 25
1007              
1008              // For each edge (6 edges total), place 2 nodes at t=1/3 and t=2/3
1009              for (let edgeIndex = 0; edgeIndex < 6; edgeIndex++) {
1010                  const startVertex = vertexPositions[edgeIndex];
1011                  const endVertex = vertexPositions[(edgeIndex + 1) % 6]; // Wrap around for last edge
1012                  
1013                  // Place 2 nodes on this edge: at t=1/3 and t=2/3
1014                  for (let nodeOnEdge = 0; nodeOnEdge < 2; nodeOnEdge++) {
1015                      const t = (nodeOnEdge + 1) / 3; // t = 1/3 for first node, 2/3 for second
1016                      const [worldX, worldY] = lerpPath(startVertex, endVertex, t);
1017                      
1018                      // Convert to canvas coordinates - use the lerp result directly (no radius adjustment!)
1019                      const x = centerX + worldX * scale;
1020                      const y = centerY + worldY * scale;
1021                      
1022                      ctx.beginPath();
1023                      ctx.arc(x, y, 8, 0, 2 * Math.PI);
1024                      ctx.fill();
1025                      
1026                      ctx.fillStyle = '#fff';
1027                      ctx.font = 'bold 10px Arial';
1028                      ctx.textAlign = 'center';
1029                      ctx.fillText(edgeNodeNumber.toString(), x, y + 3);
1030                      ctx.fillStyle = colors[2];
1031                      
1032                      edgeNodeNumber++;
1033                  }
1034              }
1035              
1036              // Add 6 additional edge midpoint nodes (37-42) at t=0.5
1037              let additionalNodeNumber = 37;
1038              for (let edgeIndex = 0; edgeIndex < 6; edgeIndex++) {
1039                  const startVertex = vertexPositions[edgeIndex];
1040                  const endVertex = vertexPositions[(edgeIndex + 1) % 6];
1041                  
1042                  const t = 0.5; // Exact midpoint
1043                  const [worldX, worldY] = lerpPath(startVertex, endVertex, t);
1044                  
1045                  // Convert to canvas coordinates
1046                  const x = centerX + worldX * scale;
1047                  const y = centerY + worldY * scale;
1048                  
1049                  ctx.beginPath();
1050                  ctx.arc(x, y, 8, 0, 2 * Math.PI);
1051                  ctx.fill();
1052                  
1053                  ctx.fillStyle = '#fff';
1054                  ctx.font = 'bold 10px Arial';
1055                  ctx.textAlign = 'center';
1056                  ctx.fillText(additionalNodeNumber.toString(), x, y + 3);
1057                  ctx.fillStyle = colors[2];
1058                  
1059                  additionalNodeNumber++;
1060              }
1061              
1062              // Add ring labels
1063              ctx.fillStyle = '#fff';
1064              ctx.font = '14px Arial';
1065              ctx.textAlign = 'left';
1066              ctx.fillText('Ring 1: Nodes 1-6', 20, 30);
1067              ctx.fillText('Ring 2: Nodes 7-18', 20, 50);
1068              ctx.fillText('Ring 3: Nodes 19-42', 20, 70);
1069          }
1070          
1071          // Initial visualizations
1072          visualizeLayout(6);
1073          createStaticCoordinateSystem();
1074      </script>
1075  </body>
1076  </html>