From ec8495cc54b96c82ed04bacab55bc7accb8178e3 Mon Sep 17 00:00:00 2001 From: Mark Allyn Date: Sat, 18 Apr 2026 22:43:10 -0700 Subject: [PATCH] close to finishing claude.md file --- CLAUDE.md | 254 +++++++++++++++++++++++++++++++++++++++++++++++-- DESIGN.md | 245 +++++++++++++++++++++++++++++++++++++++++++++++ src/settings.h | 181 +++++++++++++++++++++++++++++++++++ 3 files changed, 674 insertions(+), 6 deletions(-) create mode 100644 DESIGN.md create mode 100644 src/settings.h diff --git a/CLAUDE.md b/CLAUDE.md index 0d8a70b..98a7cc1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -105,9 +105,10 @@ Also not that when the radar exhibit starts, the very first option will be on the screen. Then the screen will advance through the scopes by two means; the pressing of the s key by the user, or automatically at every 120 seconds. You will need to emphasize in the first desciption that you can advance without waiting -for the automatic advancing by pressing the s key. This should be articulated for the +for the automatic advancing by pressing the s key. You can reverse by hitting the S key +(upper case s) This should be articulated for the discrition window for each scope. When the main exhibit descriptor screen comes up, it's -important to highlight the feature that the user can press the s key any time to 'hurry up' +important to highlight the feature that the user can press the s key or the S key any time to 'hurry up' the scope advancement. Also ensure that the timeout clock will reset when the user changes to a new scope, or presses @@ -143,6 +144,20 @@ should be articulated in the descriptive text In addition to the blips for targets, there would be a floor of noise (signal received by rain and waves. This needs to be shown. + Graticule swap simulation: In the period, changing maximum 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 must be simulated when the operator presses u or d to change range. + + The graticule swap animation uses four states: + NORMAL - graticule in place, scope operating normally + SLIDING_OUT - old graticule translates upward off screen (~0.5 seconds) + BARE_CRT - no graticule visible; CRT trace and noise floor still running + SLIDING_IN - new graticule (correct lines for new range) slides down into position (~0.5 seconds) + After SLIDING_IN completes, state returns to NORMAL with the new range active. + The u and d keys are ignored during the swap animation (operator's hands are busy). + The graticule remains incandescent color throughout — it is edge-lit glass. + 3. Chain Home A Scope There is a second use of the a-scope. That is for the early world war 2 chain home radar. This operated very differently. You have a large array of high power transmitters @@ -158,7 +173,7 @@ should be articulated in the descriptive text as this is a bit advanced. I need your advise to how to do this for children and those who never heard of chain home. - The goniometer vert and h switch could be keys [ and ] and the gonometer tuning + The goniometer vertical and horizontal switch could be key [ for toggling. The gonometer tuning would be 9 and 0 to avoid using the shift key. The tuning keys should have one unit for single press, but a slow build of of speed if key is held down. This has to stay slow due to the sudden appearance of the null. @@ -325,8 +340,96 @@ should be articulated in the descriptive text Sweep rate: approximately 30 Hz alternating between azimuth and elevation planes so that each will scan 1/15 th of a second. +THREADS -[remove modularity and threads; will work this out later before implementation] +These are the threads of processes: + +1. Display initiation and operation (anything that 'touches' the display and the shaders) Thread 1 +2. Software that receives data for targets. Thread 2 (this is the traffic cop that polls the + raspberry pis. and the Simulator. This needs mutex access to shared data with thread 1. It will + also need mutex access to shared data with thread 4 (the simulator) +3. Knob panel - thread 3 - uses a mutex to write shared state variables that thread 1 reads + to send to the shaders. +4. Simulator, thread 4. It is polled by the traffic cop + +Threads 2,3, need mutex access to shared data that is read by thread 1. +Thread 2 needs mutex access for shared data with thread 4, the simulator + +SUMMARY OF Controls: +● ┌─────┬─────────────────────────────────────┬───────┬──────────┬──────────────┬────────────┬─────────┬─────┐ + │ Key │ Function │ Intro │ Marine A │ Chain Home A │ Marine PPI │ ATC PPI │ PAR │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ s │ Advance to next scope │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ S │ back to previous scope │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ c │ Bearing clockwise │ │ ✓ │ │ │ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ v │ Bearing counterclockwise │ │ ✓ │ │ │ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ u │ Max range up │ │ ✓ │ │ ✓ │ ✓ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ d │ Max range down │ │ ✓ │ │ ✓ │ ✓ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ r │ Cursor bearing right │ │ │ │ ✓ │ ✓ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ l │ Cursor bearing left │ │ │ │ ✓ │ ✓ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ t │ Cursor range increase │ │ │ │ ✓ │ ✓ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ y │ Cursor range decrease │ │ │ │ ✓ │ ✓ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ k │ Antenna bearing offset right (boat) │ │ │ │ ✓ │ ✓ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ j │ Antenna bearing offset left (boat) │ │ │ │ ✓ │ ✓ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ [ │ Goniometer H/V switch │ │ │ ✓ │ │ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ 9 │ Goniometer tune left │ │ │ ✓ │ │ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ 0 │ Goniometer tune right │ │ │ ✓ │ │ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ . │ Toggle PRF (25/12.5 Hz) │ │ │ ✓ │ │ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ n │ Calibrator shrink │ │ │ ✓ │ │ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ + │ m │ Calibrator stretch │ │ │ ✓ │ │ │ │ + └─────┴─────────────────────────────────────┴───────┴──────────┴──────────────┴────────────┴─────────┴─────┘ + +SUMMARY of target handling: + +The traffic cop handles anything that is coming from the simulator as well as the raspberry pi's +It will use pollnig to find if anything is available from the raspberry pis and the simulator. It will +poll each source once per beam update + +The raspberry pi receiver pulls the data from each raspberry pi. If nothing, it does nothing else +for this sweep. If there is data, it will provide data to the traffic cop upon poll. + +Each raspberry pi, after boot-up, will respond to poles from the raspberry pi receiver (thread 2) + +The Simulator will run all fake targets. It will provide data to the traffic cop upon traffic cop poll. +It can run as a separate thread. It will not write data to anywhere except when polled by the traffic cop. + + +CONTROLS + +Things to note about the keyboard type controls. +The letter on the keyboard are temporary. When I get around to making +the operators panel, this all will go away. + +Implementation of controls: + +1. For keyboard controls. Those are run as thread one where The keyboard callback + belongs to GLFW (glfwSetKeyCallback) + They will manipulate the shaders only. + +2. The control desk controls will have to mutex to access the state variables that + thread 1 sends to the shaders. + +3. If the control does not yet exist, we still want stubs for receiving control + data for that control. It's just that nothing will call it. + +Scope and left window arrangement. For each scope, put the scope itself on the right hand of the window. On the left hand of the window will be a text description of that scope. @@ -351,6 +454,145 @@ different radars. Range and bearing for the precision aproach radar will be diff than any other radar as that radar is located at the end of the runway and scan both horizontal and vertical. -[other scopes to be defined later] - Please analyze and comment. Please do not generate any code file nor shader files. + +================================================================== + +CLASS DESIGN AND FILE LAYOUT + +================================================================== + +Class Hierarchy: + + Scope (abstract base) + ├── ExhibitIntro + ├── AScope (abstract) + │ ├── MarineAScope + │ └── ChainHomeAScope + ├── PPIScope (abstract) + │ ├── MarinePPIScope + │ └── ATCPPIScope + └── PARScope + +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: render(), handleKey(), getDescription() + +ExhibitIntro : public Scope + - Text-only rendering, no radar display + - Header: "WELCOME TO MUSEUM VINTAGE RADAR EXHIBIT" (all caps) + +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 + - Graticule swap animation state machine (NORMAL/SLIDING_OUT/BARE_CRT/SLIDING_IN) + when operator changes max range — see Marine A-Scope section above for full detail + - Keys: c (bearing CW), v (bearing CCW), u (range up), d (range down) + u and d ignored during graticule swap animation + +ChainHomeAScope : public AScope + - P7 phosphor (early implementation) + - 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 + - Keys: [ (goniometer H/V toggle), 9/0 (tune), . (PRF), n/m (calibrator) + +PPIScope : public Scope (abstract) — shared PPI behavior: + - Clockwise sweep with P7 phosphor persistence (blue strike, green/yellow decay) + - Incandescent bearing graticule (1-degree ticks, 15-degree labels, inner/outer rings) + - Yellow cursor: 10-degree arc + bearing crossline + - Cursor range/bearing readout under scope (white text) + - Bearing offset for boat mode (k/j) + - Cursor range clamped to max range + +MarinePPIScope : public PPIScope + - Sweep time: 4 seconds + - Max range: 2, 4, 6 miles with correct ring sets + - Keys: u (range up), d (range down) — this scope only + +ATCPPIScope : public PPIScope + - Sweep time: 5 seconds + - Max range: 5, 10, 15, 20 miles with correct ring sets + - Keys: u (range up), d (range down) — this scope only + +PARScope : public Scope + - Two stacked sub-scopes: azimuth (top, ~1/3 larger) and elevation (bottom) + - 30 Hz alternating scan between planes (~15 Hz each) + - Fixed 10-mile range, non-linear scale (inner 5 miles = 70% width) + - P7 phosphor; incandescent etched glass graticules + - All targets simulated; no cursor or bearing controls + +Supporting classes: + ScopeManager Thread 1 — owns scope list, s/S switching, 120s auto-advance timer + PhosphorRenderer Thread 1 — P1 and P7 decay/persistence; shared dependency + Graticule Thread 1 — incandescent graticule lines/text; parameterized per scope + LeftPanel Thread 1 — scope description text panel (left side of window) + SharedRenderState Threads 1,2,3 — Mutex A; state vars Thread 1 reads each frame for shader uniforms + TargetBuffer Threads 2,4 — Mutex B; target data handoff between TrafficCop and Simulator + TrafficCop Thread 2 — polls Simulator and RPi receivers; writes to SharedRenderState + Simulator Thread 4 — runs fake targets; returns data to TrafficCop when polled + KnobPanel Thread 3 — future hardware stub; writes to SharedRenderState under Mutex A + RPiReceiver Thread 2 — stub; one per Raspberry Pi; called by TrafficCop + +File layout: + + src/ + main.cpp + scope_manager.h / scope_manager.cpp + scope.h / scope.cpp — abstract Scope base + scope_intro.h / scope_intro.cpp + 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 + +settings.h — tunable constants: + All magic numbers live here. Every source file that needs a tunable value + includes settings.h. No values are hardcoded elsewhere. + Categories planned: + - Phosphor P1 color + - Phosphor P7 strike color, persistence color, decay times (PPI and Chain Home) + - Sweep line width, brightness, fade trail, periods per scope + - PAR scan rate; Chain Home PRF high and low + - Graticule incandescent color, line widths + - PPI bearing ring tick lengths, label interval, font size + - PPI range ring line width, label size, label color + - Cursor color, line width, arc span + - Noise floor amplitude and variation (Marine A-Scope) + - Graticule swap animation durations (slide out, bare CRT, slide in) + - Key-hold acceleration (initial step, rate, max) — separate for goniometer + - Auto-advance timer interval (120 seconds) + - Window size and panel layout fractions + - PAR azimuth/elevation height fractions + - UI text color and size; cursor readout text size + - Graticule label color (incandescent) diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..f7ed757 --- /dev/null +++ b/DESIGN.md @@ -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). diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..39b2d47 --- /dev/null +++ b/src/settings.h @@ -0,0 +1,181 @@ +/* + * MIT License + * Author: Mark Allyn + * + * settings.h + * + * Central location for all tunable constants and appearance variables. + * Edit values here to adjust rendering, timing, and behavior without + * touching any other source file. + * + * NOTE: This file is a planning document. C++ declarations will be + * added once all variables have been identified and agreed upon. + */ + +/* + * ================================================================ + * PHOSPHOR — P1 (Marine A-Scope) + * ================================================================ + * + * P1_COLOR_R Red component of P1 phosphor green + * P1_COLOR_G Green component of P1 phosphor green + * P1_COLOR_B Blue component of P1 phosphor green + */ + +/* + * ================================================================ + * PHOSPHOR — P7 (Marine PPI, ATC PPI, PAR, Chain Home A-Scope) + * ================================================================ + * + * P7_STRIKE_R Red component of initial beam strike (blue) + * P7_STRIKE_G Green component of initial beam strike (blue) + * P7_STRIKE_B Blue component of initial beam strike (blue) + * + * P7_PERSIST_R Red component of persistence color (yellow-green) + * P7_PERSIST_G Green component of persistence color (yellow-green) + * P7_PERSIST_B Blue component of persistence color (yellow-green) + * + * P7_DECAY_TIME Decay time constant in seconds (PPI scopes and PAR) + * P7_DECAY_TIME_CH Decay time constant for Chain Home — longer, must + * survive between slow PRF pulses (25 Hz / 12.5 Hz) + */ + +/* + * ================================================================ + * SWEEP + * ================================================================ + * + * SWEEP_LINE_WIDTH Width of the rotating sweep line in pixels + * SWEEP_LINE_BRIGHTNESS Brightness multiplier for the sweep line (0.0–1.0) + * SWEEP_FADE_TRAIL_DEGREES How many degrees behind the sweep line the bright + * afterglow trails before transitioning to P7 decay + * + * MARINE_PPI_SWEEP_PERIOD Full rotation period in seconds (4.0) + * ATC_PPI_SWEEP_PERIOD Full rotation period in seconds (5.0) + * + * PAR_SCAN_RATE_HZ PAR alternating scan rate in Hz (30) + * + * CHAIN_HOME_PRF_HIGH_HZ Chain Home high PRF in Hz (25) + * CHAIN_HOME_PRF_LOW_HZ Chain Home low PRF in Hz (12.5) + */ + +/* + * ================================================================ + * GRATICULE — General + * ================================================================ + * + * GRATICULE_COLOR_R Red component of incandescent graticule color + * GRATICULE_COLOR_G Green component of incandescent graticule color + * GRATICULE_COLOR_B Blue component of incandescent graticule color + * GRATICULE_LINE_WIDTH Width of graticule lines in pixels + */ + +/* + * ================================================================ + * GRATICULE — PPI Bearing Ring + * ================================================================ + * + * PPI_TICK_MAJOR_LENGTH Length of major tick marks (every 10 degrees) + * PPI_TICK_MINOR_LENGTH Length of minor tick marks (every 1 degree) + * PPI_TICK_LINE_WIDTH Width of tick mark lines + * PPI_LABEL_INTERVAL_DEG Degrees between text labels (15) + * PPI_LABEL_FONT_SIZE Font size for bearing labels + */ + +/* + * ================================================================ + * GRATICULE — PPI Range Rings + * ================================================================ + * + * RANGE_RING_LINE_WIDTH Width of range ring lines in pixels + * RANGE_RING_LABEL_SIZE Font size for range ring distance labels + * RANGE_RING_LABEL_R Red component (P7 color — blue fading to yellow-green) + * RANGE_RING_LABEL_G Green component + * RANGE_RING_LABEL_B Blue component + */ + +/* + * ================================================================ + * CURSOR (PPI scopes) + * ================================================================ + * + * CURSOR_COLOR_R Red component of cursor color (yellow) + * CURSOR_COLOR_G Green component of cursor color (yellow) + * CURSOR_COLOR_B Blue component of cursor color (yellow) + * CURSOR_LINE_WIDTH Width of cursor arc and bearing line in pixels + * CURSOR_ARC_SPAN_DEG Angular span of the cursor arc in degrees (10) + */ + +/* + * ================================================================ + * NOISE FLOOR (Marine A-Scope) + * ================================================================ + * + * NOISE_FLOOR_AMPLITUDE Baseline amplitude of the noise floor (0.0–1.0) + * NOISE_FLOOR_VARIATION Random variation range added to baseline + */ + +/* + * ================================================================ + * GRATICULE SWAP ANIMATION (Marine A-Scope) + * ================================================================ + * + * GRATICULE_SLIDE_OUT_TIME Duration of old graticule sliding out in seconds + * GRATICULE_BARE_CRT_TIME Duration of bare CRT pause between slides in seconds + * GRATICULE_SLIDE_IN_TIME Duration of new graticule sliding in in seconds + */ + +/* + * ================================================================ + * KEY-HOLD ACCELERATION + * ================================================================ + * + * KEY_INITIAL_STEP Step size for a single key press (degrees or miles) + * KEY_ACCEL_RATE Rate at which step size grows while key is held + * KEY_MAX_STEP Maximum step size regardless of hold duration + * + * KEY_GONIO_INITIAL_STEP Initial step for goniometer tuning (must stay slow + * to allow precise null finding) + * KEY_GONIO_ACCEL_RATE Acceleration rate for goniometer (slow build) + * KEY_GONIO_MAX_STEP Maximum goniometer step size + */ + +/* + * ================================================================ + * AUTO-ADVANCE TIMER + * ================================================================ + * + * SCOPE_AUTO_ADVANCE_SEC Seconds before auto-advancing to next scope (120) + */ + +/* + * ================================================================ + * WINDOW AND LAYOUT GEOMETRY + * ================================================================ + * + * WINDOW_WIDTH Initial window width in pixels + * WINDOW_HEIGHT Initial window height in pixels + * LEFT_PANEL_WIDTH_FRAC Left description panel as fraction of window width + * RIGHT_PANEL_WIDTH_FRAC Right scope panel as fraction of window width + * + * PAR_AZIMUTH_HEIGHT_FRAC Azimuth scope height as fraction of right panel height + * PAR_ELEVATION_HEIGHT_FRAC Elevation scope height as fraction of right panel height + */ + +/* + * ================================================================ + * TEXT + * ================================================================ + * + * UI_TEXT_COLOR_R Red component of general UI text (white) + * UI_TEXT_COLOR_G Green component of general UI text (white) + * UI_TEXT_COLOR_B Blue component of general UI text (white) + * UI_TEXT_SIZE Font size for general UI / description text + * + * CURSOR_READOUT_TEXT_SIZE Font size for cursor range/bearing readout + * displayed under PPI scope + * + * GRATICULE_LABEL_COLOR_R Red component of graticule text (incandescent) + * GRATICULE_LABEL_COLOR_G Green component + * GRATICULE_LABEL_COLOR_B Blue component + */