# Contest Anti-Cheat & Screen Telemetry Analyzer (`screen-checker`)

Rust pipeline for analyzing contestant screen recordings/screenshots using a staged Vision-Language Model (`Qwen3-VL-8B` via `llama-server`) + deterministic Tesseract OCR, bidirectional viewport-to-document text reconstruction, and algorithmic anomaly detectors.

---

## Quick Start

### 1. Build (Release Mode)
```bash
cargo build --release
```

### 2. Run on a Contest Dataset
```bash
./target/release/screen-checker \
  --screenshots volterra1-ioi2027 \
  --output report_volterra1.html
```

### 3. View the Report Live While It Generates
Because the HTML report references screenshot frames via relative paths from the report's parent directory, serve the repository root over HTTP so you can inspect results in your browser as contestants are processed:

```bash
python3 -m http.server 8888 --bind 0.0.0.0
```
Then open:
- `http://localhost:8888/report_volterra1.html`

---

## Startup & Operational Notes

### 1. Supported Dataset Directory Layouts
[`discover_contestants`](crates/cli/src/main.rs) and [`CmsClient`](crates/contest-db/src/cms.rs) automatically handle both flat and nested contest export structures:
- **Nested Contest Bundle (e.g. `volterra1-ioi2027/`):**
  - `volterra1-ioi2027/oii-check-data/<contestant_id>/` — Contestant screenshot directories and `logs.json`.
  - `volterra1-ioi2027/cms-export/` — CMS export bundle containing `contest.json` and `files/<digest>`.
- **Flat Screenshot Directory (e.g. `oii26/` or `screenshot-ois/`):**
  - `<root>/<contestant_id>/` directly under the `--screenshots` directory.

Passing either `--screenshots volterra1-ioi2027` or `--screenshots volterra1-ioi2027/oii-check-data` will automatically:
1. Unwrap `oii-check-data/` to discover all individual contestant directories.
2. Detect the sibling/child `cms-export/contest.json` and load all task template attachments (`.cpp`, `.c`, `.h`, `.hpp`, `.rs`, `.py`, such as `grader.cpp` and starter task templates) via [`CmsClient::load_export_attachments`](crates/contest-db/src/cms.rs).

> **Why CMS Template Loading Matters:** Without loading the starter templates and graders from `cms-export`, a contestant opening or pasting the official `grader.cpp` or task skeleton into their IDE has `0%` template token coverage and will trigger false-positive `PasteBomb` incidents.

### 2. Incremental Report Updates & First-Contestant Startup Delay
- **Per-Contestant Caching & Report Flushing:** The pipeline processes contestants alphabetically and writes two outputs **at the end of each contestant**:
  1. `<contestant_dir>/frame_analyses_cache.json` — Cached VLM bounding boxes and OCR output for all frames of that contestant.
  2. `--output <report.html>` — Incremental standalone HTML report containing all episodes detected across contestants completed so far.
- **Why the Initial Report Takes a Few Minutes to Appear:** On a cold run (no `frame_analyses_cache.json` yet), the report file is written after the **first contestant** finishes VLM + OCR decoding (~5–10 minutes for a typical 500–800 frame contestant using 4 parallel CUDA slots on `llama-server`).
- **Tip for Quick Smoke Checks:** To verify setup or preview the UI in seconds before or during a full run, filter to a single contestant with `--contestants <username>` or `--limit 1`.

### 3. Static Frame Deduplication
During the vision pass:
- [`SimdFrameDiffer`](crates/vision-pipeline/src/differ.rs) computes pixel-level frame-to-frame change percentages across decoded RGB buffers.
- Static frames (`< 0.2%` pixel change from the preceding frame) are skipped during `llama-server` VLM inference and automatically inherit the preceding dynamic frame's detection and OCR state in [`StagedVisionPipeline::stage2_decode_llm_with_server`](crates/vision-pipeline/src/staged_engine.rs).

---

## Useful CLI Options

| Flag | Description |
| :--- | :--- |
| `-s, --screenshots <PATH>` | Path to contest root (`volterra1-ioi2027`), `oii-check-data`, or a single contestant directory |
| `-o, --output <PATH>` | Output HTML report path (updated incrementally after each contestant) |
| `--contestants <LIST>` | Comma-separated filter of contestant IDs (e.g. `--contestants acierno,alsous`) |
| `--limit <N>` | Process at most the first `N` discovered contestants |
| `--refresh-ocr` | Re-run deterministic Tesseract OCR on cached VLM bounding boxes without re-running VLM inference |
| `--force` | Ignore existing `frame_analyses_cache.json` files and re-run full VLM inference |
| `--cms-db <URL>` | Optional live CMS PostgreSQL connection string (`postgres://...`) |
| `--terry-db <PATH>` | Optional Terry SQLite database path |
