# Component — `scan_controller` **Layer**: Decision + Memory **Status**: forward-looking design (Rust) ## 1. Purpose The system's brain. A deterministic typed state machine — `ZoomedOut`, `ZoomedIn { roi, hold_started_at }`, and `TargetFollow { target_id, started_at }`. Owns the POI queue, timeouts, the ≤5 POIs/min operator-review cap, the confidence-scaled operator-decision window, gimbal command issuance, and the new/moved/existing/removed dispatch into `mapobjects_store`. The full behaviour-tree spec — including tick scenarios and the 15 fixed-wing rules — lives in `system-flows.md §F4`. ## 2. Inputs | Input | Source | Cadence | Notes | |---|---|---|---| | `DetectionBatch` | `detection_client` | per frame | Tier 1 primitives. | | `MovementCandidate` | `movement_detector` | per frame at both zoom-out and zoom-in (suppressed only during `TargetFollow`) | Each candidate carries `source_zoom_band`. | | `Tier2Evidence` | `semantic_analyzer` | per zoom-in hold | Path / endpoint / concealment scoring. | | `VlmAssessment` | `vlm_client` (optional) | per zoom-in endpoint hold | `status: disabled` if VLM is off. | | Operator commands | `operator_bridge` | event | confirm / decline / target-follow start / target-follow release. Authenticated, signed, replay-protected upstream of this component. | | UAV telemetry | `mavlink_layer` (via `mission_executor`) | 10 Hz target | Position used for proximity-weighted POI priority and middle-waypoint computation. | | Mission state | `mission_executor` | event | Current waypoint, mission progress; used for sweep-vs-route alignment. | | MapObjects sync state | `mapobjects_store` | event at startup + post-flight | `synced` / `cached_fallback` / `degraded` — surfaces a health flag and (for `degraded`) suppresses MapObject diff classifications until corrected. | ## 3. Outputs | Output | Consumer | Shape | |---|---|---| | `GimbalCommand` (yaw / pitch / zoom) | `gimbal_controller` | per state-machine tick or per zoom-in plan step | | `POI` to operator | `operator_bridge` (then `telemetry_stream`) | enqueue / dequeue events | | Middle-waypoint hint | `mission_executor` | event on operator-confirmed target | | MapObjects update | `mapobjects_store` | new / moved / existing / removed dispatch | | Health metric | health aggregator | `state`, `pois_in_queue`, `pois_per_min`, `tick_latency_p99`, `last_state_change_ts`, `mapobjects_sync_state`. | ## 4. Key Responsibilities - Run the `ZoomedOut` / `ZoomedIn` / `TargetFollow` state machine. Transitions are explicit, typed, and fully enumerated; no ad-hoc booleans. - Maintain the POI queue ordered by `confidence × proximity_to_current_camera × age_factor`. Hard-cap output to ≤5 POIs/min surfaced to the operator. - Apply the confidence-scaled operator decision window (40 % → 30 s, 100 % → 120 s, linear; below 40 % the POI is not surfaced). Timeout = forget; decline = `IgnoredItem` entry via `mapobjects_store`. - Suppress new POIs whose `(MGRS, class_group)` matches an existing `IgnoredItem`. - For each new detection or movement candidate: compute the H3 cell, ask `mapobjects_store` to classify as new / moved / existing, and only surface non-existing entries. - **Zoom-in candidate handling.** When a `MovementCandidate` arrives with `source_zoom_band = zoomed_in`, evaluate against the current ROI: if inside, bump current-ROI confidence; if outside the ROI but inside the broader zoomed FOV, enqueue as a candidate-POI; only interrupt the current zoom-in hold if the candidate's priority exceeds the current hold's priority. - On operator confirmation: hand a middle-waypoint hint to `mission_executor`, transition to `TargetFollow`, and command `gimbal_controller` to keep the target in the centre 25 % of frame. - On operator decline / timeout / target loss: append (decline only) an `IgnoredItem` and return to `ZoomedOut`. - On `mapobjects_store` reporting `sync_state = degraded`, surface health → red and **do not** classify new detections (avoid corrupting the central observation log on next push); continue to surface POIs to the operator on Tier-1 + movement evidence alone. ## 5. Internal State The state machine lives entirely in this component. State variables: - Current state: `ZoomedOut | ZoomedIn { roi, hold_started_at } | TargetFollow { target_id, started_at }`. - POI queue: ordered, with per-entry priority and queue position. - Per-class operator-decision-window thresholds. - Last-N tick timestamps for tick-latency observability. - Frame-rate floor monitor: when sustained FPS < 10, suppress `ZoomedOut → ZoomedIn` transitions and surface health → yellow. State is in-process only; restart starts in `ZoomedOut` with an empty queue. ## 6. Failure Modes | Failure | Detection | Behaviour | |---|---|---| | `detection_client` health red | health input | Continue zoom-out sweep; emit no new POIs from Tier 1; movement candidates still flow. | | `movement_detector` health red | health input | Continue; lose movement-candidate enqueueing. | | `semantic_analyzer` health red | health input | Skip Tier 2; surface POIs with Tier-1-only evidence; flag in operator overlay. | | `vlm_client` returns `status: disabled \| timeout \| ipc_error \| schema_invalid` | per-call status | Surface POI without VLM evidence (fail-closed). | | `gimbal_controller` not ready | health input | Stay in current state; alert; do not silently drop scan steps. | | `operator_bridge` disconnected | health input | Continue zoom-out (operator UI is unreachable, but the system must not crash); pause POI surfacing; resume on reconnect. F10 lost-link ladder owns the larger response. | | `mapobjects_store` sync degraded | sync_state input | Suppress diff classifications; surface POIs on Tier-1 + movement only; health → red. | | Sustained FPS < 10 | self-instrumented | Suppress zoom-in transitions; health → yellow. | | Tick-latency above budget | self-instrumented | Health → yellow; investigate (likely upstream consumer slowness). | ## 7. Dependencies **In-process** (input): `detection_client`, `movement_detector`, `semantic_analyzer`, `vlm_client`, `operator_bridge`, `mission_executor`, `mapobjects_store`. **In-process** (output): `gimbal_controller`, `operator_bridge`, `mission_executor`, `mapobjects_store`. **External**: none directly. All external integrations are mediated by other components. ## 8. Non-Functional Targets | Concern | Target | |---|---| | Tick latency | ≤10 ms p99 | | POI enqueue → operator surface | ≤1 s in normal load | | POI rate to operator | ≤5 POIs/min (hard cap) | | Zoom-out → zoom-in transition | ≤2 s including physical zoom | | Zoom-in hold duration | configurable; default 5 s/POI | | Target-follow centre-window | target inside centre 25 % of frame while visible | | Frame-rate floor | ≥10 fps sustained; below this, suppress zoom-in transitions | ## 9. References - `architecture.md §3`, `§5 Architectural Principles`, `§6 NFR`, `§7.6 Scan controller and POI queue`, `§7.12 New vs Existing / Moved / Removed Object Detection`, `§7.13 MapObjects Sync`. - `system-flows.md §F4 Scan controller behaviour tree` (full BT spec, tick scenarios, 15 fixed-wing rules). - `data_model.md §POI`, `§IgnoredItem`, `§MapObject`.