# Centre-On-Target Primitive + GimbalState Publish

**Task**: AZ-656_gimbal_centre_on_target
**Name**: Centre-on-target primitive + timestamped GimbalState publish
**Description**: During `TargetFollow`, accept a centre-on-target stream (target bbox normalized) from `scan_controller` and command the gimbal to keep the target inside the centre 25 % of frame while visible. Stamp every emitted command + reported state with a monotonic timestamp so `movement_detector` can synchronise.
**Complexity**: 3 points
**Dependencies**: AZ-640_initial_structure, AZ-653_gimbal_a40_transport
**Component**: gimbal_controller
**Tracker**: AZ-656
**Epic**: AZ-634

## Problem

During target-follow, the gimbal must continuously re-aim to keep the target inside the centre 25 % of frame. The control loop must converge without overshoot, and every emitted command + every reported `GimbalState` must carry a monotonic timestamp so `movement_detector` can synchronise gimbal motion with the per-frame ego-motion estimate.

## Outcome

- `CentreOnTarget::tick(bbox_normalized, current_state) -> GimbalCommand` produces the yaw/pitch command needed to nudge the target toward frame centre; convergence within ≤3 ticks under nominal latency.
- Reported `GimbalState { yaw, pitch, zoom, ts_monotonic, command_in_flight }` is published on the state channel for `frame_ingest` (telemetry tagging) and `movement_detector` (ego-motion sync) consumption.
- If the target bbox is missing for 3 consecutive ticks, emit a `target_lost` signal to `scan_controller`.

## Scope

### Included
- Centre-25% control loop (proportional, configurable gain).
- Monotonic timestamp stamping (single source of truth: `Instant::now()` at emit point).
- `GimbalState` publisher.
- `target_lost` signal on 3 consecutive missing bboxes.

### Excluded
- Target-follow state ownership (`scan_controller`).
- Sweep (task 15) and pan plan (task 16).

## Acceptance Criteria

**AC-1: Centre convergence**
Given a target initially at bbox `(0.7, 0.5, 0.1, 0.1)` (right side of frame) and a healthy A40
When `tick()` is invoked over 3 cycles at 100 ms each
Then by the third cycle the target bbox centre is within the centre 25 % region.

**AC-2: GimbalState carries monotonic timestamp**
Given a sequence of `tick()` calls
When the resulting `GimbalState` is observed
Then `ts_monotonic` is strictly monotonically increasing across observations.

**AC-3: Target loss signals after 3 missing ticks**
Given the target bbox stream goes empty
When 3 consecutive ticks have no bbox
Then a `target_lost` signal is published exactly once; subsequent ticks do not re-emit.

## Non-Functional Requirements

**Performance**
- `tick()` p99 ≤2 ms.
- Centre convergence within ≤3 ticks at 10 Hz.

**Reliability**
- `target_lost` debounced — never spurious.

## Runtime Completeness

- **Named capability**: target-follow centre-25% loop + timestamped GimbalState publish.
- **Production code that must exist**: real control loop; real monotonic timestamping.
- **Unacceptable substitutes**: open-loop "send target position once" is not acceptable — the loop must close.
