# Feature: Heading Management

## Description
Manages UAV heading state per flight. Tracks current heading, maintains heading history, detects sharp turns, and determines when rotation sweeps are required. This is the stateful core of the rotation manager.

## Component APIs Implemented
- `get_current_heading(flight_id: str) -> Optional[float]`
- `update_heading(flight_id: str, frame_id: int, heading: float, timestamp: datetime) -> bool`
- `detect_sharp_turn(flight_id: str, new_heading: float) -> bool`
- `requires_rotation_sweep(flight_id: str) -> bool`

## External Tools and Services
None - pure Python state management.

## Internal Methods
- `_normalize_angle(angle: float) -> float`: Normalizes angle to 0-360 range
- `_calculate_angle_delta(angle1: float, angle2: float) -> float`: Calculates smallest delta between two angles (handles wraparound)
- `_get_flight_state(flight_id: str) -> HeadingHistory`: Gets or creates heading state for flight
- `_add_to_history(flight_id: str, heading: float)`: Adds heading to circular history buffer
- `_set_sweep_required(flight_id: str, required: bool)`: Sets sweep required flag (used after tracking loss)

## Unit Tests

### get_current_heading
1. **New flight**: Returns None (no heading set)
2. **After update**: Returns last updated heading
3. **Multiple flights**: Each flight has independent heading

### update_heading
1. **First heading**: Sets initial heading, returns True
2. **Update heading**: Overwrites previous heading
3. **Angle normalization**: 370° stored as 10°
4. **Negative normalization**: -30° stored as 330°
5. **History tracking**: Heading added to history list
6. **History limit**: Only last 10 headings kept

### detect_sharp_turn
1. **No current heading**: Returns False (can't detect turn)
2. **Small turn (15°)**: 60° → 75° returns False
3. **Sharp turn (60°)**: 60° → 120° returns True
4. **Exactly 45°**: Returns False (threshold is >45)
5. **Exactly 46°**: Returns True
6. **Wraparound small**: 350° → 20° returns False (30° delta)
7. **Wraparound sharp**: 350° → 60° returns True (70° delta)
8. **180° turn**: 0° → 180° returns True

### requires_rotation_sweep
1. **First frame (no heading)**: Returns True
2. **Heading known, no flags**: Returns False
3. **Tracking loss flag set**: Returns True
4. **Sharp turn detected recently**: Returns True
5. **After successful match**: Returns False

## Integration Tests

### Test 1: Heading Lifecycle
1. Create new flight
2. get_current_heading → None
3. requires_rotation_sweep → True
4. update_heading(heading=45°)
5. get_current_heading → 45°
6. requires_rotation_sweep → False

### Test 2: Sharp Turn Flow
1. update_heading(heading=90°)
2. detect_sharp_turn(new_heading=100°) → False
3. detect_sharp_turn(new_heading=180°) → True
4. Set sweep required flag
5. requires_rotation_sweep → True

