|
319 | 319 | const GALAXY_ORBITAL_SPEED_RESPONSE_GAIN = 0.5; |
320 | 320 | const GALAXY_ORBITAL_SPEED_MAXIMUM = 4.6; |
321 | 321 | const GALAXY_ORBITAL_RADIUS_MAXIMUM = 1.06; |
322 | | - const GALAXY_BASE_ORBITAL_SPEED_BOOST = 1.625; |
323 | 322 | function galaxyOrbitalSpeedMultiplier(setting) { |
324 | 323 | const raw = Number(setting); |
325 | 324 | const value = Number.isFinite(raw) |
|
746 | 745 | const circularSpeed = Math.sqrt(Math.max(0, acceleration * localRadius)); |
747 | 746 | const multiplier = Math.max(0, Number(orbitalSpeed) || 0); |
748 | 747 | return kinematicCap |
749 | | - ? Math.min(circularSpeed * GALAXY_BASE_ORBITAL_SPEED_BOOST * multiplier, |
| 748 | + ? Math.min(circularSpeed * multiplier, |
750 | 749 | GALAXY_LOCAL_RELATIVE_SPEED_LIMIT * multiplier) |
751 | | - : Math.min(GALAXY_LOCAL_RELATIVE_SPEED_LIMIT, circularSpeed) |
752 | | - * GALAXY_BASE_ORBITAL_SPEED_BOOST * multiplier; |
| 750 | + : Math.min(GALAXY_LOCAL_RELATIVE_SPEED_LIMIT, circularSpeed) * multiplier; |
753 | 751 | } |
754 | 752 |
|
755 | 753 | /* The classic renderer's *dense* signal (`GPERF.dense`, `links>1500` in dashboard.js). Past |
|
1230 | 1228 | || ((seededHash(opts.layoutSeed, 'system:' + String(parent.id)) & 1) ? 1 : -1); |
1231 | 1229 | const targetTangent = galaxyRelativeSpeedBudget(parent, absoluteSpeedLimit, |
1232 | 1230 | Math.min(GALAXY_LOCAL_RELATIVE_SPEED_LIMIT, |
1233 | | - Math.sqrt(Math.max(0, acceleration * radius)) * GALAXY_BASE_ORBITAL_SPEED_BOOST * orbitalSpeed), |
| 1231 | + Math.sqrt(Math.max(0, acceleration * radius)) * orbitalSpeed), |
1234 | 1232 | tangentX * sign, tangentY * sign); |
1235 | 1233 | const parentId = String(parent.id); |
1236 | 1234 | const previousParent = typeof node.__galaxyOrbitAnchorId === 'string' |
|
3428 | 3426 | const globalSpeed = omega * orbit.radius; |
3429 | 3427 | const globalVx = -Math.sin(orbit.angle) * globalSpeed * direction; |
3430 | 3428 | const globalVy = Math.cos(orbit.angle) * globalSpeed * direction; |
3431 | | - moveNode(star, targetX, targetY, globalVx, globalVy); |
| 3429 | + // Initialize local phases against the old parent frame before moving the carrier. |
3432 | 3430 | const localMotion = advanceGalaxyKinematicLocalMembers(members, star, { |
3433 | 3431 | x: targetX, y: targetY, vx: globalVx, vy: globalVy, |
3434 | 3432 | }, item.core ? Object.assign({}, opts, { |
3435 | 3433 | localOrbitCache: '__galaxyKinematicCoreLocalOrbit', |
3436 | 3434 | }) : opts); |
| 3435 | + moveNode(star, targetX, targetY, globalVx, globalVy); |
3437 | 3436 | satellites += localMotion.satellites; |
3438 | 3437 | speedCapped = speedCapped || localMotion.speedCapped; |
3439 | 3438 | const carrierContact = nodeRadius(anchor) + nodeRadius(star) |
|
4551 | 4550 | const maximumExtents = new Map(systems.map(system => [ |
4552 | 4551 | system, galaxySystemMaximumEnvelopeRadius(system, opts), |
4553 | 4552 | ])); |
4554 | | - systems.sort((left, right) => maximumExtents.get(right) - maximumExtents.get(left) |
| 4553 | + /* Rigid translations and rotations introduce a few ulps in otherwise equal envelopes. |
| 4554 | + Use stable precision only for ordering, so numerical noise cannot move a system to a |
| 4555 | + different ring. Keep exact extents for every safety reservation below. */ |
| 4556 | + const extentOrder = new Map(systems.map(system => [ |
| 4557 | + system, Number(maximumExtents.get(system).toPrecision(12)), |
| 4558 | + ])); |
| 4559 | + systems.sort((left, right) => extentOrder.get(right) - extentOrder.get(left) |
4555 | 4560 | || String(left.id).localeCompare(String(right.id))); |
| 4561 | + const remainingExtents = new Array(systems.length); |
| 4562 | + let maximumRemainingExtent = 0; |
| 4563 | + for (let index = systems.length - 1; index >= 0; index--) { |
| 4564 | + maximumRemainingExtent = Math.max(maximumRemainingExtent, maximumExtents.get(systems[index])); |
| 4565 | + remainingExtents[index] = maximumRemainingExtent; |
| 4566 | + } |
4556 | 4567 | const blackHoleBodyRadius = finitePositive(anchor.radius, |
4557 | 4568 | evidenceNodeRadius(anchor, 3), 160); |
4558 | 4569 | /* Runtime horizon projection paints the explicit global anchor at twice its body radius. |
|
4567 | 4578 | /* Reserve the maximum nested local envelope, then keep a small independent lane margin. |
4568 | 4579 | This remains collision-free when the orbital-speed control reaches its maximum. */ |
4569 | 4580 | const laneSlack = GALAXY_CARRIER_LANE_SLACK; |
4570 | | - const laneExtent = maximumExtents.get(systems[cursor]) * laneSlack; |
| 4581 | + const laneExtent = remainingExtents[cursor] * laneSlack; |
4571 | 4582 | let laneRadius = Math.max(coreRadius + laneExtent + gap |
4572 | 4583 | + GALAXY_BLACK_HOLE_EXCLUSION_PADDING, |
4573 | 4584 | previousLaneRadius + previousLaneExtent + laneExtent + gap); |
|
4588 | 4599 | 'carrier-ring:' + String(laneIndex)) / 0x100000000 * Math.PI * 2; |
4589 | 4600 | for (let slot = 0; slot < count; slot++) { |
4590 | 4601 | const system = systems[cursor + slot]; |
4591 | | - /* Re-evaluate with the largest member of the next lane only; sorting makes every |
4592 | | - remaining extent no larger than this ring's conservative laneExtent. */ |
| 4602 | + // The exact remaining maximum also covers larger members within an ordering tie. |
4593 | 4603 | const angle = phaseOffset + slot * Math.PI * 2 / count; |
4594 | 4604 | const unitX = Math.cos(angle), unitY = Math.sin(angle); |
4595 | 4605 | const shiftX = anchor.x + unitX * laneRadius - system.x; |
|
6475 | 6485 | phase.angle += phase.direction * angularSpeed * timestep; |
6476 | 6486 | const unitX = Math.cos(phase.angle), unitY = Math.sin(phase.angle); |
6477 | 6487 | const tangentX = -unitY * phase.direction, tangentY = unitX * phase.direction; |
6478 | | - let targetX = parent.x + unitX * targetRadius; |
6479 | | - let targetY = parent.y + unitY * targetRadius; |
6480 | | - if (globalAnchor && parent !== globalAnchor) { |
6481 | | - const minBhDist = (finitePositive(globalAnchor.radius, evidenceNodeRadius(globalAnchor, 3), 160) * GALAXY_BLACK_HOLE_PAINT_SCALE) |
6482 | | - + nodeRadius + GALAXY_BLACK_HOLE_EXCLUSION_PADDING; |
6483 | | - const bhDx = targetX - globalAnchor.x; |
6484 | | - const bhDy = targetY - globalAnchor.y; |
6485 | | - const bhDist = Math.hypot(bhDx, bhDy); |
6486 | | - if (bhDist < minBhDist && bhDist > 1e-9) { |
6487 | | - targetX = globalAnchor.x + (bhDx / bhDist) * minBhDist; |
6488 | | - targetY = globalAnchor.y + (bhDy / bhDist) * minBhDist; |
6489 | | - } |
6490 | | - } |
| 6488 | + // The final black-hole exclusion pass translates the complete system together. |
| 6489 | + const targetX = parent.x + unitX * targetRadius; |
| 6490 | + const targetY = parent.y + unitY * targetRadius; |
6491 | 6491 | const targetVx = (Number.isFinite(parent.vx) ? parent.vx : 0) |
6492 | 6492 | + tangentX * phaseSpeed; |
6493 | 6493 | const targetVy = (Number.isFinite(parent.vy) ? parent.vy : 0) |
|
0 commit comments