This is a project for a museum to demonstrate a simulation of a 1940's to 1960's
vintage radar, including the Chain Home radar from early World War 2, marine radar
at a marine traffic control station, and marine radar on a boat.

The project will be implemented on a Geekom A8 Max
32 GB RAM
AMD Ryzen 9 8945HS w/ Radeon 780M Graphics
We need to render to the Radeon 780M Graphics GPU

Tech Stack: We are using C++20, OpenGL 4.3 Core, GLFW, GLAD, FreeType, GDAL (libgdal-dev)
Compiler: is g++ (Ubuntu 15.2.0-4ubuntu4) 15.2.0

FreeType is the text type we use
GDAL is used for reading the LIDAR/ENC chart files
GLFW (graphics library framework) open-source, multi-platform library used to manage windows
GLAD (Multi-Language GL/GLES/WGL/GLX Loader-Generator) Loads the pointers to 
   OpenGL functions (like glDrawArrays or glCompileShader)

PostgreSQL is installed. Database: radar. User: radar. Password: radar.
User has full privileges on database radar. Table is target_data.

Operating system details:

Distributor ID: Ubuntu
Description:    Ubuntu 25.10
Release:        25.10
Codename:       questing

Use cmake for building.

[DIRECTIVE: GPU ROBUSTNESS PROTOCOL]

Debug Callback: Enable GL_DEBUG_OUTPUT and glDebugMessageCallback 
to capture driver-level warnings and errors in real-time.

I will be using SSH from Windows to write code and check with claude.
You may compile the code during an SSH session.
Please do not try to run the code during SSH session.
I will run the code while physically using the Geekom.

Please add MIT license header to each file
Please add Author: Mark Allyn to each file

Use snake_case for variables and PascalCase for classes
use #pragma once
Use // for single line comments
use /* */ for multiple block comments spanning multiple lines
avoid using auto

Please use a settings.h file for defines and variables that I can
change and do a simple re-compile instead of having the ai re write the
code. This is for debugging. For example, I may want to disable the p7 persistance
to troubleshoot the actual target processnig before it goes to persistance,
disable the land and terrain so that I can see the targets alone. Things like
that.

Summary of project:

This is a museum exhibit displaying and providing some interaction
of vintage 1940's, 1950's, and 1960's radars. A key objective is to
provide interaction with and viewing of radars from that era.

The name of the application (the name of it's executable file) shall be
radar_simulator. Therefore, if you are in the directory in which the executable
is compiled, which in this case would be the build subdirectory of the project
directory, you would type ./radar_simulator, or use a full path name
of {PROJECT_DIR}/build/radar_simulator.

In the CMakeLists.txt, plese use the name radar_simulator for the add_executable call.

There will be three main areas of the screen. On the right hand side will be the radar
scope.

On the left hand side of the screen will be a text description of the scope as well 
as the controls of the scope and keyboard keys for each control. This text will be
white while the control labels will be red and the keystrokes will be in pink.
At some point, pending a decision with the museum, we may purchase components to mount the controls on a panel. Until that is done, the controls will be on the keyboard.

On the right hand side of the screen, below the scope, would be a text status window, showing current 
maximum range, range, and bearing as appropriate to which scope we are using as well as the identification
of the scope that we are using.

Suggest that we use glViewport to define the left-hand text area, the main scope, 
and the yellow status bar. This allows you to render the scope with its own coordinate 
system while keeping the text fixed. Along with glViewport, use glScissor and glEnable(GL_SCISSOR_TEST)

Below the scope will be a text status window. This text will be yellow

Scopes in the right panel

1. Introduction of Exhibit (Explanation of the project on the left hand text panel.
2. A-scope for Chain Home Radar in the 1940's (first radar and could be tricky)
3. A-scope for marine radar in the 1940's (Before PPI radar); was a bit tedious to operate
4. PPI scope for marine traffic control (uses beam sweeping in all 360 degrees of
   rotation); Easier to use than a scope
5. PPI scope on board a boat. Shows how movement of a boat affects the radar display

Please note that these scopes will not appear all at once. The selection of which scope
the visitor sees is done by pressing a forward control and a reverse control to go around
the loop of scopes. The first display when the system is turned on or booted up is the 
Introduction of the exhibit.

Before going, we must point out the target information handling which will
affect all scopes.

====================================================================================

TARGET DETAILS & TARGET PIPELINE

Targets for this project will come from two sources:

1. Raspberry pis communicating with an AIS receiver for boats and
   an ADS-b receiver for aircraft
2. An on-board simulator that will simulate targets using an internal
   table of target information in the postgresql database.

Please note that the components for the AIS and ADS-B target
handling are not yet available, thereby the programming for that
source will not be defined at this point.

All targets from the network pipeline and simulator map to this C master structure:

struct target_data_structure {
   double target_longitude;
   double target_latitude;
   std::string vessel_name; // will be null for no available name
   std::string registration; // will be null for no registration
   float length; // in meters
   float beam; // in meters
   float altitude; // in meters
   int vessel_type; // AIS type code or aircraft type
   uint32_t mmsi; // AIS unique identifier; ICAO hex address for aircraft
   float course; // course over ground, degrees, based on true north
   float speed; // speed over ground, knots
   time_t timestamp; // time of last fix; used to age out stale targets
   float altitude; // meters, but 0 for boats
   int type; // type of target; vessel or aircraft; 0 = vessel
};

Of this heavy structure, only the following lean mathematical footprint is submitted
to the graphics shaders via an OpenGL Shader Storage Buffer Object (SSBO) layout(std430):

struct target_data_to_shader_structure {
  float target_x;     // Pre-converted local Cartesian offset X (meters relative to radar)
  float target_y;     // Pre-converted local Cartesian offset Y (meters relative to radar)
  float length;       // Vessel length bounds (meters)
  float beam;         // Vessel beam bounds (meters)
  float course;       // Course over ground vector (radians relative to True North)
  float altitude;     // Target altitude profile (meters = 0 for boats)
  time_t timestamp;   // Data aging tracking identifier
  float reflectivity; // derivied from vessel_type (see table below)
  float height_estimate;  // derived from vessel_type (see table below)
  float noiseglint;   // derived from vessel_type (see table below)
  int32_t police_boat  // this indicates that the target is for the roaming police boat. 1 = police boat
  int type            // target type (0 for boat 1 for aircraft)
};

Please note that height and material are not included in the data received from the simulator
nor the raspberry pis. This has to be derived. That will be discussed when I talk about the
traffic cop later in this document.


=============================================================================

Please note that the first iteration of the project will have only minimal controls.
This is a suggestion I got after meeting with the museum staff. Perhaps later we may
add more controls.

Also, please note that the state of the controls of each scope is independent of any
other scope. Furthermore, the controls will reset when a scope is exited and then re-entered.

Controls to affect the behavior of the scopes; (these first implemented using keyboard
strokes; later when and if physical controls are completed, the keyboard controls will be removed)
These controls will affect the state variables and the uniform variables of the shaders.

Please note that identical controls will be the same keyboard strikes or physical controls for all scopes.

=======================================================
Overall control information

These are knobs on the panel once that is built. Keyboard keys until then

1. Intensity - scope overall intensity - Keyboard 3 is lower; 4 is higher
2. Receiver Sensitivity - signal intensity - Keyboard 5 is lower; 6 is higher
3. STC Sensitivity - sensitivity for closer in targets which can overwhelm
   overall sensitivity - keyboard q for lower; w for higher 
4. STC Range - range to which sensitivity to closer targets is effective
   keyboard e for lower; r for higher
5. Noise filter for rain noise. This is for the ppi scopes. keyboard t for decrease filtering and y
    to increase filtering
6. Radiogoniometer knob for Chain Home; keyboard u for left turn; i for right turn.
7. Maximum Range for all scopes except for chain home, which is fixed. This control
   will not operate for Chain Home. These maximum
   range selections will be defined for each scope; If you try to go beyond the stated
   maximum ranges, the control will have no effect; note that the default maximum range would
   be the highest for that scope; keyboard o for lower; p for higher 
8. Range Cursor (for ppi scopes, not for a scopes) keyboard a for lower; s for higher
9. Bearing Cursor (for all ppi scopes; not for a scopes) 
   keyboard d for counterclockwise f for clockwise
10. Bearing for Marine A Scope. This is not for a cursor, but this operates the motor
   that revolves the antenna unit keyboard g for counterclockwise and h for clockwise
   
Notes; I am maintaining control separation between radiogon, marine a scope bearing, and ppi bearing. 
However, when I make the control panel, those will be combined as one physical knob with three
labels, chain home radiogon, marine a scope antenna rotation, and ppi scope bearing to save on 
hardware; the same physical control can be used for multiple scopes.

Note that the range cursor is different from the maximum range. Maximum range is the maximum
radar range setting and range cursor is the range portion of the ppi cursor.

=========================================================================
RADAR EQUATION (for all radars; note that is different for chain home)

The fundamental radar equation describes how much power returns to a radar system 
after bouncing off a distant target. 

Physically, it follows a "round-trip" journey of energy: the radar transmits a signal 
that spreads out as a sphere (losing strength by the square of the distance, $R^2$), 
hits a target that reflects a portion of that energy (the Radar Cross Section, $\sigma$), 
and that reflection then spreads out again as a second sphere on its way back (losing 
another factor of $R^2$). 

控制方程 (Governing Equation):
$$P_r = \frac{P_t G^2 \lambda^2 \sigma}{(4\pi)^3 R^4}$$

Where:
  P_r = Received Echo Power (Watts)
  P_t = Peak Transmitter Power (Watts)
  G   = Antenna Gain (Linear)
  λ   = Wavelength of transmitted carrier wave (Meters)
  σ   = Radar Cross Section (RCS, Meters squared)
  R   = Target Range from station origin (Meters)

Because each of our target scopes contains fixed design loops, these values can be pushed directly
as hardware uniform parameters matching specific hardware loop gains that do not change.

========================================================

Individual scope informations

1. Introduction to project. Just text. No scopes. Only one control for entering
   scope selection loop. Keyboard keys 1 for forward; 2 for backward.
   This is a loop type selection If you are in the Introduction, and you touch
   keyboard 2 for backward, you would go to scope 5, PPI Scope for a boat.

   =================================================================

   To control the extent of the selection of the scopes by the user, there sill be 
   a selection of what scopes are to be avaiable to the museum visitor. This
   will enable the museum to select what scopes will be available and what scopes
   will not be available at all. A script that will invoke the radar simulator
   application will need to pass parameters to indicate what will be available.
   For example, if the script were to call: 

   {PROJECT_DIR}/build/radar_simulator chain_home marine_ascope marine_traffic_ppi 

   then only the chain_home, marine_ascope and marine_traffic_ppi will be availablel
   but the marine_ppi_on_a_boat will not be available.

   This feature is needed for troubleshooting but also to allow the museum to control
   what is available. For example, if the museum is crowded with large school groups,
   then they would only have one scope available; so if they did

   {PROJECT_DIR}/build/radar_simulator marine_traffic_ppi

   Only the marine traffic control ppi scope will be available and the scope
   selection controls will not be operatable.

   To be clear, the names of the scopes currently being built are:

   1. chain_home
   2. marine_ascope
   3. marine_traffic_ppi
   4. marine_boat_ppi

   Startup behavior rules based on argument count:

   - No arguments: all four scopes available, Introduction shown first,
     navigation keys 1 and 2 are active.

   - Two or more scope names: only those scopes available in the loop,
     Introduction shown first, navigation keys 1 and 2 are active.

   - Exactly one scope name: Introduction is suppressed entirely. The
     application launches directly into that scope and stays there.
     Navigation keys 1 and 2 are disabled. The public will have no
     awareness that any other scope exists. This mode is intended for
     large groups and high-traffic museum days where staff want to
     dedicate the exhibit to one display without contention.

   ====================================================================

2. A Scope - sweep on horizontal axis. A pulse will appear for a return. The distance from
   the left hand side to the pulse is the range. The height of the pulse is the strength
   of the return signal. The bearing is determined by manual control.

   The basic controls for both A Scopes include:
      Intensity 

      Sensitivity (the strength of the signal amplification of the
      receiver). This has nothing to do with the brightness of the
      pulses. This only affects the height of the pulse and the height
      of any noise floor.

      STC sensitivity; sensitivity for close in targets. 

      STC sensitivity range; how far shall the STC sensitivity have effect. 

    Please note that the phosphor (chemical that glows when hit by
    electrons in the tube) is green, similar to an oscilloscope. The Hex
    for green is #39FF14; there is a short persistance of the phosphor after being
    struck by the electron beam. That persistance is about 25 milliseconds and its color
    is darker green at about Hex #004400

    Please also note that there are no graticules on either the Chain Home a scope nor
    the marine a a scope. The only thing on the external plate is the base line (zero signal
    which the operator can refer to that is going on (grass, calibration, and signals). It
    is an important point of reference. That base line is illuminated on the sides with small
    incandescent lamps. It is a different color than the display itself. The incandescent
    color is that of the #47 pilot lamp hex #FFB347.

   2-1 Chain Home A Scope

   ==========================================================

   [INVERTED DISPLAY & ADDITIVE VIDEO SIGNAL SUMMATION]

   The Chain Home A-Scope display layout is inverted. The steady, horizontal reference baseline 
   (the zero signal line) sits at the top of the tube display window (`y = 0.9` in viewport NDC coordinates). 
   All video activity—receiver noise floor grass, fixed calibration markers, and target echo pulses—deflects 
   vertically **downward** into the dark lower quadrants of the screen.

   This top-down structure protects the upper edge of the reference line from being distorted by noise grass, 
   enabling the operator to align the trace against an illuminated glass graticule with a clean visual focus.

   All source signals are combined in a single hardware mixing matrix before driving the electrostatic plates:
   * **Receiver Chain:** The atmospheric background noise floor (grass) and the target echo pulses are combined 
     first, and are directly scaled by the operator's Receiver Sensitivity control.
   * **Calibration Chain:** The crystal-controlled 20-nm reference pips are injected into the video chain 
     AFTER the primary receiver gain stage. As a result, modifications to the Sensitivity control do not scale 
     the vertical height of calibration pips.

   [SUMMING AMPLIFIER PROTOCOL]
   float receiver_signal = (noise_floor + target_echoes) * sensitivity;
   float total_deflection = receiver_signal + calibration_pips;
   
   // Map values downwards from structural baseline ceiling
   float final_y = baseline_y - total_deflection;

   If an operational target echo shares an identical range slot with a 20-nm calibration pip, their respective 
   voltages additively sum, dynamically pushing the peak tip further toward the bottom layout limit.

   ==========================================================================

   Because the receiving antennas are very large (about 100 feet), the
   operator cannot physically move them. 
       
   Therefore, the bearing is determined through a process called radio direction
   finding (RDF) using a specialized instrument known as a Radiogoniometer.

   The receiver towers (which were separate from the transmitter towers) 
   featured two sets of dipole antennas mounted at right angles to one 
   another—essentially one oriented North-South and the other East-West.

   The signals from these two perpendicular antennas were fed into a Radiogoniometer
   located in the receiver hut. Inside the device there are two fixed coils (field coils)
   that were mounted at right angles matching the orientation of the outdoor antennas.
   A third coil, the search coil, is mounted on a rotating shaft inside the two 
   field coils. The operator would physically turn a knob to rotate the search coil.

   The relative strength of the signal in each antenna depended on the angle of the
   incoming wave. For example, a target directly to the North would produce a maximum
   signal in the North-South antenna and zero in the East-West antenna. 

   The operator would look for a null point (a signal or pip weaker than the noise floor).
   At that point, the operator would read the bearing from a calibrated scale attached
   to the radiogoniometer knob.

   We can simulate the radiogoniometer knob that would affect the null point depending
   on the bearing of a target. The museum visitor could experience seeing different
   null points for each target. Since we do not have a physical calibrated knob, we
   can put the bearing as a text indicator below the A Scope.

   The range is 200 nm. That is the only range option for this scope.


   RADAR EQUATION STUFF FOR CHAIN HOME

   For Chain Home:
   Transmitter Power : 500 KW
   Wavelength 12 Meters
   Antenna Gain 5 dB
   Pulse Width 20 microseconds
   Beam Width 150 degrees (floodlight)
   PRF 25 HZ

   [METRIC RESONANCE MODEL]
   Because the 12-meter carrier wavelength closely matches the physical physical dimensions (wingspan) 
   of historical combat aircraft (e.g., 10-12 meters), the target functions as a resonant half-wave dipole. 
   When a target's physical length falls within the Rayleigh/Mie resonant band (40% to 60% of the carrier wavelength), 
   the backscattering intensity receives an explicit 1.5x structural cross-section multiplier.

   // Pseudocode for Shader/Logic
   // Derive range and bearing from SSBO Cartesian fields
   float ch_range        = sqrt(target.target_x * target.target_x + target.target_y * target.target_y);
   float ch_bearing_rad  = atan(target.target_x, target.target_y); // GLSL atan(y,x) measured from north
   float ch_aspect_angle = ch_bearing_rad - target.course;         // course is radians in SSBO
   float ch_proj_width   = abs(sin(ch_aspect_angle)) * target.length + abs(cos(ch_aspect_angle)) * target.beam;
   float base_sigma      = ch_proj_width * 2.5;

   float resonance  = (target.length >= wavelength * 0.4 && target.length <= wavelength * 0.6) ? 1.5 : 1.0;
   float final_sigma = base_sigma * resonance;

   The 20-nm Markers: Chain Home used crystal-controlled oscillators to create 
   fixed reference "pips" every 20 nm. These should be rendered as thin, 
   vertical spikes that never move, regardless of target sensitivity.

   The "Floodlight" Effect: Because the beam is 150° wide, the A-Scope will 
   show every aircraft in that massive sector simultaneously. The only way to 
   tell them apart was the range (distance from left) and the Radiogoniometer nulling.

   The Waveform Shape: For CH, the pips should be slightly "noisier" than 
   marine radar. Use a random jitter function in your vertex shader to 
   simulate the atmospheric noise floor common at 25 MHz.

   2-2 Marine A Scope

   Utilization of A scope marine was limited to military use prior to PPI scope
   invention. An example is British Type 271 radar, introduced in 1941.

   Marine radar frequencies allowed the use of much smaller antennas;
   dishes or horns. Those antennas would be mounted on the shaft of a servo motor. The
   servo motor would be driven by another servo that is attached to the bearing control
   knob on the radar console. The bearing is on a calibrated dial on the bearing control
   knob.

   We can simulate the bearing knob that would affect the simulated pointing of the
   dish antenna. The museum visitor could experience seeing different
   pips appear as they rotate the antenna toward them. Likewise the pips would disappear
   as the antenna is rotated away.

   The range is indicated at how far the pip is from the left hand side of the scope which
   is the location of the radar transmitter. If the target goes further away, 
   the pip will move to the right. If the target comes close to you, the pip will 
   move left.

   This pip has a finite rise time as the transmitter starts.
   The width is set by the modulator stage in the transmitter.
   Following the width, the pip has a finite fall time as the transmitter stops. This
   creates a curved waveform; not just a line.

   A photograph for this display show no graticule at all. Only range pips formed by an oscillator.
   Those oscillator pips are fixed. Range settings do not affect them. There is a baseline at the
   bottom, etched in glass, that is side illuminated by incandescent lamps.

   Like Chain Home, the Marine A-scope sums noise, calibration pips, and target echoes into a
   single signal before the deflection plates — this is a hardware reality of any CRT A-scope.
   The calibration pips are injected after the receiver gain stage, so they remain at constant
   height regardless of the Sensitivity control. The shader summing follows the same structure:

   /* Simulated Summing Amplifier Logic — same pattern as Chain Home.
      Cal pips outside sensitivity multiply because they bypass the receiver gain stage. */
   float receiver_signal = (noise_floor + target_echoes) * sensitivity;
   float total_deflection = receiver_signal + calibration_pips;

   // Apply to upward-deflecting baseline (Marine scope is right-side up)
   float final_y = baseline_y + total_deflection;

   The maximum ranges for this scope are: 
   1. 1.5 nm; marker pips every 0.25 nm
   2. 3.0 nm; marker pips every 0.5 nm
   3. 6.0 nm; marker pips every 1.0 nm
   4. 12.0 nm; marker pips every 2.0 nm

   RADAR EQUATION FOR MARINE A SCOPE

   Here are the fixed values (those values that can be declared as 
   uniforms) for the marine a scope radar. I suggest these for the uniform names

   peak_power = 500 KW
   wavelength = 10 centimeters
   antenna_gain = 30 db
   pulse_rep_frequency = 500 hz
   horizontal_beamwidth = 2.5 degrees

   Now we have some stuff for the settings file:

   SYSTEM_TEMPERATURE ($T_s$): Usually 290K; used to calculate the noise floor
   NOISE_FIGURE ($F$): A value in dB that determines how much "grass" your 
     specific receiver adds to the signal (20 dB typical for early centimetric systems).
   BOLTZMANN_CONSTANT ($k$): $1.38 \times 10^{-23}$, essential for the thermal 
     noise part of your simulation.

   Now, here is a snippet of pseudo code:

   /* Uniform Variables provided by CPU */
   uniform float u_AntennaBearing;    // Current rotation of knob/motor, degrees
   uniform float horizontal_beamwidth; // Fixed for the scope (e.g., 2.5 degrees)

   /* Logic for each Target — all derived from SSBO fields target_x, target_y, length, beam, course */

   // Derive range (meters) and bearing (degrees, north-clockwise) from Cartesian SSBO fields
   float target_range       = sqrt(target.target_x * target.target_x + target.target_y * target.target_y);
   float target_bearing_rad = atan(target.target_x, target.target_y); // GLSL atan(y,x) from north
   float target_bearing_deg = degrees(target_bearing_rad);
   if (target_bearing_deg < 0.0) target_bearing_deg += 360.0;

   // Compute RCS from length and beam using aspect angle (course is radians in SSBO)
   float aspect_angle    = target_bearing_rad - target.course;
   float projected_width = abs(sin(aspect_angle)) * target.length + abs(cos(aspect_angle)) * target.beam;
   float target_rcs      = projected_width * 2.5;

   float angle_diff = abs(target_bearing_deg - u_AntennaBearing);

   // Handle the 359 to 0 degree wrap-around
   if (angle_diff > 180.0) angle_diff = 360.0 - angle_diff;

   // BeamFactor: 1.0 at center, drops to 0.5 at horizontal_beamwidth/2
   // This creates the "fade in / fade out" effect as you turn the knob
   float beam_factor = exp(-2.77 * pow(angle_diff / (horizontal_beamwidth / 2.0), 2.0));

   // Final received power Pr — uses only derived values, no missing SSBO fields
   float Pr = (peak_power * pow(antenna_gain, 2.0) * pow(wavelength, 2.0) * target_rcs * beam_factor) /
              (pow(4.0 * PI, 3.0) * pow(target_range, 4.0));

3. PPI Scope

   PPI stands for Plan Position Indicator

   The Core VisualizationThe PPI scope represents the radar antenna as a 
   central point (the origin).The "Sweep": A radial line rotates 360 
   degrees around the center, synchronized with the physical rotation of the 
   radar antenna.Distance (Range): 

   The distance from the center of the screen to a "blip" 
   represents how far away the object is.

   Bearing (Azimuth): The angle of the blip 
   relative to the top of the screen (usually North or the front of a ship/aircraft) 
   represents its direction. 

   Key Technical ComponentsIf you are prompting an AI to simulate or analyze a PPI, 
   use these technical markers:Persistence: Older CRT scopes used "long-persistence 
   phosphors" so that a target would remain visible as a glowing trail 
   even after the sweep line passed over it.

   Range Rings: Concentric circles overlaid on the display to help the operator estimate 
   distance at a glance.

   Strobe/Cursor: A line or marker used to pinpoint the exact coordinates of a specific target.

   Mathematical LogicFor an AI to process PPI data, it needs to understand the conversion 
   from Polar to Cartesian coordinates. If a radar detects a target at distance $r$ and 
   angle $\theta$, the position on the 2D screen $(x, y)$ is calculated as:$$x = r \cdot 
   \sin(\theta)$$$$y = r \cdot \cos(\theta)$$. 

   Why it differs from other ScopesTo clarify for the AI, distinguish it from the A-Scope: A 
   simple 1D graph showing "Energy vs. Distance" (looks like an EKG).

   Please note that the P7 phosphor for vintage PPI radar has several colors; the
   following table describes this:

   Suggested Simulation Table for the P7 PPI phosphor

   1. Excitation 0 (flash) Bright Blue hex #A0CFFF - note this is active electron beam
   2. Immediate blue - 1 ms duration after Excitation; Blue hex #1010FF - note that 
      from here on is afterglow after beam stops
   3. Short term Yellow Green - 1 second duration after Immediate blue; yellow green hex #E2FF80
   4. Long Term Amber -  10 seconds duration after Short Term; Amber hex #FFA040
   5. Expiration - 12 seconds duration after Long Term Amber; very dark hex #050700

   =========================================================================

   [PPI BEAM-WIDTH & TARGET SPATIAL ARC DISPERSION]

   Because the radar antenna emits a beam with a finite horizontal beamwidth rather than an 
   infinitely narrow beam, targets intersect the energy wave continuously as the antenna rotates 
   across their angular boundaries. This spatial geometric interaction distorts a standalone point target 
   into a concentric circumferential **arc** drawn perpendicular to the radar's origin vector.

   The returned signal intensity distribution across this structural arc is defined by three rules:
   1. **Geometric Profile Aspect:** The cross-section $\sigma$ is computed dynamically using the target's explicit 
      course vector and bearing. The projected surface area facing the wavefront handles the ship's 
      length and beam parameters:
      $$\text{Projected Width} = (|\sin(\text{aspect\_angle})| \times \text{length}) + (|\cos(\text{aspect\_angle})| \times \text{beam})$$
      $$\sigma = \text{Projected Width} \times 2.5$$
   2. **Gaussian Power Distribution:** The returned echo energy along the arc changes smoothly according to a 
      Gaussian curve. The absolute edges are dimmest (where the beam's outer threshold grazes the 
      target perimeter) and expand exponentially to max intensity at the center axis of alignment.
   3. **Sweep Intersect Envelope:** Energy returns climb from the noise floor, achieve peak voltage 
      at perfect cross-coincidence, and fade back down into the receiver noise as the sweep line passes the target.

   Very important note on PPI radars: Definition of the 'top of the scope' and the graticule:

   For a marine traffic control center radar, the top of the scope or 0 degrees is true north.
   It does  not move sice the control center radar does not move.

   for a boat, the top of the scope is the bow of the boat (where are you heading). The zero
   degrees on the graticule is still true north. The graticule is rotated by a small motor that
   is tied to the boat's gyro compass. This allows the instinctive behavior of the skipper to
   look forward by looking at the top of the scope.

   Another importand note. The circular sweep on the scopr is clockwise. The degree ticks on the
   graticule count clockwise from true north. 

   Note on blooming. If the signal on the grid of the crt is too great, the
   brightness will no longer increase, but there will be a slight blooming of
   the target. 

   Note that values will change depending on the scope being used (traffic control or boat)

   -------------------------------------------------------------------

 The selected_range is from the operator control to select the maximum range of the radar 
 Options for traffic control are:
     1.5 nm range 
     6 nm range
     12 nm range

 Options for boat radar are:
     1.0 nm range
     3 nm range
     6 nm range


------------------------------------------------------------------------------------
   
=====================================================================================

PPI Radar Equiation

   ===================================================================================
   For vessel traffic control radar:

   Transmitter Peak Power - float transmitter_peak; 25.0 KW
   Frequency - float frequency; 9.375 GHZ
   Wavelength - float wavelength; 0.032 meters
   Receiver Noise Figure - float nf; 11 dB
   System Losses - float sl; 8 dB
   Antenna Beam Width - float beamwidth; 0.7 degrees
   Pulse width - float pulsewidth; 0.1 microsecond
   Pulse Rep Rate lookup table based on selected maximum range - float prf;
     1.5 nm range PRF 3500. HZ
     6 nm range PRF 2000. HZ
     12 nm range 1000. HZ
   Radar Antenna Rotation Rate float antenna_rpm; 15 RPM
   Radar Antenna Gain - float radar_antenna_gain_db; 34 dB

   Receiver gain notes:
   Raw gain is about 100 dB
   However the gain is logarithmic. It compressed the massive dynamic range 
     of real-world echoes so that a giant ship and a small wooden fishing 
     boat could both be visible on the CRT at the same time without the operator 
     constantly riding the gain knob.

     The operator would turn the Gain knob up until the background 
     of the CRT just started to fill with a faint, shimmering, dancing 
     texture of fine static sparks (often called "grass" or "receiver noise").

     If the gain was too low, weak echoes from distant targets or small 
     fiberglass boats in Bellingham Bay wouldn't have enough voltage 
     to overcome the CRT's grid cutoff, leaving them completely invisible. 
     If the gain was too high, the screen would "blossom" with solid white noise, 
     blinding the operator entirely.
   
   ====================================================================== 

   For Polce or coast guard boat radar:

   Transmitter Peak Power - float transmitter_peak; 15.0 KW
   Frequency - float frequency; 9.375 GHZ
   Wavelength - float wavelength; 0.032 meters
   Receiver Noise Figure - float nf; 11 dB
   System Losses - flost sl; 8 dB
   Antenna Beam Width - float beamwidth; 1.2 degrees
   Pulse Rep Rate lookup table based on selected maximum range - float prf;
     1.0 nm range PRF 4000. HZ
     3 nm range PRF 3000. HZ
     6 nm range 1000. HZ
   Pulse width - float pulsewidth; 0.1 microsecond
   Radar Antenna Rotation Rate float antenna_rpm; 25 RPM
   Radar Antenna Gain - float radar_antenna_gain_db; 31 dB

   Receiver gain notes:
   Raw gain is about 100 dB
   However the gain is logarithmic. It compressed the massive dynamic range 
     of real-world echoes so that a giant ship and a small wooden fishing 
     boat could both be visible on the CRT at the same time without the operator 
     constantly riding the gain knob.

     The operator would turn the Gain knob up until the background 
     of the CRT just started to fill with a faint, shimmering, dancing 
     texture of fine static sparks (often called "grass" or "receiver noise").

     If the gain was too low, weak echoes from distant targets or small 
     fiberglass boats in Bellingham Bay wouldn't have enough voltage 
     to overcome the CRT's grid cutoff, leaving them completely invisible. 
     If the gain was too high, the screen would "blossom" with solid white noise, 
     blinding the operator entirely.
   
   =====================================================================

   Locations of the radars -

   Chain Home : RAF Ventnor (Isle Of Wight) 
      Decimal Degrees (Latitude / Longitude): 50.600° N, 1.183° W 

   A Scope Marine - Anchored Boat at Community Boating Center (stays here, not moving around)
      latitude \(48.70529^{\circ}\text{ N}\) and longitude \(-122.50547^{\circ}\text{ W}\)

   PPI Marine Traffic Control - center of bellingham bay
      latitude \(48^\circ 43' 25" \text{ N}\) and longitude \(122^\circ 34' 35" \text{ W}\) 

   PPI Police Boat ( starts as docked at the taylor street dock; but will be moving around
      48.7253874° N latitude and -122.5077482° W longitude.

   =====================================================================

   Graticules - These are plastic overlays over the face of the scope. They are
   for the purposes of showing the bearing. They are calibrated in degrees; short line (1/8 inch)
   each degree; medium line (1/4 inch) for every 5 degrees; and a longer line (1/2 inch) for every
   10 degrees. Line for true north; 2/3 inch.

   Notes that these graticule lines are lit by a #47 incandescent bulb #FFB347.

   There are text numeric values for every 15 degreen

   There are two rings
   The bearing ticks are on the inside of the outer ring. 
   The numeric degree values are between the outer and inner rings.
   The innter ring is the boundry for active targets and the sweep.

   =======================================================================

   Range Rings:

   There shall be range rings (Direct emission from the phosphor, same
   as target. They will vary by the maximum range setting.

   Range rings shall be only for the PPI radar. Not for the a scope radar.
   For the Marine Traffic Radar:

   Options for traffic control are:
     1.5 nm range rings
       .5 nm 
       1.0 nm
     6 nm range
       2.0 nm
       4.0 nm
     12 nm range
       4.0 nm
       8.0 nm

   Options for boat radar are:
     1.0 nm range
       .5 nm
     3 nm range
       1 nm
       2 nm
     6 nm range
       2 nm
       4 nm

     PLease note that there is  no range ring for the full range as that is
     the inner ring of the graticule

     Please also note that the range rings will behave the same as targets as
     far as the P7 phosphor is concerned. This means that they will glow brigh
     as the sweep passes over them. But they will decay the same as the targets.

     The range rings will not be subject to effects by the gain control nor
     the noise suppression control

   =======================================================================

   GROUND TERRAIN AND LIDAR DATA PROCESSING

   Note that the following files already exist in the project directory
   in the following:

   map
   map/lidar_processed
   map/charts_enc
   map/charts_enc/US5WA45M.000
   map/charts_enc/n48_w123_1arc_v3.tif
   map/lidar_raw
   map/lidar_raw/wa2022_nooksack_dem_J1364940.zip
   map/lidar_raw/wa2016_west_dem_J1364939.zip
   map/gebco_for_chain_home
   map/gebco_for_chain_home/gebco_2026_n52.866_s46.397_w-3.93_e2.784_jpg_hs.jpg
 
   For Chain Home only:

   New file added - 
     map/gebco_for_chain_home/gebco_2026_n52.866_s46.397_w-3.93_e2.784_jpg_hs.jpg

   The file map/gebco_for_chain_home/gebco_2026_n52.866_s46.397_w-3.93_e2.784_jpg_hs.jpg
   is a GEBCO hillshaded JPEG. The _hs suffix means "hillshade" — a directional-lighting
   visual render of the terrain. It is NOT raw elevation data. Pixel color values cannot
   be directly converted to meters above sea level.

   Bounding box (from filename):
     North: 52.866 N
     South: 46.397 N
     West:  -3.930 E (3.930 W)
     East:   2.784 E

   RAF Ventnor (50.600 N, -1.183 E) maps to approximately pixel (659, 544)
   in a 1611x1553 image (top-left origin, Y increases downward).

   UV coordinate formula for a given lat/lon in the shader:
     u = (longitude - LON_W) / (LON_E - LON_W)
     v = (LAT_N - latitude)  / (LAT_N - LAT_S)   // v=0 at top

     LAND vs. WATER DISCRIMINATION
     Since this is a hillshade image without elevation values, land and water must be
     distinguished by color hue in the fragment shader. Convert the RGB texture sample
     to HSV and apply these thresholds:
       Ocean (no echo):  HSV hue approximately 170 to 220 degrees
       Land  (echo):     HSV hue approximately 50 to 170 degrees
     These are defined in settings.h as GEBCO_OCEAN_HUE_MIN_DEG and GEBCO_OCEAN_HUE_MAX_DEG
     so they can be tuned without rewriting shader code.

     Because JPEG compression blurs color at coastline boundaries, apply a small
     soft transition zone (GEBCO_COAST_BLEND_UV_WIDTH in settings.h) to avoid
     a hard pixel-boundary artifact at the shoreline.

     COVERAGE NOTE
     The 200nm range circle from Ventnor extends beyond the image to the north, east,
     and west. Any radar sample point whose lat/lon falls outside the bounding box
     should be treated as open ocean (zero land echo). The primary threat axis
     (English Channel, French coast, Belgian coast) is fully within the image.

     ELEVATION LIMITATION
     This file provides land/water masking only, not actual terrain height. All land
     returns use a single fixed amplitude defined in settings.h as
     CHAIN_HOME_LAND_ECHO_AMPLITUDE. There is no line-of-sight terrain shadowing
     for Chain Home; the 150 degree floodlight beam and 12m wavelength make
     fine terrain masking historically irrelevant for this simulation.

     OPTIONAL UPGRADE PATH
     If higher fidelity is later needed, replace with the raw GEBCO NetCDF grid
     (GEBCO_2024.nc, freely downloadable from gebco.net). That file provides actual
     elevation in meters on a 15 arc-second grid and can be loaded as a GL_R32F
     texture giving real line-of-sight capability.

     SETTINGS.H ENTRIES FOR CHAIN HOME TERRAIN
     #define CHAIN_HOME_MAP_LAT_N          52.866f
     #define CHAIN_HOME_MAP_LAT_S          46.397f
     #define CHAIN_HOME_MAP_LON_W          -3.930f
     #define CHAIN_HOME_MAP_LON_E           2.784f
     #define CHAIN_HOME_LAND_ECHO_AMPLITUDE 0.6f   // fraction of max A-scope deflection
     #define GEBCO_OCEAN_HUE_MIN_DEG        170.0f
     #define GEBCO_OCEAN_HUE_MAX_DEG        220.0f
     #define GEBCO_COAST_BLEND_UV_WIDTH     0.003f  // ~1-2 pixels at this resolution
     #define ENABLE_CHAIN_HOME_TERRAIN      1       // set 0 to suppress land clutter



   For all except for Chain Home:

   This needs to be processed into a fragment shader via ray casting.

   Details on files:

   US5WA45M.000 (ENC Chart): This is an Electronic Navigational Chart 
       (S-57 format) specifically covering the Bellingham Bay and Harbor 
       area. It contains vector data for the shoreline, docks, piers, 
       and water depths (bathymetry), as well as navigational aids.

    Note that the US5WA45M.000 (ENC Chart) may not be needed at all. The
    n48_w123_1arc_v3.tif (DEM) and the LiDAR DEMs already define land vs. water
    by elevation — any cell above 0 meters is land and will return a radar echo.
    The ENC vector shoreline data would be redundant for this purpose.


   n48_w123_1arc_v3.tif (DEM): This is a USGS 1-arc-second Digital 
       Elevation Model in GeoTIFF format. It provides a continuous grid of 
       land elevation data for the area (roughly 30-meter resolution). 
       This is your primary source for the broad terrain shape.

   wa2022_nooksack...zip & wa2016_west...zip (LiDAR DEMs): These are 
       high-resolution Digital Elevation Model zips from the Washington 
       State LiDAR portal. They contain the incredibly detailed data for 
       localized buildings, docks, and fine shoreline features that the 
       30-meter USGS data misses.

   Suggestion for creating height map:

       Use GDAL (Geospatial Data Abstraction Library): Use command-line 
       tools like gdal_translate or gdalwarp to extract the elevation data 
       from the .tif and the unzipped LiDAR files.

       Reproject to a Common Grid: Crop and reproject all of these datasets 
       into a matching coordinate system (like UTM Zone 10N) and resample 
       them onto a single, standardized 2D grid. Layer them so that the 
       high-resolution LiDAR overrides the coarser USGS data where they overlap.

       Generate a Height Map Texture: Convert this final 2D grid of 
       elevation values into a single-channel floating-point data structure. 
       In OpenGL, you will load this directly into VRAM as a GL_R32F 
       (32-bit floating-point red channel) 2D texture, where the texture 
       coordinates $(u, v)$ represent latitude/longitude or local grid 
       coordinates, and the pixel value represents the height above sea level.

       Note that it may be better to do the conversion once and save the grid of 2d
       elevation data into a file. So, during the first invocation of the radar program,
       run the conversion once and put the final data into map/converted_data. If the file
       is already there, then do not perform the conversion.

       The texture will need to be moved around for the police boat ppi radar, but it
       will be stationary for the vessel traffic control ppi radar.

       Please note that we should have values defined in the settings.h file for the
       strengths of the DEM map information and the LiDAR information as well as the
       total strength of all of the shoreline information.

   SETTINGS.H VALUES FOR LAND AND TERRAIN DATA

   The following entries belong in settings.h so that terrain rendering behavior
   can be tuned or disabled without touching shader or pipeline code.

   /* ------------------------------------------------------------------
      TERRAIN / LAND DATA — settings.h entries
      ------------------------------------------------------------------ */

   /* Master on/off switch for all terrain rendering.
      Set to 0 to suppress land echoes entirely; useful when you want to
      observe only live targets without any terrain clutter. */
   #define ENABLE_TERRAIN_RENDERING  1

   /* Individual layer enable flags.
      Lets you isolate which elevation source is contributing echoes. */
   #define ENABLE_DEM_LAYER    1   // USGS 1-arc-second (~30 m) GeoTIFF
   #define ENABLE_LIDAR_LAYER  1   // High-res WA State LiDAR DEMs

   /* Elevation threshold (meters) above which a cell is treated as land
      and returns a radar echo.  0.0 = sea level.  Raise slightly (e.g.
      0.5) to suppress low-lying marshes that are technically above datum
      but unlikely to produce a real ship-hazard echo. */
   #define LAND_ELEVATION_THRESHOLD_M  0.0f

   /* Echo amplitude scalars for each elevation source.
      These feed the fragment shader as uniforms so the DEM and LiDAR
      layers can be weighted independently.
      1.0 = nominal; raise to exaggerate; lower to soften. */
   #define DEM_ECHO_STRENGTH    1.0f
   #define LIDAR_ECHO_STRENGTH  1.2f   // slightly stronger — LiDAR is more accurate

   /* Overall terrain echo scale applied after the per-layer scalars.
      Acts as a master volume knob for all shoreline / land returns.
      Adjust this first before touching the per-layer values. */
   #define TERRAIN_ECHO_TOTAL_SCALE  1.0f

   /* Raycasting step size (meters) used when marching each radar beam
      sample through the height-map texture.  Smaller = more accurate
      shoreline but more GPU cost.  8.0 is a reasonable starting point
      for the ~30 m DEM; drop to 4.0 if LiDAR detail is being missed. */
   #define TERRAIN_RAY_STEP_M  8.0f

   /* Maximum elevation (meters) used to normalize the GL_R32F texture
      values before they reach the shader.  Should be >= the tallest
      terrain feature in the scene (Bellingham area peaks ~600 m). */
   #define TERRAIN_MAX_ELEVATION_M  700.0f

   /* Height (meters) at which LiDAR data takes priority over the coarser
      USGS DEM when the two grids are composited during map/converted_data
      generation.  Cells with LiDAR values >= this threshold replace the
      DEM value; cells below fall back to DEM. */
   #define LIDAR_PRIORITY_HEIGHT_M  0.5f

   /* Debug: show only terrain echoes, suppress all moving targets.
      Set to 1 to isolate land-clutter rendering during development. */
   #define DEBUG_TERRAIN_ONLY  0

   /* Debug: colour-code which elevation source is active for each pixel
      (blue = DEM only, green = LiDAR override).  Set to 1 to visualise
      coverage boundaries when compositing the height-map. */
   #define DEBUG_SHOW_LIDAR_COVERAGE  0
  
=============================================================

RADAR DATA HANDLING AND PROCAESSING 

PLease note that this final topic covers several pieces, including
the postgres database table data, the complete operation of the Traff Cop software,
and the operation of the Simulator.

Please also not that this will not inlude the software to handle target being received
from the AIS and ADS(b) data, which will be received on AirSpy software defined radios
and formatted by raspberry pi's. This will not be used for the initial demonstarion
of this project. We will leave these out for now, and judging from the size of this
current CLAUDE.md file; that may be a separate as yet non existant CLAUDE.md file.

Here is the data items for the table in the Postgres database:

1. float length
2. float beam
3. int32_t material - 1 for metal, 2 for wood, 3 for fiberglass
4. timestamp elapsed_seconds
5. binary may_need_update   (this if a randome value is applied to any part of the target
                             and that the user may want to update the database. This
                             will not force the user to do the update, a later
                             database editing program will use this.
6. int vessel_type (AIS type code or aircraft type)
7. uint32_t mmsi   (unique identification for boat or ICAL for aircraft
8. float reflectivity (derived from target type)
9. float height_estimate (derived from target type)
10. float noiseglint (derived from target type_
11. int32_t police_boat (this indicates that this is for the police boat - 1 is true)

Please note that this database is only for the physical characteristics. Dynamic
information which is created by the simulator (or raspberry pis) as that data changes.


Please also note that the handling for the police boat's cruising around Bellingham Bay
will also be part of the job of the simulator. This is achieved by having an indicator
in the target data, labeled police_boat, which is a binary value. True means that this
target is for the police boat. If the target is the police boat, the police boat radar
will not show the target as a target, but the direction and speed of the police boat
will be taken from the target information of the police boat. This means that the police
boat radar graticulel will be adjuste appropriately and that the location will need to be
used to update the ppi display so that the center of the display will reflect the location
indicated in the target data for this target. Therefore, the actual target information
for the police boat will not be sent to the shaders if the active radar is for the
police boat. The location and heading will be sent to the shaders as uniform values. 
All other radars will treat this just as regular
target.

This also means that there would be the following uniform variables in the shaders for the
police boat:

1. Heading
2. Location (local coordinates) which will have to be derived by the traffic cop from the
   original latitude and longitude from the simulator

Now, lets discuss the threads. There will be five threads:

Thread 1 = Operation of setting up the system; ie; initializeing Opengl,
       compiling, building, and loading all of the shaders. It also handles
       the keyboard operation of the radar controls as those are callbacks from
       the OpenGL. Accepting target traffic from the Traffic Cop will be performed,
       however, the Traffic Cop will need to assert a mutex in order to write any
       target data to this Thread 1. 

Thread 2 = Traffic Cop. The Traffic Cop will accept Target Data from the Simulator (which
       will be discussed here) ; and the AIS and ADS(b) data from the raspberry pis.  At 
       this time, we need to provide only a stub withing the traffic cop to access the Raspberry pis.
       The traffic cop will need to set a mutex before handing radar traffic to the Thread 1 software
       which will send the radar data on to the shaders. Note that the traffic cop needs to do
       the following processing with target data:

          1. Convert the GPS longitude information to the local coordinates for each radar.
          2. Check and discard targets that are outside the range of the radar that is
             currently being used. 
          3. Discard targets whose timestamp is older than 1 hour

Thread 3 = Simulator. The simulator creates and manages the simulated targets. It needs to
        assert a mutex to transfer target information to the Traffic cop

Thread 4 = Physical controls, knobs and switches. This has not been developed and will not
        be developed with the current version of the project. It will be developed later. It
        does need to set a mutex in order to send control information to Thread 1 so that thread
        can set the state variables and the uniforms for the controls in the shaders.

Thread 5 = Raspberry pi data recever = this receives the data from the raspberry pis. It will
        not be developed for the current version of the project. The Raspberry PI receiver will
        need to set a mutex in order to transfer target data to the traffic cop.

================================================================================

The traffic cop - handling radar data and deriving reflectivity stuff

The segment handling asynchronous target ingestion is called traffic_cop.
The traffic_cop runs on a dedicated background execution thread separate from the rendering loop. 
Data synchronization between threads must be managed explicitly using std::mutex blocks.

Among its duties are to manage the database.

New targets (which have not ever been introduced to this system will be added to the
database. The purpose of the database is to remember physical characteristics of targets;
ie; the stuff that is not dynamic to the movement of the target. 

This is to save processing time for stuff such as reflectivity.

As target data is read from the database, it will also be copied to an in-memory copy
of the database so that processing can be more efficent. The maximum size of the in-memory
copy shall be limited to 200 targets. If the number of targets gets larger than 200 targets, the
targets that have not been updated for the longest time shall be overwritten by new targets.
Judging from the current traffic of bellingham bay, 200 targets for the caching memory copy of
the database should be enough.

As you may notice, height and material have not been including in the table of data we get from
the simulator nor the raspberry pis taking in the ais and ads-b data from the sdr's. Those values
have to be derived by the traffic cop and then entered into the database.

Here is sample code for doing this; this is suggestion only.

Suggested code for deriving target height, reflectivity
    (derived from material) and noiseGlint (derived from material)
     
struct RadarMaterialProfile {
    float reflectivity; // 0.0 (stealth/fiberglass) to 1.0 (massive steel)
    float heightEstimate; // in meters, to calculate radar horizon cutout
    float noiseGlint; // simulated signal scintillation
};

RadarMaterialProfile getMaterialProfile(int aisTypeCode) {
    RadarMaterialProfile profile;
    
    switch(vessel_type) { // vessel_type is in the target_data_structure
        case 30: // Fishing (often wooden/fiberglass hulls, low sitting)
            profile.reflectivity = 0.35; profile.heightEstimate = 4.0; profile.noiseGlint = 0.2;
            break;
        case 36: // Sailing Vessel (tall aluminum mast, but low fiberglass hull)
            profile.reflectivity = 0.25; profile.heightEstimate = 15.0; profile.noiseGlint = 0.4; // High mast glint!
            break;
        case 37: // Pleasure Craft (fiberglass speedboats, yachts)
            profile.reflectivity = 0.20; profile.heightEstimate = 3.0; profile.noiseGlint = 0.1;
            break;
        case 52: // Tugboats (dense, heavy low-sitting steel blocks)
            profile.reflectivity = 0.95; profile.heightEstimate = 35.0; profile.noiseGlint = 0.05; // Perfect metal reflector!
            break;
        default: // Catch-all / Code 0 - some owners neglect setting this in their transponders
            profile.reflectivity = 0.50; profile.heightEstimate = 8.0; profile.noiseGlint = 0.2;
            break;
    }
    return profile;
}


Using this data, the traffic cop can derive the time varying fluctuations of the target
(note that this is suggested code; may not be the actual code you generate; and
note that this data is not included in the database as it is active data)


// Inside your C++ target loop (running every frame or every radar sweep)
float baseReflectivity = target.materialProfile.reflectivity;
float glintFactor = target.materialProfile.noiseGlint;

// Generate a random float between -1.0 and 1.0
float randomNoise = ((float)rand() / RAND_MAX) * 2.0f - 1.0f;

// Calculate the dynamic, fluctuating reflectivity for THIS frame
float dynamicReflectivity = baseReflectivity + (randomNoise * glintFactor);

// Clamp it so it doesn't drop below 0 or overshoot 1.0
dynamicReflectivity = std::max(0.0f, std::min(1.0f, dynamicReflectivity));

// Pass 'dynamicReflectivity' to your GLSL shader uniform array
glUniform1f(glGetUniformLocation(shaderProgram, "targetReflectivity[i]"), dynamicReflectivity);


The segment handling asynchronous target ingestion is called traffic_cop.
The traffic_cop runs on a dedicated background execution thread separate from the rendering loop. 
Data synchronization between threads must be managed explicitly using std::mutex blocks.

[TRAFFIC COP OPERATIONAL TIMING PROTOCOL]
1. Processing Loop: Aggregated targets are packaged into a uniform array and dispatched 
   as they arrive to the traffic cop from the simulator or the receiver handling the raspberry pis.
2. Range Filtering: Discard any targets residing outside the active radar's 
   designated maximum operational range. Apply this if active radar is not Chain Home. Do not apply
   if this is for Chain Home
3. Altitude Restriction: Enforce a strict <= 40-meter restriction for marine nodes (Marine A-scope
   and all PPI Marine radars). Discard any aircraft violating this ceiling. Do not apply this
   restriction if the active radar is Chain Home.
4. Precision Alignment: Latitude and longitude coordinate conversions into local meters relative to 
   the radar origin must be handled on the CPU thread within traffic_cop 
   to protect against FP32 structural precision rounding errors inside the GPU.
5. The determination of the reflectivity, height estimate, and the noiseglint must 
   be processed by the traffic cop
6. The traffic cop needs to know the longitude and latitude of the chain home ascope, the marine ascope,
   and the marine traffic control radars. It will already have the longitude and latitude of the
   police boat radar as it will be processing the location of the police boat radar from the
   simulator.
7. Critical Section: Assert a std::mutex to gain safe writing access to the 
   double-buffered array driving the SSBO, copy the structural contents, and 
   clear the mutex immediately. May I suggest that this activity be performed 
   about once every screen update. However, the processing speed may not be
   able to achieve this. Also bear in mind that data from the raspberry pis
   and the simulator may not updata every screen update. If that's the case, the
   traffic cop will not do anything. Also note that the SSBO  maximum target capability
   will be 200. Oldest targets shall not be sent to the SSBO if the limit is reached.
   Lets include the maximum in memory copy of the database and SSBO shouse be
   a value in the settings.h file.


===============================================================

Sections still to be worked on:

Details on the simulator will be discussed later.

Details on the relationship between the simulator and the movement of the
police boat will be discussed later.

==================================

End of discussion

==================================

