close to finishing claude.md file
This commit is contained in:
245
DESIGN.md
Normal file
245
DESIGN.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# Radar Simulation — Class Design and File Layout
|
||||
Author: Mark Allyn
|
||||
|
||||
---
|
||||
|
||||
## Class Hierarchy
|
||||
|
||||
```
|
||||
Scope (abstract base)
|
||||
├── ExhibitIntro
|
||||
├── AScope (abstract)
|
||||
│ ├── MarineAScope
|
||||
│ └── ChainHomeAScope
|
||||
├── PPIScope (abstract)
|
||||
│ ├── MarinePPIScope
|
||||
│ └── ATCPPIScope
|
||||
└── PARScope
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Class Descriptions
|
||||
|
||||
### Scope (abstract base)
|
||||
Everything all scopes share:
|
||||
- Left panel text rendering
|
||||
- `s` / `S` key handling (scope advance / reverse)
|
||||
- Auto-advance timer reset on any key or control input
|
||||
- Pure virtual methods: `render()`, `handleKey()`, `getDescription()`
|
||||
|
||||
### ExhibitIntro : public Scope
|
||||
- Text-only rendering, no radar display
|
||||
- Header: "WELCOME TO MUSEUM VINTAGE RADAR EXHIBIT" (all caps)
|
||||
- Emphasizes s/S keys and 120-second auto-advance
|
||||
|
||||
### AScope : public Scope (abstract)
|
||||
Shared A-scope behavior:
|
||||
- Horizontal range axis, vertical amplitude axis
|
||||
- Noise floor rendering (rain/wave clutter)
|
||||
- Incandescent graticule (three horizontal amplitude lines + vertical range lines)
|
||||
- Bearing control with key-hold acceleration
|
||||
- Phosphor type as parameter (P1 or P7)
|
||||
|
||||
### MarineAScope : public AScope
|
||||
- P1 phosphor (green)
|
||||
- Range settings: 2, 4, 6 miles
|
||||
- Max 2: one interim range at 1
|
||||
- Max 4: one interim range at 2
|
||||
- Max 6: one interim range at 4
|
||||
- Keys: c (bearing CW), v (bearing CCW), u (range up), d (range down)
|
||||
- u and d are ignored during graticule swap animation
|
||||
|
||||
**Graticule swap animation:**
|
||||
In the period, changing max range required the operator to physically slide the glass
|
||||
graticule panel upward and out from in front of the CRT, then slide the replacement
|
||||
graticule (calibrated for the new range) downward into position. This is simulated.
|
||||
|
||||
State machine triggered by u or d key:
|
||||
|
||||
NORMAL graticule in place, scope operating normally
|
||||
|
|
||||
| u or d pressed
|
||||
v
|
||||
SLIDING_OUT old graticule translates upward off screen (~0.5 seconds)
|
||||
|
|
||||
v
|
||||
BARE_CRT no graticule rendered; CRT trace and noise floor still running
|
||||
|
|
||||
v
|
||||
SLIDING_IN new graticule slides down into position (~0.5 seconds)
|
||||
|
|
||||
v
|
||||
NORMAL new range now active
|
||||
|
||||
- u and d keys ignored while state != NORMAL
|
||||
- Graticule remains incandescent color throughout (edge-lit glass, not CRT-dependent)
|
||||
|
||||
### ChainHomeAScope : public AScope
|
||||
- P7 phosphor (early implementation, slow decay for slow PRF)
|
||||
- Goniometer state: H/V mode toggle, azimuth angle, elevation angle
|
||||
- PRF toggle: 25 Hz / 12.5 Hz
|
||||
- Calibrator stretch/shrink scale factor
|
||||
- Fixed 100-mile range (no range change)
|
||||
- Keys: [ (goniometer H/V toggle), 9 (tune left), 0 (tune right),
|
||||
. (PRF toggle), n (calibrator shrink), m (calibrator stretch)
|
||||
|
||||
### PPIScope : public Scope (abstract)
|
||||
Shared PPI behavior:
|
||||
- Clockwise sweep with P7 phosphor persistence
|
||||
- Immediate beam strike: blue
|
||||
- Persistence: green/yellow, faded by next sweep pass
|
||||
- Incandescent bearing graticule:
|
||||
- Inner ring with 1-degree tick marks (0–359, True North = 0)
|
||||
- Text labels every 15 degrees
|
||||
- Outer ring
|
||||
- Yellow cursor: 10-degree arc section + bearing crossline
|
||||
- Cursor range/bearing readout displayed under scope (white text)
|
||||
- Bearing offset for boat mode (k = right, j = left)
|
||||
- Keys: r (cursor bearing right), l (cursor bearing left),
|
||||
t (cursor range increase), y (cursor range decrease),
|
||||
k (antenna bearing offset right), j (antenna bearing offset left)
|
||||
- Cursor range clamped to max range if exceeded
|
||||
|
||||
### MarinePPIScope : public PPIScope
|
||||
- Sweep time: 4 seconds
|
||||
- Max range settings: 2, 4, 6 miles
|
||||
- Max 2: rings at 1, 2
|
||||
- Max 4: rings at 2, 4
|
||||
- Max 6: rings at 4, 6
|
||||
- Keys: u (range up), d (range down) — affects only this scope
|
||||
|
||||
### ATCPPIScope : public PPIScope
|
||||
- Sweep time: 5 seconds
|
||||
- Max range settings: 5, 10, 15, 20 miles
|
||||
- Max 5: rings at 2.5, 5
|
||||
- Max 10: rings at 2, 4, 6, 8, 10
|
||||
- Max 15: rings at 4, 8, 12, 15
|
||||
- Max 20: rings at 5, 10, 15, 20
|
||||
- Keys: u (range up), d (range down) — affects only this scope
|
||||
|
||||
### PARScope : public Scope
|
||||
- Two vertically stacked sub-scopes (right panel):
|
||||
- Top: azimuth (lateral deviation vs. range) — ~1/3 larger
|
||||
- Bottom: elevation (vertical deviation vs. range)
|
||||
- P7 phosphor; graticules are incandescent etched glass
|
||||
- 30 Hz alternating scan between azimuth and elevation planes
|
||||
(each plane scans at ~15 Hz, i.e., 1/15 second per plane)
|
||||
- Fixed 10-mile range — no range change control
|
||||
- Non-linear horizontal scale: inner 5 miles occupies 70% of width
|
||||
- All targets simulated; no cursor or bearing controls
|
||||
- Located at south end of Runway 16/34, BLI — active runway 34 (northbound)
|
||||
|
||||
---
|
||||
|
||||
## Supporting Classes
|
||||
|
||||
| Class | Thread | Purpose |
|
||||
|---|---|---|
|
||||
| `ScopeManager` | 1 | Owns scope list; handles s/S switching and 120s auto-advance timer |
|
||||
| `PhosphorRenderer` | 1 | P1 and P7 decay/persistence simulation; shared dependency |
|
||||
| `Graticule` | 1 | Draws incandescent graticule lines and text; parameterized per scope |
|
||||
| `LeftPanel` | 1 | Renders scope description text panel (left side of window) |
|
||||
| `SharedRenderState` | 1,2,3 | Mutex A — state variables Thread 1 reads each frame to push as shader uniforms |
|
||||
| `TargetBuffer` | 2,4 | Mutex B — target data handoff between Thread 2 (traffic cop) and Thread 4 (simulator) |
|
||||
| `TrafficCop` | 2 | Polls Simulator and RPi receivers each beam update; writes targets to SharedRenderState |
|
||||
| `Simulator` | 4 | Runs fake targets independently; returns data to TrafficCop when polled |
|
||||
| `KnobPanel` | 3 | Future hardware stub — writes to SharedRenderState under Mutex A |
|
||||
| `RPiReceiver` | 2 | Stub — one instance per Raspberry Pi; called by TrafficCop |
|
||||
|
||||
---
|
||||
|
||||
## Thread Summary
|
||||
|
||||
| Thread | Class(es) | Mutex Access |
|
||||
|---|---|---|
|
||||
| Thread 1 | ScopeManager, all Scope subclasses, PhosphorRenderer, Graticule, LeftPanel | Reads SharedRenderState under Mutex A |
|
||||
| Thread 2 | TrafficCop, RPiReceiver | Writes SharedRenderState under Mutex A; reads TargetBuffer under Mutex B |
|
||||
| Thread 3 | KnobPanel | Writes SharedRenderState under Mutex A |
|
||||
| Thread 4 | Simulator | Writes TargetBuffer under Mutex B |
|
||||
|
||||
Keyboard input arrives via GLFW callback (glfwSetKeyCallback) in Thread 1.
|
||||
Thread 1 dispatches s/S to ScopeManager and all other keys to the active Scope.
|
||||
|
||||
---
|
||||
|
||||
## Proposed File Layout
|
||||
|
||||
```
|
||||
src/
|
||||
main.cpp — GLFW init, thread launch, main loop
|
||||
scope_manager.h / scope_manager.cpp
|
||||
scope.h / scope.cpp — abstract Scope base class
|
||||
scope_intro.h / scope_intro.cpp — ExhibitIntro
|
||||
scope_ascope.h / scope_ascope.cpp — abstract AScope
|
||||
scope_marine_a.h / scope_marine_a.cpp
|
||||
scope_chain_home.h / scope_chain_home.cpp
|
||||
scope_ppi.h / scope_ppi.cpp — abstract PPIScope
|
||||
scope_marine_ppi.h / scope_marine_ppi.cpp
|
||||
scope_atc_ppi.h / scope_atc_ppi.cpp
|
||||
scope_par.h / scope_par.cpp
|
||||
phosphor.h / phosphor.cpp
|
||||
graticule.h / graticule.cpp
|
||||
left_panel.h / left_panel.cpp
|
||||
shared_render_state.h / shared_render_state.cpp
|
||||
target_buffer.h / target_buffer.cpp
|
||||
traffic_cop.h / traffic_cop.cpp
|
||||
simulator.h / simulator.cpp
|
||||
knob_panel.h / knob_panel.cpp
|
||||
rpi_receiver.h / rpi_receiver.cpp
|
||||
settings.h — all tunable constants (no .cpp needed)
|
||||
|
||||
shaders/
|
||||
phosphor.vert / phosphor.frag — parameterized for P1 and P7 via uniforms
|
||||
graticule.vert / graticule.frag
|
||||
text.vert / text.frag
|
||||
sweep.vert / sweep.frag
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Design Notes
|
||||
|
||||
1. **ScopeManager** sits in Thread 1 and holds the active scope pointer. The GLFW
|
||||
key callback calls ScopeManager::handleKey(), which dispatches s/S to itself
|
||||
and all other keys to the active scope. The auto-advance timer resets on any
|
||||
key event or control input.
|
||||
|
||||
2. **SharedRenderState** holds two categories: control state (bearing, range,
|
||||
cursor — written by Thread 1 keyboard callbacks and Thread 3 knob panel) and
|
||||
target state (written by Thread 2). Thread 1 reads the whole struct once per
|
||||
frame under Mutex A to push uniforms to the shaders.
|
||||
|
||||
3. **TargetBuffer** is separate from SharedRenderState — it is the handoff point
|
||||
between Thread 2 (traffic cop) and Thread 4 (simulator) under Mutex B.
|
||||
|
||||
4. **PhosphorRenderer** is a shared utility. AScope and PPIScope subclasses
|
||||
receive it as a constructor dependency rather than each reimplementing decay
|
||||
logic. Pass phosphor type (P1/P7) and decay constant as parameters.
|
||||
|
||||
5. **Shaders** are parameterized via uniforms rather than duplicated. A single
|
||||
phosphor shader pair handles both P1 (green, no persistence) and P7 (blue
|
||||
strike, yellow-green decay) by passing color and decay time as uniforms.
|
||||
|
||||
6. **Shoreline and terrain geometry** is loaded once at Thread 1 startup as a
|
||||
static VBO. It is read-only after load — no mutex needed.
|
||||
|
||||
7. **Each scope's max range and cursor state are independent.** Changing range
|
||||
on the Marine PPI does not affect the ATC PPI, and vice versa. State is
|
||||
owned by each scope instance.
|
||||
|
||||
8. **PAR sub-scopes** (azimuth and elevation) can be implemented as private
|
||||
member objects within PARScope rather than as separate Scope subclasses,
|
||||
since they are never displayed independently.
|
||||
|
||||
9. **settings.h** is a header-only file (no .cpp). It will contain only
|
||||
`constexpr` constants organized into named sections. Every other source
|
||||
file that needs a tunable value includes this file. No magic numbers
|
||||
anywhere else in the codebase. Add candidate variables here before
|
||||
coding begins; values can be refined during debugging and appearance work.
|
||||
|
||||
10. **MarineAScope graticule swap** is a state machine with four states:
|
||||
NORMAL → SLIDING_OUT → BARE_CRT → SLIDING_IN → NORMAL. The u and d keys
|
||||
are blocked during the animation. The new range value is latched when the
|
||||
key is pressed but not applied to the scope state until SLIDING_IN completes.
|
||||
Animation duration is approximately 0.5 seconds per slide (out and in).
|
||||
Reference in New Issue
Block a user