# ROI CNN Inference + Size/Timeout Bounds + Concealment Scoring

**Task**: AZ-670_semantic_analyzer_roi_cnn
**Name**: ONNX/TensorRT ROI CNN + ROI size/timeout enforcement + concealment scoring
**Description**: Lightweight CNN session (ONNX/TensorRT) for endpoint-candidate concealment scoring. Bound every inference by strict ROI size and timeout. Never run on a full frame.
**Complexity**: 5 points
**Dependencies**: AZ-640_initial_structure, AZ-669_semantic_analyzer_primitive_graph
**Component**: semantic_analyzer
**Tracker**: AZ-670
**Epic**: AZ-630

## Problem

Endpoint candidates (branch piles, dark entrances, dugouts) need a concealment score that combines visual cues a primitive graph can't capture alone. A lightweight CNN session (ONNX or TensorRT) runs on bounded ROI crops with a strict timeout — never on a full frame. Oversize ROIs are rejected pre-decode. Inference timeout returns a structured `Tier2Evidence { status: timeout }` so `scan_controller` can decide to skip VLM and surface a low-evidence POI.

## Outcome

- `RoiInference::infer(roi_crop) -> Result<ConcealmentScore, RoiError>` runs the CNN session.
- ROI size check pre-decode: reject if larger than `max_roi_bytes` config; `RoiError::Oversize`.
- Wall-clock timeout `inference_timeout_ms` (default 200 ms); on timeout returns `RoiError::Timeout`.
- CNN backend: ONNX Runtime primary (CPU); TensorRT optional behind a build-time feature for Jetson.
- Health surface: `tier2_latency_p50/p99`, `roi_size_bytes_p99`, `errors_total`, `oversize_rejections_total`, `timeouts_total`.

## Scope

### Included
- ONNX Runtime binding (Rust crate `ort` or equivalent).
- TensorRT integration behind feature flag (defer real impl if not Jetson-ready).
- ROI size + timeout bounds.
- Concealment scoring (raw CNN output + post-process).

### Excluded
- Primitive graph + freshness scoring (task 30).
- Recommended-action policy (task 32).
- The CNN model weights themselves (treated as a build/deploy artefact; ONNX model file path is config).

## Acceptance Criteria

**AC-1: Inference happy path**
Given a 256×256 RGB ROI and a fixture CNN model
When `infer(roi)` runs
Then it returns `Ok(ConcealmentScore { value: f32, model_version })` within ≤200 ms p99.

**AC-2: Oversize ROI rejected pre-decode**
Given an ROI larger than `max_roi_bytes`
When `infer(roi)` is called
Then it returns `Err(RoiError::Oversize)` immediately; no decode happens.

**AC-3: Inference timeout returns explicit error**
Given a fixture CNN that takes 500 ms
When `infer(roi)` is called with `inference_timeout_ms = 200`
Then it returns `Err(RoiError::Timeout)` and `timeouts_total` increments by 1.

**AC-4: TensorRT feature absent does not break build**
Given the binary is built WITHOUT the `tensorrt` feature
When the build runs
Then it builds cleanly using ONNX Runtime only.

## Non-Functional Requirements

**Performance**
- Per-ROI inference: ≤200 ms p99 (per `description.md §8`).
- Concealed-position recall ≥60 %, precision ≥20 % (per `description.md §8`; both measured against the benchmark dataset, not asserted here).

## Runtime Completeness

- **Named capability**: ROI CNN inference on ONNX (TensorRT optional).
- **Production code that must exist**: real ONNX session; real ROI bounds; real timeout.
- **Allowed external stubs**: a tiny fixture ONNX model for unit tests.
- **Unacceptable substitutes**: running on the full frame instead of an ROI is unacceptable (memory + latency).
