# Solar Panels sun tracking

# Code side

## Class hierarchy

`Module_SolarPanel` → `Module_Deployable` → `PartBehaviourModule`

The tracking logic lives almost entirely in `Module_Deployable`, with `Module_SolarPanel` overriding two hooks (`CalculateTrackingLOS` and `PostCalculateTracking`) to add occlusion testing and power computation.

## Per-frame tick

`OnModuleFixedUpdate` (Module_Deployable.cs) calls `UpdateDeployment`, which dispatches to `CalculateTracking` only when the panel is in `Extended` state.


`CalculateTracking`:


1. **Find the tracking target.** `GetTrackingBodyTransformFrame` resolves a `trackingTransform`. For `trackingMode = "Sun"` it calls `Game.UniverseModel.GetStar(transform.Position)` to pick the nearest star dynamically.
2. **Compute tracking direction.** Converts the star's position into the part's local body frame, then transforms it into world space and into the pivot's local space (`relativeDirection`).
3. **LOS check.** Calls `CalculateTrackingLOS` (overridden in `Module_SolarPanel`).
4. **Rotate the pivot** (if LOS is clear and `isTracking` is on). For `rotationMode = YAW`:
   * Computes a yaw delta: `Atan2(relativeDirection.x, z) * 57.3` + `TrackingAlignmentOffset` (90° for the accordion).
   * Applies a `Quaternion.Lerp` toward the target using `trackingSpeed * UniverseTimeDelta`, storing the result in `dataDeployable.currentRotation`.
5. **Calls** `**PostCalculateTracking**` on `Module_SolarPanel` to update exposure and power output.

The actual `Transform.localRotation` assignment happens in `OnLateUpdate`, which runs after all physics to avoid conflicts.

## LOS / occlusion 

`Module_SolarPanel.CalculateTrackingLOS`:

Checks in order:


1. `SimBlockingBody` — set by orbital simulation when the part is in a body's shadow.
2. **Incidence dot** — `Dot(panelIncidenceDirection, sunDir)`. If the dot is ≤ 0, the sun is behind the panel and the ray isn't even cast.
3. **Physics.Raycast** from `secondaryTransform.position + sunDir * RaycastOffset` toward the sun:
   * Hit a `PQSRenderer` → blocked by terrain.
   * Hit a `PartBehavior` that is not `this.part` → blocked by another vessel part (self-hits filtered out as geometry artifacts, see the issue #684 comment).

## Power output

`Module_SolarPanel.PostCalculateTracking`:

`exposure   = Dot(PanelIncidenceDirection, sunDir)         // 0..1 flowRate   = Rate * exposure * SimStarEnergyScale * EfficiencyMultiplier `

`SimStarEnergyScale` is injected by the sim layer to account for distance from the star.

## Retraction

On retract, `HasTrackerReturnedToOrigin` lerps the pivot back to `originalRotation` (captured at spawn) in sync with the retraction animation, so the panels don't snap.

# Prefab side

Example with `solararray_2v_accordion`.

From the part JSON:

| Field | Value | Meaning |
|-------|-------|---------|
| `pivotName` | `"bone_rotator"` | The named transform in the mesh that physically rotates to face the sun |
| `secondaryTransform` | `"raycastTransform"` | The named transform used as the LOS ray origin |
| `trackingMode` | `"Sun"` | Tracks the nearest star (resolved dynamically) |
| `rotationMode` | `"YAW"` | Single-axis rotation only |
| `TrackingAlignmentOffset` | `90.0` | 90° correction because `bone_rotator`'s local forward doesn't coincide with the panel surface normal |
| `trackingSpeed` | `0.25` | Slew rate (fraction of remaining angle per `UniverseTimeDelta` tick) |
| `UseRaycastForTrackingDot` | `true` | Incidence dot computed from `raycastTransform`'s orientation, not `bone_rotator` directly |
| `PanelIncidenceDirection` | `(1, 0, 0)` | Local +X is the panel's active face axis |
| `RaycastOffset` | `0.25` | 25 cm offset to prevent the ray from hitting the panel's own collider |
| `LimitRotation` | `false` | Full 360° rotation allowed |
| `isTracking` | `true` | Enables the tracking behavior at all |

The **mesh** exposes two named transforms that must exist in the model hierarchy: `bone_rotator` (rotated each frame by the tracking math) and `raycastTransform` (a fixed reference point used for the sun-facing dot product and LOS ray origin).