# Radar Exhibit — Design Document

## Hardware target
- Geekom A8 Max: AMD Ryzen 9 8945HS, Radeon 780M (Mesa/RADV), 32 GB RAM
- Ubuntu 25.10, g++ 15.2.0, C++20, OpenGL 4.3 Core

---

## Screen layout

Three fixed regions rendered with `glViewport` + `glScissor`:

| Region | Content |
|--------|---------|
| Left panel | Text description, control list (white text; red labels; pink keystrokes) |
| Right panel — upper | Radar scope |
| Right panel — lower | Status bar: range, bearing, scope ID (yellow text) |

---

## Scope sequence (cycled with keys 1 / 2)

1. Exhibit introduction (text only)
2. Chain Home A-Scope (1940s)
3. Marine A-Scope (1940s)
4. PPI — stationary marine traffic control
5. PPI — on-board boat

Each scope's control state is **independent** and **resets on re-entry**.

---

## Radar equation

$$P_r = \frac{P_t G^2 \lambda^2 \sigma}{(4\pi)^3 R^4}$$

Signal strength falls off with $1/R^4$. Each radar type has fixed hardware loop gain baked into its shader set as a constant.

---

## Controls (keyboard until physical panel is built)

| Key | Action |
|-----|--------|
| 1 / 2 | Next / previous scope |
| 3 / 4 | Intensity down / up |
| 5 / 6 | Receiver sensitivity down / up |
| q / w | STC sensitivity down / up |
| e / r | STC range down / up |
| t / y | Radiogoniometer left / right (Chain Home only) |
| u / i | Max range down / up (all except Chain Home) |
| o / p | Range cursor down / up (PPI only) |
| a / s | Bearing cursor CCW / CW (PPI only) |
| d / f | Antenna bearing CCW / CW (Marine A-Scope only) |

Notes:
- Radiogoniometer, marine antenna bearing, and PPI bearing cursor share one physical knob on the eventual panel; kept separate in software.
- Range cursor ≠ max range: max range is the radar's selected range setting; range cursor is the measurement cursor on the PPI display.

---

## Scope specifications

### Chain Home A-Scope
- Fixed range: 200 miles
- No graticule; crystal-oscillator range pips every 20 miles (operator cannot change)
- Bearing via radiogoniometer simulation: operator finds null point; bearing shown as text below scope
- Two perpendicular antenna sets (N-S, E-W); radiogoniometer angle determines which target pip goes to null

### Marine A-Scope
- Ranges: 1.5 / 3.0 / 6.0 / 12.0 miles
- Range pips: every 0.25 / 0.5 / 1.0 / 2.0 miles respectively (fixed oscillator; not affected by range setting)
- No graticule
- Bearing by rotating simulated dish/horn antenna (d/f keys)
- Pip shape: finite rise, fixed-width pulse, finite fall — curved waveform, not a vertical line

### PPI scopes (both)
- 360° sweep with phosphor persistence via FBO (semi-transparent black quad each frame; no full clear)
- Range cursor and bearing cursor overlay

#### Stationary (marine traffic control)
- Fixed observer position; targets move relative to scope center

#### On-boat
- Observer is scope center; heading-up display — world rotates as boat heading changes
- Boat position/heading supplied from GDAL/PostgreSQL data

---

## Architecture

### Class hierarchy (State pattern)

```
BaseScope          — intensity, sensitivity, STC vars, virtual render/update
├── AScope         — horizontal sweep, curved pip waveform
│   ├── ChainHomeScope   — radiogoniometer, fixed 200 mi range, 20 mi pips
│   └── MarineAScope     — antenna bearing knob, 4 range settings
└── PPIScope       — 360° sweep, FBO persistence, range/bearing cursors
    ├── StationaryPPI
    └── OnBoatPPI        — heading offset passed into shader
```

### Threading model

| Thread | Responsibility |
|--------|---------------|
| Render (main) | OpenGL loop, GLFW input, all `glDraw*` calls — never blocks |
| Simulation | Polls `target_data` (PostgreSQL) and GDAL; updates shared `std::vector<Target>` under `std::mutex` |

### FBO phosphor decay
- Render sweep into FBO each frame
- Blit FBO to screen
- Do **not** clear color buffer between frames; draw full-screen quad at very low alpha (~0.05) to decay old sweeps

### Shader notes
- GL_DEBUG_OUTPUT + `glDebugMessageCallback` enabled at startup (GPU robustness protocol)
- No NVIDIA-specific extensions; Mesa/RADV only
- $1/R^4$ attenuation computed in fragment shader; hardware loop gain is a per-scope `uniform` constant

---

## File structure

```
new-radar/
├── CMakeLists.txt
├── src/          — .cpp source files
├── include/      — .h/.hpp headers
│   ├── glad/
│   └── KHR/
├── glad/src/     — glad.c (bundled)
├── shaders/      — .vert / .frag files
├── data/         — patrol_route.json, etc.
└── map/
    ├── charts_enc/   — ENC .000 and GeoTIFF files
    └── lidar_raw/    — LIDAR zips
```

---

## Database

- PostgreSQL, database `radar`, user `radar`, password `radar`
- Table `target_data`: polled by simulation thread for live target positions
