diff --git a/CLAUDE.md b/CLAUDE.md index 7afb7f0..0789cc0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,804 +1,182 @@ -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. +# Historical Radar Simulator Exhibit Project Profile +**Author:** Mark Allyn +**License:** MIT License -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 +## 1. Project Overview & Environment -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) +This project is a museum exhibit designed to simulate vintage radar systems from the 1940s through the 1960s. It features real-time interactive simulations of the early World War II British **Chain Home Radar**, a shore-based **Marine Traffic Control Radar**, and a mobile **Shipborne/Patrol Boat Radar**. -PostgreSQL is installed. Database: radar. User: radar. Password: radar. -User has full privileges on database radar. Table is target_data. +### Hardware Specifications +* **Target Machine:** Geekom A8 Max Mini PC +* **Memory:** 32 GB RAM +* **Processor:** AMD Ryzen 9 8945HS +* **Integrated Graphics:** Radeon 780M Graphics GPU (Primary rendering target) -Operating system details: +### Software & Compiling Stack +* **Operating System:** Ubuntu 25.10 ("Questing") +* **Programming Language:** C++20 +* **Compiler:** g++ (Ubuntu 15.2.0-4ubuntu4) 15.2.0 +* **Graphics Core:** OpenGL 4.3 Core Profile +* **Window & Event Management:** GLFW +* **OpenGL Function Pointer Loader:** GLAD +* **Text Rendering Engine:** FreeType (libfreetype2) +* **Geospatial & Spatial Ingestion:** GDAL (`libgdal-dev` for reading LIDAR/ENC chart assets) +* **Database Infrastructure:** PostgreSQL + * **Database Name:** `radar` + * **Username:** `radar` + * **Password:** `radar` + * **Target Data Table:** `target_data` -Distributor ID: Ubuntu -Description: Ubuntu 25.10 -Release: 25.10 -Codename: questing +### Compilation & Operating Rules +* **Build Automation:** Modern CMake (`CMakeLists.txt`) +* **Project Executable Target Name:** `radar_simulator` +* **Execution Command:** Run from the compiled build folder using `./radar_simulator` or via the full path `{PROJECT_DIR}/build/radar_simulator`. +* **Remote Workflow Environment:** Code is written and verified via SSH sessions from a Windows workstation. Compilation is allowed over the SSH connection. Execution and visual verification must be performed physically on the Geekom hardware. -Use cmake for building. +--- -[DIRECTIVE: GPU ROBUSTNESS PROTOCOL] +## 2. Coding Guidelines & Architecture Standard -Debug Callback: Enable GL_DEBUG_OUTPUT and glDebugMessageCallback -to capture driver-level warnings and errors in real-time. +* **Header Guard Protocol:** `#pragma once` is mandatory for all headers. +* **Naming Conventions:** Use `snake_case` for variables, objects, and functions. Use `PascalCase` for classes and structs. +* **Comments Pattern:** Single-line comments must utilize `//`. Multi-line blocks or logic structural boundaries must use standard `/* ... */` formatting. +* **Type Assertions:** Avoid using the `auto` keyword to preserve strict type transparency across the pipeline. +* **UI Mappings:** Divide the screen layout into distinct functional sub-windows using `glViewport`, `glScissor`, and `glEnable(GL_SCISSOR_TEST)`. + * **Left Hand Panel (35% Width):** Displays textual descriptions of the selected scope, interactive control titles, and mapped key layouts. Regular text is rendered in white, control titles are rendered in red, and hotkeys are rendered in pink. + * **Right Hand Panel (Main Area):** Houses the active circular or vector radar scope display over a pitch-black background. + * **Right Hand Lower Panel (15% Height):** Serves as a diagnostic status bar displaying maximum range, active cursor metrics, and scope identification written in yellow text. -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 +## 3. Target Ingestion Pipeline (`traffic_cop`) -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 +Targets enter the simulator from an external tracking network or an internal simulation table managed in PostgreSQL. All elements converge into a uniform memory footprint on a dedicated background execution thread called the `traffic_cop`. -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. +### Data Models -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: +```cpp +enum class TargetType : int { + VESSEL = 0, + AIRCRAFT = 1 +}; 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 - 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 - TargetType type; // type of target; vessel or aircraft + double target_longitude; + double target_latitude; + std::string vessel_name; // Null if unavailable + std::string registration; // Null if unavailable + float length; // Meters + float beam; // Meters + int vessel_type; // AIS or aircraft code type + uint32_t mmsi; // AIS unique ID / ICAO hex for planes + float course; // Over-ground heading in degrees from True North + float speed; // Over-ground speed in knots + time_t timestamp; // Aging tracker for clearing dead nodes + float altitude; // Meters (Fixed to 0 for surface targets) + TargetType type; }; -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) - time_t timestamp; // Data aging tracking identifier +To optimize memory bandwidth on the Radeon 780M GPU, the traffic_cop packs raw telemetry down into a lightweight structure passed directly into a standard OpenGL Shader Storage Buffer Object (SSBO) with a layout(std430) alignment.C++struct target_data_to_shader_structure { + float target_x; // Local Cartesian offset East (meters from radar origin) + float target_y; // Local Cartesian offset North (meters from radar origin) + float length; // Vessel length bounding box size (meters) + float beam; // Vessel beam bounding box size (meters) + float course; // Heading vector in radians from True North + float altitude; // Target altitude profile (meters) + long long timestamp;// Data alignment padding field }; - -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 a batch operation precisely when the PPI radar sweep line crosses 0.0 radians (True North). -2. Range Filtering: Discard any targets residing outside the active radar's designated maximum operational range. -3. Altitude Restriction: Enforce a strict <= 40-meter restriction for marine nodes (Marine Chain Home - and all PPI Marine radars). Discard any aircraft violating this ceiling. -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. 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. - -Note that the construction of the simulator will be discussed 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-mile 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-mile 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 miles. 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-Mile Markers: Chain Home used crystal-controlled oscillators to create - fixed reference "pips" every 20 miles. 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 miles; marker pips every 0.25 miles - 2. 3.0 miles; marker pips every 0.5 miles - 3. 6.0 miles; marker pips every 1.0 miles - 4. 12.0 miles; marker pips every 2.0 miles - - 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. - - This is suggested example psudeo code for the target shader - for both the radar equation as well as the blooming - - 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 - - -// Suggested Fragment Shader snippet for a single radar return -// Please note that this is for the frag shader for each of -// the ppi radars -uniform float user_gain; // 0.0 to 1.0 -uniform float selected_range; // e.g., 6.0 Nautical Miles - -// Target attributes passed in or looked up from a texture/buffer -struct RadarTarget { - float range; // Distance from radar center - float azimuth; // Angle in radians - float RCS; // Radar Cross Section -}; - -void main() { - // Current fragment coordinates in Polar space - float frag_range = current_fragment_range(); - float frag_azimuth = current_fragment_azimuth(); - - // 1. Calculate raw received power via Radar Equation - float R4 = pow(RadarTarget.range, 4.0); - float P_r = (Pt * G_squared * lambda_squared * RadarTarget.RCS) / (pow(4.0 * 3.14159, 3.0) * R4 * L); - - // 2. Apply the 1960s Logarithmic IF Amplifier compression - float V_echo = log(1.0 + P_r * 1000.0); - float V_total = user_gain * V_echo; - - // 3. Dynamic Blooming Coefficients - // As V_total exceeds 1.0 (saturation), expand the search/paint radius - float saturation = max(0.0, V_total - 1.0); - - float dynamic_range_width = baseline_pulse_width + (0.05 * saturation); - float dynamic_azimuth_width = baseline_beamwidth + (0.02 * pow(saturation, 2.0)); - - // 4. Compute Distance from current fragment to the true target center - float d_range = abs(frag_range - RadarTarget.range); - float d_azimuth = abs(frag_azimuth - RadarTarget.azimuth); - - // 5. Evaluate a Gaussian blur shape using the bloomed dimensions - float radial_paint = exp(-pow(d_range / dynamic_range_width, 2.0)); - float azimuth_paint = exp(-pow(d_azimuth / dynamic_azimuth_width, 2.0)); - - float final_intensity = V_total * radial_paint * azimuth_paint; - - // Clamp to CRT max brightness - gl_FragColor = vec4(final_intensity * phosphor_color, 1.0); -} - ------------------------------------------------------------------------------------- - -===================================================================================== - -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. - - ===================================================================== - - 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. - - - ======================================================================= - -================================== - -End of discussion - -================================== - -settings.h file suggestions: - -/* +Ingestion ProtocolThread Isolation: The traffic_cop handles parsing, scaling, and double-buffering completely isolated on its own thread to prevent blocking the frame rendering pipeline.Precision Correction: Coordinate projections (converting Lat/Long maps to local relative meters) are performed on the CPU using double-precision calculations before passing parameters down as 32-bit floating-point numbers to protect against rounding distortions.Boundary & Ceiling Filters: Targets residing beyond the selected scope's maximum range are immediately culled. Marine installations enforce an absolute <= 40-meter altitude cap; any aircraft target exceeding this height is dropped.Synchronized Dispatch: The traffic_cop writes updates into the graphics pipeline using a std::mutex critical section precisely when the PPI radar line swings past 0.0 radians (True North).4. Operational Exhibit ScopesUsers step through the available scope list in a continuous loop using the forward (1) and backward (2) keys. Scopes reset to their baseline parameters upon entry.Exhibit Introduction: Displays static informational text on the left panel. The right view panel remains blank until navigation keys are pressed.Chain Home A-Scope (1940s): Simulated 1D electrostatic vector trace. Features an inverted top-down baseline. All video signals deflect downward. Includes crystal-controlled 20-mile reference pips and a wide $150^\circ$ floodlight footprint. Target azimuth coordinates are evaluated manually via a radiogoniometer search coil nulling control.Marine A-Scope (1940s): Represents early surface-search systems like the British Type 271. Features a bottom-up baseline trace showing signal power versus range. The antenna is rotated manually using a servo motor console knob to sweep for target peak voltages.Marine Traffic Control PPI Scope (1960s): Stationary shore installation tracking Bellingham Bay. Features a high-power $25\text{ kW}$ transmitter, a highly directional 12-foot linear waveguide array ($0.7^\circ$ horizontal beamwidth), and a slow 15 RPM rotation rate. The screen top is locked to True North.Shipborne Marine PPI Scope (1960s): Simulates a moving patrol boat or Coast Guard cutter. Features a $15\text{ kW}$ tactical transmitter, a 4-to-6-foot antenna ($1.2^\circ$ horizontal beamwidth), and a fast 25 RPM sweep. The screen top is locked to the vessel's bow, and the graticule ring dynamically adjusts using gyrocompass inputs.5. Shader Modularization StrategyThe rendering engine decomposes processing tasks across distinct, optimized shader sets to isolate raw signal generation from persistent phosphor simulations. [Target SSBO Data] & [GDAL Elevation Maps] + │ + ▼ + ┌──────────────────────────────────────────────────┐ + │ PASS A: Signal Generation & Terrain Shader Pass │ + │ (Computes Radar Equations, Blooming, & Clutter) │ + └────────────────────────┬─────────────────────────┘ + │ + ▼ [High-Precision Float FBO Texture] + ┌──────────────────────────────────────────────────┐ + │ PASS B: P7 Phosphor Integration & Decay Shader │ + │ (Applies Multistage Time-Based Exponential Fade) │ + └────────────────────────┬─────────────────────────┘ + │ + ▼ [Ping-Pong Swap Texture] + ┌──────────────────────────────────────────────────┐ + │ PASS C: Orthographic 2D UI & Graticule Overlay │ + │ (Applies Backlit Vector Rings and Text Overlays) │ + └──────────────────────────────────────────────────┘ +Pass A (Signal Generation & Terrain Pipeline): Evaluates the radar equation, target cross-sections, and landscape masks on a per-pixel basis. It writes output results directly into a high-precision floating-point Framebuffer Object (FBO) texture, preventing signal clipping and rounding artifacts.Pass B (P7 Phosphor Integration & Persistence Pass): Emulates vintage cathode-ray tube long-persistence phosphors. It blends the newly generated excitation texture from Pass A into a historical ping-pong frame buffer tracking multi-stage exponential decay.Pass C (Orthographic 2D UI & Graticule Overlay): Draws edge-lit vector rings, angular graduation marks, baseline coordinates, and text glyph textures directly onto screen coordinates.6. Complete Compilation Blueprint Natively for UbuntuCMakeLists.txtCMakecmake_minimum_required(VERSION 3.22) +project(RadarSimulator SYSTEM CXX) + +# Target configuration details: Mark Allyn, 2026 +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Enforce architectural optimizations for the Geekom A8 Max (Ryzen 9 8945HS) +if(CMAKE_COMPILER_IS_GNUCXX) + add_compile_options(-Wall -Wextra -O3 -march=native -pthread) +endif() + +# Find systemic hardware dependencies inside Ubuntu +find_package(PkgConfig REQUIRED) +pkg_check_modules(FREETYPE REQUIRED freetype2) +pkg_check_modules(GLFW3 REQUIRED glfw3) + +find_package(PostgreSQL REQUIRED) + +find_path(GDAL_INCLUDE_DIR gdal.h PATH_SUFFIXES gdal) +find_library(GDAL_LIBRARY NAMES gdal) + +if(NOT GDAL_LIBRARY OR NOT GDAL_INCLUDE_DIR) + message(FATAL_ERROR "GDAL structural development assets not found in ecosystem.") +endif() + +# Define the primary radar simulator executable target +add_executable(radar_simulator + main.cpp + settings.h +) + +# Explicit target layout inclusion bounds +target_include_directories(radar_simulator PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${FREETYPE_INCLUDE_DIRS} + ${GLFW3_INCLUDE_DIRS} + ${PostgreSQL_INCLUDE_DIRS} + ${GDAL_INCLUDE_DIR} +) + +# Core library synchronization +target_link_libraries(radar_simulator PRIVATE + ${GLFW3_LIBRARIES} + ${FREETYPE_LIBRARIES} + ${PostgreSQL_LIBRARIES} + ${GDAL_LIBRARY} + GL + dl + pthread +) +settings.hC++/* * MIT License - * * Copyright (c) 2026 Mark Allyn - * * Permission is hereby granted, free of charge, to any person obtaining a copy + * Copyright (c) 2026 Mark Allyn + * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * * The above copyright notice and this permission notice shall be included in all + * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. - * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. - * * Author: Mark Allyn + * Author: Mark Allyn */ #pragma once @@ -839,13 +217,13 @@ settings.h file suggestions: HARDWARE ENVIRONMENT MATRICES ========================================================================= */ namespace ChainHome { - const float PEAK_POWER = 500000.0f; /* 500 KW */ - const float WAVELENGTH = 12.0f; /* 12 Meters */ - const float ANTENNA_GAIN = 3.16f; /* 5 dB expressed as linear gain */ - const float PULSE_WIDTH = 0.000020f; /* 20 microseconds */ - const float BEAM_WIDTH = 150.0f; /* floodlight sector in degrees */ - const float PRF = 25.0f; /* Pulse Repetition Frequency (Hz) */ - const float MAX_RANGE_METERS = 321868.0f; /* Fixed 200 Miles */ + const float PEAK_POWER = 500000.0f; /* 500 KW */ + const float WAVELENGTH = 12.0f; /* 12 Meters */ + const float ANTENNA_GAIN = 3.16f; /* 5 dB expressed as linear gain */ + const float PULSE_WIDTH = 0.000020f; /* 20 microseconds */ + const float BEAM_WIDTH = 150.0f; /* floodlight sector in degrees */ + const float PRF = 25.0f; /* Pulse Repetition Frequency (Hz) */ + const float MAX_RANGE_METERS = 321868.0f; /* Fixed 200 Miles */ } namespace MarineAScope { @@ -868,6 +246,7 @@ namespace MarineTrafficPPI { const float HORIZONTAL_BEAMWIDTH = 0.7f; /* 0.7 degrees razor-sharp */ const float PULSE_WIDTH = 0.1e-6f; /* 0.1 microseconds */ const float ANTENNA_RPM = 15.0f; /* 15 RPM mechanical rotation rate */ + const float ANTENNA_GAIN = 2511.89f; /* 34 dB linear converter multiplier */ } namespace MarineBoatPPI { @@ -879,4 +258,803 @@ namespace MarineBoatPPI { const float HORIZONTAL_BEAMWIDTH = 1.2f; /* 1.2 degrees */ const float PULSE_WIDTH = 0.1e-6f; /* 0.1 microseconds */ const float ANTENNA_RPM = 25.0f; /* 25 RPM high-frequency sweep */ + const float ANTENNA_GAIN = 1258.93f; /* 31 dB linear converter multiplier */ +} +main.cppC++/* + * MIT License + * Copyright (c) 2026 Mark Allyn + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * Author: Mark Allyn + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "settings.h" + +enum class TargetType : int { VESSEL = 0, AIRCRAFT = 1 }; +enum class ScopeType { EXHIBIT_INTRO = 0, CHAIN_HOME_A_SCOPE = 1, MARINE_A_SCOPE = 2, MARINE_TRAFFIC_PPI = 3, MARINE_BOAT_PPI = 4 }; + +struct target_data_to_shader_structure { + float target_x; + float target_y; + float length; + float beam; + float course; + float altitude; + long long timestamp; +}; + +struct SharedTrafficBuffer { + std::mutex data_mutex; + std::vector active_targets; + std::atomic current_sweep_radians{0.0f}; +}; + +struct ScopeState { + float intensity = 0.8f; + float receiver_sensitivity = 0.5f; + float stc_sensitivity = 0.3f; + float stc_range = 0.4f; + float noise_filter_rain = 0.2f; + float radiogoniometer_deg = 0.0f; + float max_range_setting = 12.0f; + float range_cursor = 2.0f; + float bearing_cursor_deg = 0.0f; + float antenna_bearing_deg = 0.0f; +}; + +struct SystemController { + ScopeType current_scope = ScopeType::EXHIBIT_INTRO; + std::vector available_scopes; + bool navigation_enabled = true; + size_t active_scope_index = 0; + ScopeState states[5]; + SharedTrafficBuffer thread_bridge; + std::atomic run_thread_execution{true}; + + /* Persistent Phosphor Accumulation FBO Handlers */ + GLuint phosphor_fbo[2] = {0, 0}; + GLuint phosphor_texture[2] = {0, 0}; + int current_fbo_index = 0; + int fbo_width = 1024; + int fbo_height = 1024; +}; + +static SystemController g_SystemContext; + +void APIENTRY glDebugOutputCallback(GLenum source, GLenum type, GLuint id, GLenum severity, + GLsizei length, const GLchar* message, const void* userParam) { + (void)source; (void)id; (void)length; (void)userParam; + if (type == GL_DEBUG_TYPE_ERROR) { + std::cerr << "[GPU RUNTIME CRITICAL ERROR]: " << message << std::endl; + } +} + +void traffic_cop_thread_loop() { + while (g_SystemContext.run_thread_execution.load()) { + std::this_thread::sleep_for(std::chrono::milliseconds(16)); + float current_sweep = g_SystemContext.thread_bridge.current_sweep_radians.load(); + + /* Sync Batch Dispatch Trigger precisely when crossing True North (0.0 rad) */ + if (std::abs(current_sweep) <= 0.05f) { + std::lock_guard lock(g_SystemContext.thread_bridge.data_mutex); + g_SystemContext.thread_bridge.active_targets.clear(); + + target_data_to_shader_structure mock_freighter; + mock_freighter.target_x = 2200.0f; + mock_freighter.target_y = 4100.0f; + mock_freighter.length = 210.0f; + mock_freighter.beam = 32.0f; + mock_freighter.course = 0.785f; + mock_freighter.altitude = 0.0f; + mock_freighter.timestamp = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + + /* Altitude Ceiling Filter Validation Rules */ + if (mock_freighter.altitude <= 40.0f) { + g_SystemContext.thread_bridge.active_targets.push_back(mock_freighter); + } + } + } +} + +void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { + (void)window; (void)scancode; (void)mods; + if (action != GLFW_PRESS && action != GLFW_REPEAT) return; + + size_t active_idx = static_cast(g_SystemContext.current_scope); + ScopeState& state = g_SystemContext.states[active_idx]; + + switch (key) { + case GLFW_KEY_1: + if (g_SystemContext.navigation_enabled && !g_SystemContext.available_scopes.empty()) { + g_SystemContext.active_scope_index = (g_SystemContext.active_scope_index + 1) % g_SystemContext.available_scopes.size(); + g_SystemContext.current_scope = g_SystemContext.available_scopes[g_SystemContext.active_scope_index]; + } + break; + case GLFW_KEY_2: + if (g_SystemContext.navigation_enabled && !g_SystemContext.available_scopes.empty()) { + if (g_SystemContext.active_scope_index == 0) g_SystemContext.active_scope_index = g_SystemContext.available_scopes.size() - 1; + else g_SystemContext.active_scope_index--; + g_SystemContext.current_scope = g_SystemContext.available_scopes[g_SystemContext.active_scope_index]; + } + break; + case GLFW_KEY_3: state.intensity = std::max(0.0f, state.intensity - 0.05f); break; + case GLFW_KEY_4: state.intensity = std::min(1.0f, state.intensity + 0.05f); break; + case GLFW_KEY_5: state.receiver_sensitivity = std::max(0.0f, state.receiver_sensitivity - 0.05f); break; + case GLFW_KEY_6: state.receiver_sensitivity = std::min(1.0f, state.receiver_sensitivity + 0.05f); break; + case GLFW_KEY_Q: state.stc_sensitivity = std::max(0.0f, state.stc_sensitivity - 0.05f); break; + case GLFW_KEY_W: state.stc_sensitivity = std::min(1.0f, state.stc_sensitivity + 0.05f); break; + case GLFW_KEY_E: state.stc_range = std::max(0.0f, state.stc_range - 0.05f); break; + case GLFW_KEY_R: state.stc_range = std::min(1.0f, state.stc_range + 0.05f); break; + + /* Master Sync for Dual Map Controls */ + case GLFW_KEY_T: + case GLFW_KEY_J: state.noise_filter_rain = std::max(0.0f, state.noise_filter_rain - 0.05f); break; + case GLFW_KEY_Y: + case GLFW_KEY_K: state.noise_filter_rain = std::min(1.0f, state.noise_filter_rain + 0.05f); break; + + case GLFW_KEY_U: state.radiogoniometer_deg = std::fmod(state.radiogoniometer_deg - 2.0f + 360.0f, 360.0f); break; + case GLFW_KEY_I: state.radiogoniometer_deg = std::fmod(state.radiogoniometer_deg + 2.0f, 360.0f); break; + case GLFW_KEY_O: state.max_range_setting = std::max(1.5f, state.max_range_setting - 1.5f); break; + case GLFW_KEY_P: state.max_range_setting = std::min(48.0f, state.max_range_setting + 1.5f); break; + case GLFW_KEY_A: state.range_cursor = std::max(0.0f, state.range_cursor - 0.1f); break; + case GLFW_KEY_S: state.range_cursor = std::min(state.max_range_setting, state.range_cursor + 0.1f); break; + case GLFW_KEY_D: state.bearing_cursor_deg = std::fmod(state.bearing_cursor_deg - 1.0f + 360.0f, 360.0f); break; + case GLFW_KEY_F: state.bearing_cursor_deg = std::fmod(state.bearing_cursor_deg + 1.0f, 360.0f); break; + case GLFW_KEY_G: state.antenna_bearing_deg = std::fmod(state.antenna_bearing_deg - 1.0f + 360.0f, 360.0f); break; + case GLFW_KEY_H: state.antenna_bearing_deg = std::fmod(state.antenna_bearing_deg + 1.0f, 360.0f); break; + case GLFW_KEY_ESCAPE: glfwSetWindowShouldClose(window, GLFW_TRUE); break; + } +} + +void allocate_phosphor_storage_buffers() { + glGenFramebuffers(2, g_SystemContext.phosphor_fbo); + glGenTextures(2, g_SystemContext.phosphor_texture); + + for (int i = 0; i < 2; ++i) { + glBindFramebuffer(GL_FRAMEBUFFER, g_SystemContext.phosphor_fbo[i]); + glBindTexture(GL_TEXTURE_2D, g_SystemContext.phosphor_texture[i]); + + /* High Precision Floating Point Textures required to eliminate quantization errors during continuous integration fade steps */ + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, g_SystemContext.fbo_width, g_SystemContext.fbo_height, 0, GL_RGBA, GL_FLOAT, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, g_SystemContext.phosphor_texture[i], 0); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + std::cerr << "[FBO CORE ERROR]: High precision accumulation texture link failure." << std::endl; + } + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +int main(int argc, char* argv[]) { + std::vector args(argv + 1, argv + argc); + if (args.empty()) { + g_SystemContext.available_scopes = { ScopeType::EXHIBIT_INTRO, ScopeType::CHAIN_HOME_A_SCOPE, ScopeType::MARINE_A_SCOPE, ScopeType::MARINE_TRAFFIC_PPI, ScopeType::MARINE_BOAT_PPI }; + } else if (args.size() == 1) { + g_SystemContext.navigation_enabled = false; + std::string name = args[0]; + if (name == "chain_home") g_SystemContext.available_scopes.push_back(ScopeType::CHAIN_HOME_A_SCOPE); + else if (name == "marine_ascope") g_SystemContext.available_scopes.push_back(ScopeType::MARINE_A_SCOPE); + else if (name == "marine_traffic_ppi") g_SystemContext.available_scopes.push_back(ScopeType::MARINE_TRAFFIC_PPI); + else if (name == "marine_boat_ppi") g_SystemContext.available_scopes.push_back(ScopeType::MARINE_BOAT_PPI); + } else { + g_SystemContext.available_scopes.push_back(ScopeType::EXHIBIT_INTRO); + for (const auto& name : args) { + if (name == "chain_home") g_SystemContext.available_scopes.push_back(ScopeType::CHAIN_HOME_A_SCOPE); + else if (name == "marine_ascope") g_SystemContext.available_scopes.push_back(ScopeType::MARINE_A_SCOPE); + else if (name == "marine_traffic_ppi") g_SystemContext.available_scopes.push_back(ScopeType::MARINE_TRAFFIC_PPI); + else if (name == "marine_boat_ppi") g_SystemContext.available_scopes.push_back(ScopeType::MARINE_BOAT_PPI); + } + } + g_SystemContext.current_scope = g_SystemContext.available_scopes[0]; + + std::thread traffic_cop_thread(traffic_cop_thread_loop); + + if (!glfwInit()) return -1; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + GLFWwindow* window = glfwCreateWindow(1920, 1080, "Historical Radar Simulator Exhibit", nullptr, nullptr); + if (!window) { glfwTerminate(); return -1; } + glfwMakeContextCurrent(window); + glfwSetKeyCallback(window, key_callback); + gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); + +#if GL_DEBUG_OUTPUT_ENABLED + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(glDebugOutputCallback, nullptr); +#endif + + allocate_phosphor_storage_buffers(); + + while (!glfwWindowShouldClose(window)) { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + /* Base System Reset */ + glScissor(0, 0, width, height); + glDisable(GL_SCISSOR_TEST); + glClearColor(0.01f, 0.01f, 0.01f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_SCISSOR_TEST); + + /* VIEWPORT 1: Left Control Layout Panel */ + int left_w = static_cast(width * 0.35f); + glViewport(0, 0, left_w, height); + glScissor(0, 0, left_w, height); + glClearColor(0.04f, 0.04f, 0.04f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + /* Execute FreeType UI text pass here */ + + /* VIEWPORT 2: Main Operational Scope Unit */ + int scope_x = left_w; + int scope_w = width - left_w; + int status_h = static_cast(height * 0.15f); + int scope_h = height - status_h; + + /* P7 Ping-Pong Interception Loop logic for active PPI installations */ + if (g_SystemContext.current_scope == ScopeType::MARINE_TRAFFIC_PPI || g_SystemContext.current_scope == ScopeType::MARINE_BOAT_PPI) { + int read_idx = g_SystemContext.current_fbo_index; + int write_idx = 1 - read_idx; + + /* Bind write target */ + glBindFramebuffer(GL_FRAMEBUFFER, g_SystemContext.phosphor_fbo[write_idx]); + glViewport(0, 0, g_SystemContext.fbo_width, g_SystemContext.fbo_height); + glScissor(0, 0, g_SystemContext.fbo_width, g_SystemContext.fbo_height); + + /* Pass B: Draw raw physical excitation sweep signals directly into the persistent layer */ + /* Pass C: Bind texture[read_idx], apply mathematical exponential decay delta inside the blend shader */ + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + g_SystemContext.current_fbo_index = write_idx; // Cycle the buffers + + /* Draw completed phosphor frame onto presentation screen coordinates */ + glViewport(scope_x, status_h, scope_w, scope_h); + glScissor(scope_x, status_h, scope_w, scope_h); + /* Draw raw texture bounds across simple screen-space quad geometry */ + } else { + /* Standard A-Scope direct rendering pass track bypassing the texture matrix entirely */ + glViewport(scope_x, status_h, scope_w, scope_h); + glScissor(scope_x, status_h, scope_w, scope_h); + /* Direct A-Scope geometric stream lines rendering code execution loop */ + } + + /* VIEWPORT 3: Yellow Diagnostic Status Bar */ + glViewport(scope_x, 0, scope_w, status_h); + glScissor(scope_x, 0, scope_w, status_h); + glClearColor(0.02f, 0.02f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + /* Diagnostic status outputs rendering step */ + + /* Advance mechanical radar line variables smoothly */ + float step = 0.012f; + if (g_SystemContext.current_scope == ScopeType::MARINE_BOAT_PPI) step = 0.022f; // Fast boat scan + float updated = g_SystemContext.thread_bridge.current_sweep_radians.load() + step; + if (updated >= 6.2831853f) updated = 0.0f; + g_SystemContext.thread_bridge.current_sweep_radians.store(updated); + + glfwSwapBuffers(window); + glfwPollEvents(); + } + + g_SystemContext.run_thread_execution.store(false); + traffic_cop_thread.join(); + glfwTerminate(); + return 0; +} + +7. Mathematical & Logical Shader Set Specifications (GLSL Target Mapping) +Set 1 & 2: Text Rendering System (FreeType Font Pipeline) +OpenGL Shading Language + +// VERTEX SHADER +#version 430 core +layout(location = 0) in vec4 vertex; // [pos.x, pos.y, tex.s, tex.t] +out vec2 v_TexCoords; +uniform mat4 u_OrthographicProjection; + +void main() { + v_TexCoords = vertex.zw; + gl_Position = u_OrthographicProjection * vec4(vertex.xy, 0.0, 1.0); +} + +// FRAGMENT SHADER +#version 430 core +in vec2 v_TexCoords; +out vec4 frag_color; +uniform sampler2D u_TextGlyphTexture; +uniform vec3 u_TextRGBUniform; // Passed from color macros (#FFFFFF, #FF0000, etc.) + +void main() { + // Extract mono-channel mask information from the FreeType slot + float glyph_alpha = texture(u_TextGlyphTexture, v_TexCoords).r; + if (glyph_alpha < 0.05) discard; + + frag_color = vec4(u_TextRGBUniform, glyph_alpha); +} + +Set 3: Chain Home Radar A-Scope Targets (Inverted Vector Generator) +OpenGL Shading Language + +// VERTEX SHADER +#version 430 core +/* Generates a continuous line-strip representing physical voltage deflection */ +layout(location = 0) in float in_range_miles; +out float v_signal_amplitude; + +// Sync block data passback +layout(std430, binding = 0) buffer TargetBuffer { + struct TargetData { + float target_x; float target_y; + float length; float beam; + float course; float altitude; long long ts; + } targets[]; +}; + +uniform int u_target_count; +uniform float u_sensitivity; +uniform float u_radiogoniometer_rad; // Changed via U/I keys +const float ch_wavelength = 12.0; + +// Pseudo-random high-frequency noise loop for 25 MHz atmospheric grass simulation +float generate_atmospheric_jitter(float seed) { + return fract(sin(seed * 12.9898) * 43758.5453) * 0.06; +} + +void main() { + float total_echoes = 0.0; + float range_meters = in_range_miles * 1609.34; + + for(int i = 0; i < u_target_count; ++i) { + float t_range = sqrt(targets[i].target_x * targets[i].target_x + targets[i].target_y * targets[i].target_y); + + // 1. Evaluate range coincidence window + if (abs(range_meters - t_range) < 1500.0) { + float t_bearing = atan(targets[i].target_x, targets[i].target_y); + float aspect_angle = t_bearing - targets[i].course; + float proj_width = abs(sin(aspect_angle)) * targets[i].length + abs(cos(aspect_angle)) * targets[i].beam; + float sigma = proj_width * 2.5; + + // 2. Metric Resonance Multiplier Rule + if (targets[i].length >= (ch_wavelength * 0.4) && targets[i].length <= (ch_wavelength * 0.6)) { + sigma *= 1.5; + } + + // 3. Radiogoniometer Search Coil Nulling calculation + // Null point occurs when search coil is perpendicular to signal wavefront + float angular_null_factor = abs(cos(t_bearing - u_radiogoniometer_rad)); + + // 4. Calculate Radar Equation Received Power Pr + float P_r = (500000.0 * pow(3.16, 2.0) * pow(ch_wavelength, 2.0) * sigma * angular_null_factor) / + (pow(4.0 * 3.14159, 3.0) * pow(t_range, 4.0)); + + total_echoes += P_r * 2e11; // Conversion factor into vertical deflection voltage + } + } + + // 5. Injected Crystal Calibration Pips (Every 20 Miles) + float calibration_pip = 0.0; + if (mod(in_range_miles + 0.1, 20.0) < 0.3) { + calibration_pip = 0.25; // Fixed invariant height spike + } + + // 6. Summing Amplifier Protocol Mapping Downward + float noise_grass = generate_atmospheric_jitter(in_range_miles); + float receiver_signal = (noise_grass + total_echoes) * u_sensitivity; + + // Total voltage pushes the trace down from the upper ceiling grid (y = 0.9) + float final_y = 0.9 - (receiver_signal + calibration_pip); + + // Normalized device coordinates map range miles horizontally from left (-0.9) to right (0.9) + float final_x = -0.9 + (in_range_miles / 200.0) * 1.8; + + gl_Position = vec4(final_x, final_y, 0.0, 1.0); + v_signal_amplitude = receiver_signal + calibration_pip; +} + +// FRAGMENT SHADER +#version 430 core +in float v_signal_amplitude; +out vec4 frag_color; + +void main() { + // Green mono-phosphor mixing matrix base + vec3 excitation_color = vec3(0.22, 1.0, 0.08); // #39FF14 + vec3 decay_base = vec3(0.0, 0.26, 0.0); // #004400 + + vec3 mixed_color = mix(decay_base, excitation_color, clamp(v_signal_amplitude * 4.0, 0.0, 1.0)); + frag_color = vec4(mixed_color, 0.9); +} + +Set 4: Marine Radar A-Scope Targets (Right-Side Up Centimetric Pipeline) +OpenGL Shading Language + +// VERTEX SHADER +#version 430 core +layout(location = 0) in float in_range_miles; +out float v_intensity; + +layout(std430, binding = 0) buffer TargetBuffer { + struct TargetData { + float target_x; float target_y; + float length; float beam; + float course; float altitude; long long ts; + } targets[]; +}; + +uniform int u_target_count; +uniform float u_sensitivity; +uniform float u_antenna_bearing_deg; // Rotated via G/H keys +uniform float u_max_range_miles; + +const float peak_power = 500000.0; +const float wavelength = 0.10; +const float antenna_gain = 1000.0; +const float horizontal_beamwidth = 2.5; + +float generate_thermal_noise(float r) { + // Standard high-frequency receiver thermal grass calculations + return fract(cos(r * 45.1232) * 91234.56) * 0.04; +} + +void main() { + float total_echoes = 0.0; + float range_meters = in_range_miles * 1852.0; // Nautical Mile scale conversion + + for (int i = 0; i < u_target_count; ++i) { + float t_range = sqrt(targets[i].target_x * targets[i].target_x + targets[i].target_y * targets[i].target_y); + + // Evaluate radar return pulse shape width envelopes + if (abs(range_meters - t_range) < 60.0) { + float t_bearing_rad = atan(targets[i].target_x, targets[i].target_y); + float t_bearing_deg = degrees(t_bearing_rad); + if (t_bearing_deg < 0.0) t_bearing_deg += 360.0; + + float angle_diff = abs(t_bearing_deg - u_antenna_bearing_deg); + if (angle_diff > 180.0) angle_diff = 360.0 - angle_diff; + + // Direct servo directional beam attenuation + float beam_factor = exp(-2.77 * pow(angle_diff / (horizontal_beamwidth / 2.0), 2.0)); + + float aspect = t_bearing_rad - targets[i].course; + float rcs = (abs(sin(aspect)) * targets[i].length + abs(cos(aspect)) * targets[i].beam) * 2.5; + + // Standard Marine Radar Equation + float Pr = (peak_power * pow(antenna_gain, 2.0) * pow(wavelength, 2.0) * rcs * beam_factor) / + (pow(4.0 * 3.14159, 3.0) * pow(t_range, 4.0)); + + // Pulse rise/fall envelope configuration shapes the curve + float offset = abs(range_meters - t_range) / 60.0; + float pulse_envelope = cos(offset * 1.5707); // Smooth bell output curve instead of a raw vertical line + + total_echoes += Pr * pulse_envelope * 1e8; + } + } + + // Dynamic calibration ticks based on range modes + float calibration_pip = 0.0; + float interval = (u_max_range_miles > 6.0) ? 2.0 : (u_max_range_miles > 2.0) ? 1.0 : 0.25; + if (mod(in_range_miles, interval) < 0.04) { + calibration_pip = 0.15; + } + + // Right-Side Up Addition Logic: Deflects upwards from bottom baseline floor (y = -0.8) + float grass = generate_thermal_noise(in_range_miles); + float receiver_signal = (grass + total_echoes) * u_sensitivity; + + float final_y = -0.8 + (receiver_signal + calibration_pip); + float final_x = -0.9 + (in_range_miles / u_max_range_miles) * 1.8; + + gl_Position = vec4(final_x, final_y, 0.0, 1.0); + v_intensity = receiver_signal + calibration_pip; +} + +// FRAGMENT SHADER +#version 430 core +in float v_intensity; +out vec4 frag_color; + +void main() { + frag_color = vec4(vec3(0.22, 1.0, 0.08) * (v_intensity * 5.0), 1.0); +} + +Set 5 & 6: PPI Processing Radar Return (Render-to-Texture Signal Generation Pass) +OpenGL Shading Language + +// VERTEX SHADER +#version 430 core +layout(location = 0) in vec2 in_position; // Coordinate space spanning [-1, 1] texture boundaries +out vec2 v_tex_coord; + +void main() { + v_tex_coord = in_position * 0.5 + 0.5; + gl_Position = vec4(in_position, 0.0, 1.0); +} + +// FRAGMENT SHADER +#version 430 core +in vec2 v_tex_coord; +out vec4 frag_excitation_energy; + +layout(std430, binding = 0) buffer TargetBuffer { + struct TargetData { + float target_x; float target_y; + float length; float beam; + float course; float altitude; long long ts; + } targets[]; +}; + +uniform int u_target_count; +uniform float u_gain; // 0.0 to 1.0 manual control knob +uniform float u_noise_filter_rain; // Linked to J/K or T/Y properties +uniform float u_active_sweep_rad; // Dynamic angular vector +uniform float u_max_range_meters; // Dynamic conversion threshold mapping scales +uniform int u_is_boat_radar; // 1 = Active configuration metrics for mobile patrol boat + +// Constants injected dynamically derived from the structural lookup namespaces +uniform float Pt; +uniform float G_squared; +uniform float lambda_squared; +uniform float L; +uniform float baseline_beamwidth; +uniform float baseline_pulse_width; + +float generate_sea_clutter(vec2 uv, float sweep_angle) { + // Computes heavy concentric sea clutter returns fading out at 1/R^3 near origin + float dist = length(uv - vec2(0.5)); + float clutter_noise = fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453); + return (clutter_noise * exp(-dist * 8.0) * 0.3) * (1.0 - u_noise_filter_rain); +} + +void main() { + vec2 polar_offset = v_tex_coord - vec2(0.5); + float frag_range = length(polar_offset) * u_max_range_meters; + float frag_azimuth = atan(polar_offset.x, polar_offset.y); // Derived clockwise relative to North + if (frag_azimuth < 0.0) frag_azimuth += 2.0 * 3.14159265; + + // 1. Evaluate sweep intersection envelope + float angular_distance = abs(frag_azimuth - u_active_sweep_rad); + if (angular_distance > 3.14159265) angular_distance = (2.0 * 3.14159265) - angular_distance; + + if (angular_distance > (baseline_beamwidth * 3.0 * 3.14159 / 180.0)) { + discard; // Optimize processing load pipeline safely + } + + float total_video_voltage = 0.0; + + for (int i = 0; i < u_target_count; ++i) { + float t_range = sqrt(targets[i].target_x * targets[i].target_x + targets[i].target_y * targets[i].target_y); + float t_azimuth = atan(targets[i].target_x, targets[i].target_y); + if (t_azimuth < 0.0) t_azimuth += 2.0 * 3.14159265; + + // Calculate aspect area cross-section curve profiles + float aspect = t_azimuth - targets[i].course; + float projected_width = (abs(sin(aspect)) * targets[i].length) + (abs(cos(aspect)) * targets[i].beam); + float sigma = projected_width * 2.5; + + // 2. Process Raw Radar Equation + float R4 = pow(t_range, 4.0); + if (R4 < 1.0) R4 = 1.0; + float P_r = (Pt * G_squared * lambda_squared * sigma) / (pow(4.0 * 3.14159, 3.0) * R4 * L); + + // 3. Logarithmic Compression Block + float V_echo = log(1.0 + P_r * 8000.0); + float V_total = u_gain * V_echo; + + // 4. Mathematical Target Saturation Blooming Formulas + float saturation = max(0.0, V_total - 1.0); + float dynamic_range_width = baseline_pulse_width + (0.06 * saturation * u_max_range_meters); + float dynamic_azimuth_width = (baseline_beamwidth * 3.14159 / 180.0) + (0.04 * pow(saturation, 2.0)); + + float d_range = abs(frag_range - t_range); + float d_azimuth = abs(frag_azimuth - t_azimuth); + if (d_azimuth > 3.14159) d_azimuth = (2.0 * 3.14159) - d_azimuth; + + // 5. Evaluate spatial envelope maps + float radial_paint = exp(-pow(d_range / dynamic_range_width, 2.0)); + float azimuth_paint = exp(-pow(d_azimuth / dynamic_azimuth_width, 2.0)); + + // Accumulate active arc signatures + total_video_voltage += V_total * radial_paint * azimuth_paint; + } + + // Mix raw environment components into receiver stack + float clutter = generate_sea_clutter(v_tex_coord, u_active_sweep_rad); + float final_excitation = (total_video_voltage + clutter) * exp(-pow(angular_distance / (baseline_beamwidth * 3.14159 / 180.0), 2.0)); + + frag_excitation_energy = vec4(vec3(final_excitation), 1.0); +} + +Set 7: Range Rings for PPI Radars (Concentric Vector Circles) +OpenGL Shading Language + +// FRAGMENT SHADER +#version 430 core +in vec2 v_tex_coord; // Interpolated coordinates +out vec4 frag_color; +uniform float u_max_range_miles; + +void main() { + float dist = length(v_tex_coord - vec2(0.5)); // Normalized range out from center axis + float range_miles = dist * u_max_range_miles; + + // Define interval patterns + float interval = (u_max_range_miles > 10.0) ? 4.0 : (u_max_range_miles > 4.0) ? 2.0 : 0.5; + float modulo_ring = mod(range_miles, interval); + + // High-precision ring edge generation using pixel derivatives + float ring_width = 0.015 * u_max_range_miles; + if (modulo_ring < ring_width && dist < 0.48) { + // Pilot bulb amber glow backlight (#FFB347) + frag_color = vec4(1.0, 0.701, 0.278, 0.4); + } else { + discard; + } +} + +Set 8: P7 Phosphor Persistence Accumulation Shader (The Decay Engine) +OpenGL Shading Language + +// FRAGMENT SHADER +#version 430 core +/* Processed over a screen-space quad reading the frame history loop */ +in vec2 v_tex_coord; +out vec4 frag_accumulated_out; + +uniform sampler2D u_tex_current_excitation; // Pass B layout output texture +uniform sampler2D u_tex_history_persistent; // Texture from read_idx index tracking +uniform float u_time_delta_seconds; // Time scale step track +uniform int u_disable_persistence_switch; // Hooked into DEBUG_DISABLE_P7_PERSISTENCE macro state + +void main() { + vec4 current_flash = texture(u_tex_current_excitation, v_tex_coord); + + if (u_disable_persistence_switch == 1) { + // Render raw instantaneous flash states cleanly bypassing afterglow tracking + if(current_flash.r > 0.02) { + frag_accumulated_out = vec4(0.627, 0.811, 1.0, 1.0); // Bright Blue excitation (#A0CFFF) + } else { + frag_accumulated_out = vec4(0.0, 0.0, 0.0, 1.0); + } + return; + } + + vec4 history = texture(u_tex_history_persistent, v_tex_coord); + + // 1. Ingest new signal values inside current execution pass frames + float normalized_energy = max(current_flash.r, history.r); + float alpha_age = history.a; + + if (current_flash.r > 0.05) { + alpha_age = 0.0; // Reset lifetime clock for newly excitation coordinate vectors + } else { + alpha_age += u_time_delta_seconds; // Chrono aging tracking step + } + + // 2. Map color states across normalized time deltas + vec3 physical_p7_rgb = vec3(0.0); + + if (alpha_age <= 0.001) { + physical_p7_rgb = vec3(0.627, 0.811, 1.0); // State 1: #A0CFFF (Excitation Blue) + } else if (alpha_age <= 0.01) { + physical_p7_rgb = vec3(0.062, 0.062, 1.0); // State 2: #1010FF (Immediate Blue core) + } else if (alpha_age <= 1.0) { + // Interpolate gracefully down into yellow green afterglow states + float factor = (alpha_age - 0.01) / 0.99; + physical_p7_rgb = mix(vec3(0.062, 0.062, 1.0), vec3(0.886, 1.0, 0.501), factor); // #E2FF80 + } else if (alpha_age <= 11.0) { + // Deep integration curve decay down into warm amber storage + float factor = (alpha_age - 1.0) / 10.0; + physical_p7_rgb = mix(vec3(0.886, 1.0, 0.501), vec3(1.0, 0.627, 0.25), factor); // #FFA040 + } else { + // Expiration threshold boundary mapping + float factor = clamp((alpha_age - 11.0) / 2.0, 0.0, 1.0); + physical_p7_rgb = mix(vec3(1.0, 0.627, 0.25), vec3(0.02, 0.027, 0.0), factor); // #050700 + if (alpha_age > 13.0) normalized_energy *= 0.92; // Systematically clear the tail from storage FBO + } + + frag_accumulated_out = vec4(physical_p7_rgb, normalized_energy); +} + +Set 9: Land and Terrain Radar Return (GDAL Matrix Ingestion) +OpenGL Shading Language + +// FRAGMENT SHADER +#version 430 core +in vec2 v_tex_coord; +out vec4 frag_terrain_power; + +uniform sampler2D u_gdal_elevation_map; // Packed height profile map parsed by CPU thread +uniform float u_active_sweep_rad; +uniform float u_max_range_meters; +uniform int u_disable_terrain_switch; // Hooked into DEBUG_DISABLE_TERRAIN macro state + +void main() { + if (u_disable_terrain_switch == 1) { + discard; + } + + vec2 polar_offset = v_tex_coord - vec2(0.5); + float frag_azimuth = atan(polar_offset.x, polar_offset.y); + if (frag_azimuth < 0.0) frag_azimuth += 2.0 * 3.14159265; + + // Direct aspect alignment check + if (abs(frag_azimuth - u_active_sweep_rad) > 0.02) { + discard; // Terrain coordinates are only rendered when intersected by the active beam sweep + } + + // Extract elevation maps metrics natively from GDAL texture layers + float elevation = texture(u_gdal_elevation_map, v_tex_coord).r; + + if (elevation > 0.0) { // Valid terrain coordinate target detected + // Land terrain structures return immense, saturating echo signatures + frag_terrain_power = vec4(vec3(1.8), 1.0); + } else { + discard; + } +} + +Set 10 & 11: Calibration Graticule & Base Lines (Incandescent Mechanical Faceplate Layer) +OpenGL Shading Language + +// VERTEX SHADER +#version 430 core +layout(location = 0) in vec2 in_vertex_pos; +uniform mat4 u_MVP_Matrix; +uniform float u_boat_heading_deg; // Rotates the graticule ring for Boat Radar mode +uniform int u_is_boat_mode; + +void main() { + vec2 rotated_pos = in_vertex_pos; + if (u_is_boat_mode == 1) { + float rad = radians(u_boat_heading_deg); + float cos_b = cos(rad); float sin_b = sin(rad); + rotated_pos.x = in_vertex_pos.x * cos_b - in_vertex_pos.y * sin_b; + rotated_pos.y = in_vertex_pos.x * sin_b + in_vertex_pos.y * cos_b; + } + gl_Position = u_MVP_Matrix * vec4(rotated_pos, 0.0, 1.0); +} + +// FRAGMENT SHADER +#version 430 core +out vec4 frag_graticule_color; +uniform vec3 u_incandescent_lamp_rgb; // Initialized to #FFB347 macro state value + +void main() { + // Renders the fine ticks, text rings, and A-scope baselines with a warm, steady edge-lit glow + frag_graticule_color = vec4(u_incandescent_lamp_rgb, 0.75); }