Files
updated-radar/CLAUDE.md
2026-04-21 09:06:10 -07:00

63 KiB
Raw Blame History

Introduction:

This is a project for a museum to demonstrate a simulation of a 1950's to 1960's vintage marine radar and air traffic control radar

The project will be implemented on a Geekom A8 Max with AMD AI chip R9-8945HS with 32 GB ram

Project: C++ OpenGL Radar Simulation

Environment: Ubuntu Linux (Remote SSH from Windows) Tech Stack: C++20, OpenGL 3.3 Core, GLFW, GLAD, FreeType

The operating system is Linux (Ubuntu) Details:

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

The compiler is cpp (Ubuntu 15.2.0-4ubuntu4) 15.2.0

We plan to use the cmake for building.

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

Here is the directory structure with files already installed: All directories are in the new-radar top level directory. The entire directory list is /home/maallyn/new-radar on the Geekom.

./shaders ./shaders/CLAUDE.md ./glad ./glad/src ./glad/src/glad.c ./include ./include/glad ./include/glad/glad.h ./include/CLAUDE.md ./include/KHR ./include/KHR/khrplatform.h ./new-claude ./README.md ./CMakeLists.txt ./build ./build/CLAUDE.md ./CLAUDE.md ./.new-claude.swp ./LICENSE ./src ./src/CLAUDE.md

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

GENERAL STUFF

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

Please note that all on-screen text shall be white and fully illuminated and is not subject to phosphor persistence or decay.

Exceptions:

Graticule text: should be incandescent for the bearing marks. Graticule text for all a-scope should be incandescent, not white and not phosphor as they are dependent on glass graticules with etched lines and text.

PPI Scope Range Ring Markers Text on the PPI scope range rings shall be blue fade to yellow green as on p7 phosphor. Which is the same for ppi targets.

Please note that direction as stated here are True directions. 000 is True North

Maximum Range is 6 miles for marine type radar Maximum Range is 20 miles for air traffic control radar. Maximum Range is 100 miles for chain home Maximum Range 10 miles for precision approach radar; graticule is incandescent showing the azimuth path and elevation path as described below in PAR description. Those graticules are etched glass for minimal parallax

The proposed location of the marine radar antenna is in the middle of Bellingham Bay on a 100 foot platform. (This should be mentioned as fictitious in the description) Location is 48.74361448950435 latitude, -122.56466911663048 longitude

The proposed location of the air traffic control radar is the Bellingham airport (BLI) control tower. Latitude: 48° 47' 33.7" N ; Longitude: 122° 32' 15.1" W

The proposed location for the chain home would be at the original location on the UK coast facing the European Continent

The following types of scope will be used; (note that these are not all going to show at once. They will be selectable using a push button (a letter on the keyboard until I get physical buttons that are connected to a gpio pin. The selection key should be s (short for scope)

Please note that all keyboard based controls need to be described in each scope's left hand text panel. These are different for each scope. Note that the s for selecting a scope should be in each scope's description and what would the next scope be.

Also note that when the radar exhibit starts, the very first option will be on the screen. Then the screen will advance through the scopes by two means; the pressing of the s key by the user, or automatically at every 120 seconds. You will need to emphasize in the first description that you can advance without waiting for the automatic advancing by pressing the s key. You can reverse by hitting the S key (upper case s) This should be articulated for the description window for each scope. When the main exhibit descriptor screen comes up, it's important to highlight the feature that the user can press the s key or the S key any time to 'hurry up' the scope advancement.

Also ensure that the timeout clock will reset when the user changes to a new scope, or presses any key or operates any control on the panel. This should be articulated in the descriptive text

  1. Exhibit introduction - a text block describing the exhibit and the basics on how to use it and what you are seeing. This should be text only. Top would be in all caps, "WELCOME TO MUSEUM VINTAGE RADAR EXHIBIT"

  2. Marine A-Scope - (horizontal axis is range; vertical axis is amplitude of return pulse; bearing will be set via a bearing control; current implementation would be two keys on the keyboard; one key to go clockwise on bearing and another key would be to go counterclockwise. The A scope phosphor is P1, which is green. The c key for clockwise on a scope and v for counterclockwise. The step rate for this control, before the knob is implemented would be one or two degrees per key press, but if the key is held down, it would increase slowly due to how long the key is depressed

    The A scope graticule is manually swapped out at each maximum range value by the operator during the period. Here we will have to fake it out. And that graticule needs to have an incandescent color. That graticule will have three horizontal graticule lines for estimating return pulse strength. The range lines (vertical lines) must match the interim and final ranges as selected by the max range selection To change maximum range, use key u for up and d for down. Possible settings are 2,4,6 miles; this must be noted clearly on the description text.

    Max is 2; one interim range at 1 Max is 4; one interim range at 2 Max is 6; one interim range at 4

    In addition to the blips for targets, there would be a floor of noise (signal received by rain and waves. This needs to be shown.

    Graticule swap simulation: In the period, changing maximum range required the operator to physically slide the glass graticule panel upward and out from in front of the CRT, then slide the replacement graticule (calibrated for the new range) downward into position. This must be simulated when the operator presses u or d to change range.

    The graticule swap animation uses four states: NORMAL - graticule in place, scope operating normally SLIDING_OUT - old graticule translates upward off screen (~0.5 seconds) BARE_CRT - no graticule visible; CRT trace and noise floor still running SLIDING_IN - new graticule (correct lines for new range) slides down into position (~0.5 seconds) After SLIDING_IN completes, state returns to NORMAL with the new range active. The u and d keys are ignored during the swap animation (operator's hands are busy). The graticule remains incandescent color throughout — it is edge-lit glass.

  3. Chain Home A Scope There is a second use of the a-scope. That is for the early world war 2 chain home radar. This operated very differently. You have a large array of high power transmitters 'floodlighting' the target area (in World War 2, that would be the English Channel. Since we don't care about land reflections with the original chain home setup was facing the English Channel, we can tell visitors that this radar is set at the English Channel (do this explanation on the explanation side panel for this radar mode. And for simulating operator using this radar, there would be two controls, one for the 'nulling the signal at the correct direction; simulating the behavior of the goniometer and the other for using the goniometer for elevation. For museum accuracy, we need to simulate the sharp 'null' when the goniometer is at the direction of the signal. This concept needs to be covered in the description text thoroughly as this is a bit advanced. I need your advice on how to do this for children and those who never heard of chain home.

    The goniometer vertical and horizontal switch could be key [ for toggling. The goniometer tuning would be 9 and 0 to avoid using the shift key. The tuning keys should have one unit for single press, but a slow build of of speed if key is held down. This has to stay slow due to the sudden appearance of the null.

    Targets for Chain Home would all have to be simulated as there will be no ais nor ads-b. Simulations would show several aircraft approaching the radar in many different directions and ranges. The museum visitor for exercise could try to sort out the targets by range and bearing and elevation by the nulling procedure noted above as well as the distance of the pulse from the origin.

    The graticule is etched glass (side lit with incandescent lights) with 10 mile markers for range (horizontal axis). There are no vertical markers; the signal strength value is not important. The only vertical value that is important is the nulling of the signal based on bearing and elevation from the manipulation of the goniometer.

    The refresh rates for chain home were slow in order to avoid aliasing with targets far away, the pulse repetition frequency (PRF) is about 25 times per second. This rate is 1/2 of the standard 50 Hz for British power.

    The operator did have a switch to switch from the 25 pulses per second PRF to 12.5 pulses per second PRF so that they could help eliminate the range ambiguity problem, where a target far away could appear to be right on site since that echo would return at the precise time for the next pulse to go out at 25 PRF. This needs to be explained in the explainer window for the chain home. Mention that mountains or planes in the continent could have that kind of range. Furthermore, the operator can reduce the PRF in order to reduce confusion caused by other radio transmissions such as press-to-talk communications transmissions.

    Let's assign key . for toggling between 25 and 12.5 PRF. There is no range selection. Note on description; this is to reduce use of the shift key.

    Because of the slow repetition rate, the phosphor used was an early implementation of the p7 phosphor so that the targets will still glow between the sweeps and not cause flickering.

    Another unique feature would be a response to the drifting problem in early electronics. The scope electronics would use a crystal calibrator that puts tiny pips or spikes at known intervals (10 miles). The operator would use a knob, or control, to stretch or shrink the electronic trace so that the 10 mile pips align perfectly with the 10 mile marks on the edge lit glass graticule.

    Let's assign key n for shrink and m for stretch. (may be ambiguous, but I am running out of keys. Note in the descriptor.

  4. Marine PPI Scope - marine scopes have the following items in common: Targets, range rings, and range ring text levels shall be treated the same for presentation. All are P7 phosphor. Immediate strike by the electron beam is blue. persistence is green/yellow. Targets, range rings, and range ring labels shall all persist and fade out together. They should be faded out by the time the sweep to that location.

     The maximum range settings are 6 miles for the marine radar scope 
     Rings should be 2,4, and 6 miles for marine.
     The max range settings for marine ppi will be u for up and d for
     down. If  you are in the marine ppi, you change only the max range for the marine
     ppi. The possible max range values for
     the marine radar are 2,4,6 
     miles.
    
     Marine:
     Max is 2; one interim range at 1, final ring at 2
     Max is 4; one interim range at 2, final ring at 4
     Max is 6; one interim range at 4, final ring at 6
    
     Note on range. If cursor range is beyone max, clamp it to the max.
    
     Bear in mind that the max range setting is independent for both radars. 
    
     The bearing graticule (lit incandescent) There should
     be an inner circle with tickmarks for each degree, starting at 0 (north) and going
     clockwise to the last tick, which is 359. Outside the inner ring shall be text
     labels for every 15 degrees. Outside the text labels, there will be
     an outer ring. Both inner and outer rings, along with ticks, and the bearing
     labels are to be incandescent color.
    
     The sweep time shall be 4 seconds for the marine scope 
    
     The sweep direction is clockwise, which means that the antenna
     dish rotates clockwise.
    
     The scope has a cursor for range and bearing. The cursor consists of a
     section of a ring ( 10 degrees) and a cross line for bearing. 
     The cursor should be yellow (it
     a plastic overlay in the period time. Two controls control the  cursor; range and
     bearing. Both were physical crank controls. For now, both we need to use key pairs
     on the keyboard. A white text indication of range and bearing should be put under 
     the scope. In the real day, it was a machanical readout. The key sequence would be
     r for bearing to the right and l for bearing for the left; and t for higher range
     and y for smaller range. These controls should have slow movement for single stroke; but
     gradual for for holding key down.
    
  5. Air Traffic PPI Scope - Targets, range rings, and range ring text levels All are P7 phosphor. Immediate strike by the electron beam is blue. persistence is green/yellow. Targets, range rings, and range ring labels shall all persist and fade out together. They should be faded out by the time the sweep to that location.

     Rings should be 5,10,15,20 for the air traffic control radar. 
     The max range settings for air ppi will be u for up and d for
     down. Use of these controls affect only the scope  you are in. No other scopes are
     affected. 
     The ranges for air traffic control radar are 5,10,15,20
     miles.
    
     Air Traffic Control:
     Max is 5; one interim range; two total; rings at 2.5; final ring at 5
     Max is 10; four interim ranges, five total; 2,4,6,8; final ring at 10
     Max is 15; three interim ranges four total; 4,8,12; final ring at 15
     Max is 20, three interim ranges four total; 5,10,15; final ring at 20
    
     Note on range. If cursor range is beyone max, clamp it to the max.
    
     Bear in mind that the max range setting is independent for both radars. 
    
     The bearing graticule (lit incandescent) for the scopes are the same. There should
     be an inner circle with tickmarks for each degree, starting at 0 (north) and going
     clockwise to the last tick, which is 359. Outside the inner ring shall be text
     labels for every 15 degrees. Outside the text labels, there will be
     an outer ring. Both inner and outer rings, along with ticks, and the bearing
     labels are to be incandescent color.
    
     The sweep time shall be 5 seconds for the
     air traffic scope.
    
     The sweep direction on the scope is clockwise, which means that the antenna
     dish rotates clockwise.
    
     The scope has cursor for range and bearing. The cursor consists of a
     section of a ring ( 10 degrees) and a cross line for bearing. 
     The cursor should be yellow (it
     a plastic overlay in the period time. Two controls control the  cursor; range and
     bearing. Both were physical crank controls. For now, both we need to use key pairs
     on the keyboard. A white text indication of range and bearing should be put under 
     the scope. In the real day, it was a machanical readout. The key sequence would be
     r for bearing to the right and l for bearing for the left; and t for higher range
     and y for smaller range.
     These controls should have slow movement for single stroke; but
     gradual for for holding key down.
    
  6. Precision approach (PAR for short) PAR was developed in WWII and matured in the 1950s. With a fixed 10 mile range, it was controller who talked the pilot down verbally over radio, which means that the pilot does not have to rely on any equipment on the plane itself to help with landing. The display shows the full 10-mile approach path, but the controller's active guidance window is roughly the last 5 miles, intensifying from about 2 miles out to touchdown. This needs to be carefully explained on the explainer screen. Let's locate this at the south end of Runway 16/34 landing at BLI and let's have the active runway 34 (northbound landing)

    Locate at the end of Runway 16/34 at Bellingham Airport (BLI). Two vertically stacked scopes share the right panel. Top scope: azimuth (lateral deviation vs. range from touchdown). Bottom scope: elevation (vertical deviation vs. range). Have the azimuth scope to about 1/3 larger than the elevation scope Both use P7 phosphor; graticules are incandescent etched glass. Range: 10 miles maximum, fixed (no range change control). Non-linear scale: inner 5 miles occupies 70% of horizontal width. All targets are simulated. No cursor or bearing controls; PAR has no bearing selection — it always points toward the runway. Sweep rate: approximately 30 Hz alternating between azimuth and elevation planes so that each will scan 1/15 th of a second.

THREADS

These are the threads of processes:

  1. Display initiation and operation (anything that 'touches' the display and the shaders) Thread 1
  2. Software that receives data for targets. Thread 2 (this is the traffic cop that polls the raspberry pis. and the Simulator. This needs mutex access to shared data with thread 1. It will also need mutex access to shared data with thread 4 (the simulator)
  3. Knob panel - thread 3 - uses a mutex to write shared state variables that thread 1 reads to send to the shaders.
  4. Simulator, thread 4. It is polled by the traffic cop

Threads 2,3, need mutex access to shared data that is read by thread 1. Thread 2 needs mutex access for shared data with thread 4, the simulator

SUMMARY OF Controls: ● ┌─────┬─────────────────────────────────────┬───────┬──────────┬──────────────┬────────────┬─────────┬─────┐ │ Key │ Function │ Intro │ Marine A │ Chain Home A │ Marine PPI │ ATC PPI │ PAR │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ s │ Advance to next scope │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ S │ back to previous scope │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ c │ Bearing clockwise │ │ ✓ │ │ │ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ v │ Bearing counterclockwise │ │ ✓ │ │ │ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ u │ Max range up │ │ ✓ │ │ ✓ │ ✓ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ d │ Max range down │ │ ✓ │ │ ✓ │ ✓ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ r │ Cursor bearing right │ │ │ │ ✓ │ ✓ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ l │ Cursor bearing left │ │ │ │ ✓ │ ✓ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ t │ Cursor range increase │ │ │ │ ✓ │ ✓ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ y │ Cursor range decrease │ │ │ │ ✓ │ ✓ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ k │ Antenna bearing offset right (boat) │ │ │ │ ✓ │ ✓ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ j │ Antenna bearing offset left (boat) │ │ │ │ ✓ │ ✓ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ [ │ Goniometer H/V switch │ │ │ ✓ │ │ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ 9 │ Goniometer tune left │ │ │ ✓ │ │ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ 0 │ Goniometer tune right │ │ │ ✓ │ │ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ . │ Toggle PRF (25/12.5 Hz) │ │ │ ✓ │ │ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ n │ Calibrator shrink │ │ │ ✓ │ │ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ m │ Calibrator stretch │ │ │ ✓ │ │ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ 1 │ Gain increase │ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ 2 │ Gain decrease │ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ 3 │ Rain clutter filter increase │ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ 4 │ Rain clutter filter decrease │ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ 5 │ Wave clutter filter increase │ │ ✓ │ ✓ │ ✓ │ │ │ ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼─────────┼─────┤ │ 6 │ Wave clutter filter decrease │ │ ✓ │ ✓ │ ✓ │ │ │ └─────┴─────────────────────────────────────┴───────┴──────────┴──────────────┴────────────┴─────────┴─────┘

Table for general controls not yet implemented on the keyboard in the table above:

  1. Intensity
  2. Focus
  3. Astigmatism
  4. Graticule light intensity

Note: Gain (keys 1/2), rain clutter (keys 3/4), and wave clutter (keys 5/6) are now in the keyboard control table above. They remain physical encoder controls on the operator panel when that hardware is installed; the keyboard keys are the temporary stand-in. All three have defaults in settings.h.

SUMMARY of target handling:

The traffic cop handles anything that is coming from the simulator as well as the raspberry pi's It will use polling to find if anything is available from the raspberry pis and the simulator. It will poll each source once per beam update

The raspberry pi receiver pulls the data from each raspberry pi. If nothing, it does nothing else for this sweep. If there is data, it will provide data to the traffic cop upon poll.

Each raspberry pi, after boot-up, will respond to polls from the raspberry pi receiver (thread 2)

The Simulator will run all fake targets. It will provide data to the traffic cop upon traffic cop poll. It can run as a separate thread. It will not write data to anywhere except when polled by the traffic cop.

CONTROLS

Every control listed above — both keyboard controls and the 7 general operator controls — shall have a corresponding default constant in settings.h. This allows any startup value to be changed at compile time without touching scope or rendering code.

The 7 general controls (Intensity, Focus, Astigmatism, Gain, Rain Clutter, Wave Clutter, Graticule Intensity) are physical encoder controls not yet purchased. Their placeholder default constants are defined in settings.h. KnobPanel (Thread 3) will compile and run from the start but will idle without ever acquiring Mutex A until real hardware is wired in. SharedRenderState holds the default values unchanged; Thread 1 reads and applies them every frame. No feature flags or conditional compilation are needed — the code path is complete end-to-end, always at the compile-time default.

Three of the 7 general controls — Gain, Rain Clutter, and Wave Clutter — have temporary keyboard implementations (keys 1/2, 3/4, and 5/6 respectively) that write to the same SharedRenderState fields the hardware encoders will eventually write to. When physical encoders are installed, the keyboard keys can be removed or left as redundant overrides.

Things to note about the keyboard type controls. The letter on the keyboard are temporary. When I get around to making the operators panel, this all will go away.

Implementation of controls:

  1. For keyboard controls. Those are run as thread one where The keyboard callback belongs to GLFW (glfwSetKeyCallback) They will manipulate the shaders only.

  2. The control desk controls will have to mutex to access the state variables that thread 1 sends to the shaders.

  3. If the control does not yet exist, we still want stubs for receiving control data for that control. It's just that nothing will call it.

Scope and left window arrangement.

For each scope, put the scope itself on the right hand of the window. On the left hand of the window will be a text description of that scope.

Underneath each scope's description will be cursor range and bearing from the radar location; and the setting of maximum range; and the bearing offset; for 0 would be to have 0 degrees pointing to true north (this is needed if I decide to implement a radar on a boat. If implemented, use k for bearing to right; and j for bearing to left. Make note in description that this is only used if this is a radar on a boat. (perhaps later on, I could add a PPI on a boat scenario)

Please note that some keys may be the same from scope to scope. This is okay. Each scope's controls are for that scope that you are connected do.They will not effect settings on another scope.

Please note that the maximum range setting on a scope specific to that scope and will be in that scope's definition. and the bearing selection is scope specific. The manually operated radar dish for the a scope is not the same as the PPI radar dishes. They are from different eras. In addition, all range and bearing data for marine is separate than for air traffic control. They are completely different radars. Range and bearing for the precision approach radar will be different than any other radar as that radar is located at the end of the runway and scan both horizontal and vertical.

Please analyze and comment. Please do not generate any code file nor shader files.

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

CLASS DESIGN AND FILE LAYOUT

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

Class Hierarchy:

Scope (abstract base) ├── ExhibitIntro ├── AScope (abstract) │ ├── MarineAScope │ └── ChainHomeAScope ├── PPIScope (abstract) │ ├── MarinePPIScope │ └── ATCPPIScope └── PARScope

Scope (abstract base) — everything all scopes share:

  • Left panel text rendering
  • s / S key handling (scope advance / reverse)
  • Auto-advance timer reset on any key or control input
  • Pure virtual: render(), handleKey(), getDescription()

ExhibitIntro : public Scope

  • Text-only rendering, no radar display
  • Header: "WELCOME TO MUSEUM VINTAGE RADAR EXHIBIT" (all caps)

AScope : public Scope (abstract) — shared A-scope behavior:

  • Horizontal range axis, vertical amplitude axis
  • Noise floor rendering (rain/wave clutter)
  • Incandescent graticule (three horizontal amplitude lines + vertical range lines)
  • Bearing control with key-hold acceleration
  • Phosphor type as parameter (P1 or P7)

MarineAScope : public AScope

  • P1 phosphor (green)
  • Range settings: 2, 4, 6 miles
  • Graticule swap animation state machine (NORMAL/SLIDING_OUT/BARE_CRT/SLIDING_IN) when operator changes max range — see Marine A-Scope section above for full detail
  • Keys: c (bearing CW), v (bearing CCW), u (range up), d (range down) u and d ignored during graticule swap animation

ChainHomeAScope : public AScope

  • P7 phosphor (early implementation)
  • Goniometer state: H/V mode toggle, azimuth angle, elevation angle
  • PRF toggle: 25 Hz / 12.5 Hz
  • Calibrator stretch/shrink scale factor
  • Fixed 100-mile range
  • Keys: [ (goniometer H/V toggle), 9/0 (tune), . (PRF), n/m (calibrator)

PPIScope : public Scope (abstract) — shared PPI behavior:

  • Clockwise sweep with P7 phosphor persistence (blue strike, green/yellow decay)
  • Incandescent bearing graticule (1-degree ticks, 15-degree labels, inner/outer rings)
  • Yellow cursor: 10-degree arc + bearing crossline
  • Cursor range/bearing readout under scope (white text)
  • Bearing offset for boat mode (k/j)
  • Cursor range clamped to max range

MarinePPIScope : public PPIScope

  • Sweep time: 4 seconds
  • Max range: 2, 4, 6 miles with correct ring sets
  • Keys: u (range up), d (range down) — this scope only

ATCPPIScope : public PPIScope

  • Sweep time: 5 seconds
  • Max range: 5, 10, 15, 20 miles with correct ring sets
  • Keys: u (range up), d (range down) — this scope only

PARScope : public Scope

  • Two stacked sub-scopes: azimuth (top, ~1/3 larger) and elevation (bottom)
  • 30 Hz alternating scan between planes (~15 Hz each)
  • Fixed 10-mile range, non-linear scale (inner 5 miles = 70% width)
  • P7 phosphor; incandescent etched glass graticules
  • All targets simulated; no cursor or bearing controls

Supporting classes: ScopeManager Thread 1 — owns scope list, s/S switching, 120s auto-advance timer PhosphorRenderer Thread 1 — P1 and P7 decay/persistence; shared dependency Graticule Thread 1 — incandescent graticule lines/text; parameterized per scope LeftPanel Thread 1 — scope description text panel (left side of window) SharedRenderState Threads 1,2,3 — Mutex A; state vars Thread 1 reads each frame for shader uniforms TargetBuffer Threads 2,4 — Mutex B; target data handoff between TrafficCop and Simulator TrafficCop Thread 2 — polls Simulator and RPi receivers; writes to SharedRenderState Simulator Thread 4 — runs fake targets; returns data to TrafficCop when polled KnobPanel Thread 3 — future hardware stub; writes to SharedRenderState under Mutex A RPiReceiver Thread 2 — stub; one per Raspberry Pi; called by TrafficCop

File layout:

src/ main.cpp scope_manager.h / scope_manager.cpp scope.h / scope.cpp — abstract Scope base scope_intro.h / scope_intro.cpp scope_ascope.h / scope_ascope.cpp — abstract AScope scope_marine_a.h / scope_marine_a.cpp scope_chain_home.h / scope_chain_home.cpp scope_ppi.h / scope_ppi.cpp — abstract PPIScope scope_marine_ppi.h / scope_marine_ppi.cpp scope_atc_ppi.h / scope_atc_ppi.cpp scope_par.h / scope_par.cpp phosphor.h / phosphor.cpp graticule.h / graticule.cpp left_panel.h / left_panel.cpp shared_render_state.h / shared_render_state.cpp target_buffer.h / target_buffer.cpp traffic_cop.h / traffic_cop.cpp simulator.h / simulator.cpp knob_panel.h / knob_panel.cpp rpi_receiver.h / rpi_receiver.cpp

settings.h                         — all tunable constants; no .cpp needed

shaders/ phosphor.vert / phosphor.frag — parameterized for P1 and P7 via uniforms graticule.vert / graticule.frag text.vert / text.frag sweep.vert / sweep.frag

settings.h — tunable constants: All magic numbers live here. Every source file that needs a tunable value includes settings.h. No values are hardcoded elsewhere. Categories planned: - Phosphor P1 color - Phosphor P7 strike color, persistence color, decay times (PPI and Chain Home) - Sweep line width, brightness, fade trail, periods per scope - PAR scan rate; Chain Home PRF high and low - Graticule incandescent color, line widths - PPI bearing ring tick lengths, label interval, font size - PPI range ring line width, label size, label color - Cursor color, line width, arc span - Noise floor amplitude and variation (Marine A-Scope) - Graticule swap animation durations (slide out, bare CRT, slide in) - Key-hold acceleration (initial step, rate, max) — separate for goniometer - Auto-advance timer interval (120 seconds) - Window size and panel layout fractions - PAR azimuth/elevation height fractions - UI text color and size; cursor readout text size - Graticule label color (incandescent) - Gain: default (0.5), minimum (0.0), maximum (1.0), keyboard step size - Rain clutter filter: default (0.0 = off), minimum (0.0), maximum (1.0), keyboard step size - Wave clutter filter: default (0.0 = off), minimum (0.0), maximum (1.0), keyboard step size - Key-hold acceleration for gain, rain clutter, and wave clutter keys

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

LEFT PANEL TEXT — ONE ENTRY PER SCOPE

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

Note: Each left panel renders its description text followed immediately by its control table. Every panel also states the next scope name so the visitor knows what pressing s will show. The s / S advance note appears in every panel.


PANEL 1 — EXHIBIT INTRODUCTION

Header (all caps, large): WELCOME TO THE MUSEUM VINTAGE RADAR EXHIBIT

Body: Welcome! This exhibit lets you experience how radar worked from the 1940s through the 1960s — technology that changed the course of World War 2 and shaped modern aviation and maritime safety.

Radar works by sending out short bursts of radio energy and listening for the echo that bounces back from ships, aircraft, and terrain. By measuring the time it takes for the echo to return, the radar calculates how far away the object is. Rotating the antenna builds a map of everything around it.

This exhibit features six radar displays. Explore each one at your own pace.

Press s at any time to jump to the next display — do not wait for the automatic 120-second advance. Press S (shift+s) to go back. Pressing any key or control resets the 120-second timer.

Next: Marine A-Scope →

┌─────┬───────────────────────┐ │ KEY │ FUNCTION │ ├─────┼───────────────────────┤ │ s │ Next display │ │ S │ Previous display │ └─────┴───────────────────────┘


PANEL 2 — MARINE A-SCOPE

The A-Scope was one of the earliest radar displays, used aboard ships and in coastal stations in the 1950s. Unlike the circular display you may have seen in movies, the A-Scope sweeps left to right: distance (range) runs along the bottom axis, and the height of each spike shows how strong the echo is from that direction.

To look in a different direction, the operator physically rotates the antenna by hand. Use c and v to rotate the antenna on this display.

The green glow is the P1 phosphor coating on the inside of the cathode ray tube. In a real radar room this was often the only light in the space.

The glass panel in front of the screen is the graticule — an etched, back-lit calibration scale for measuring range. When you change the maximum range setting, watch the operator swap the graticule panel by hand — just as it was done in the period.

Location: Bellingham Bay, WA — a fictional 100-foot mid-bay platform.

Press s to advance. Press S to go back. Auto-advance in 120 seconds. Any key press resets the timer. Next: Chain Home A-Scope →

┌──────┬──────────────────────────────┐ │ KEY │ FUNCTION │ ├──────┼──────────────────────────────┤ │ s/S │ Next / previous scope │ │ c/v │ Bearing clockwise / CCW │ │ u/d │ Max range up / down (2,4,6) │ │ 1/2 │ Gain increase / decrease │ │ 3/4 │ Rain filter increase / dec │ │ 5/6 │ Wave filter increase / dec │ └──────┴──────────────────────────────┘


PANEL 3 — CHAIN HOME A-SCOPE

Chain Home was Britain's early warning radar network, built in the late 1930s and critical during the Battle of Britain (1940). Instead of a rotating dish, it used a fixed array of tall transmit towers that flooded the sky over the English Channel with radio energy — a technique called floodlighting. Aircraft crossing the Channel reflected this energy back to separate receive antennas, sometimes at ranges up to 100 miles.

FINDING DIRECTION — THE GONIOMETER Because there was no rotating dish, finding the bearing and altitude of a target required a technique called nulling. A device called a goniometer electronically combined signals from several receive antennas. The operator slowly turned the goniometer dial while watching the screen. In most positions the signal was strong. But at one precise setting the signal suddenly disappeared — this was the null. When nulled, the goniometer was pointing directly at the aircraft. Think of it like tuning a radio: you search for the silent spot between two stations. Press [ to switch the goniometer between bearing (horizontal) and elevation (vertical) modes. Use keys 9 and 0 to tune — turn slowly, the null appears suddenly.

CALIBRATOR PIPS Early electronics drifted, stretching or compressing the range scale. The crystal calibrator injects tiny spikes at exact 10-mile intervals. Use n to shrink and m to stretch the trace until the pips line up with the 10-mile marks on the glass graticule.

This display is set at the Chain Home station at Poling, East Sussex, facing the English Channel. All targets are simulated German aircraft.

Press s to advance. Press S to go back. Auto-advance in 120 seconds. Any key press resets the timer. Next: Marine PPI →

┌──────┬─────────────────────────────────┐ │ KEY │ FUNCTION │ ├──────┼─────────────────────────────────┤ │ s/S │ Next / previous scope │ │ [ │ Goniometer mode: bearing / elev │ │ 9/0 │ Goniometer tune left / right │ │ . │ Toggle PRF: 25 Hz / 12.5 Hz │ │ n/m │ Calibrator shrink / stretch │ │ 1/2 │ Gain increase / decrease │ │ 3/4 │ Rain filter increase / dec │ │ 5/6 │ Wave filter increase / dec │ └──────┴─────────────────────────────────┘


PANEL 4 — MARINE PPI SCOPE

The PPI (Plan Position Indicator) became the standard radar display for ships from the late 1950s onward. The antenna rotates clockwise and the sweep line rotates with it, painting a map of everything within range. Targets glow bright blue the instant the sweep passes over them, then fade through green to yellow before the sweep returns — this is the P7 phosphor persistence that keeps the picture visible between sweeps.

The dotted range rings give distance reference. The incandescent bearing scale shows True direction (0 = North, clockwise to 359).

The yellow overlay is a mechanical cursor — a plastic ring and crosshair mounted in front of the screen. Use the cursor keys to position it over a target; range and bearing read out below the scope.

Location: Bellingham Bay, WA — a fictional 100-foot mid-bay platform. Targets: AIS-equipped vessels and simulated traffic.

Press s to advance. Press S to go back. Auto-advance in 120 seconds. Any key press resets the timer. Next: ATC PPI →

┌──────┬────────────────────────────────────┐ │ KEY │ FUNCTION │ ├──────┼────────────────────────────────────┤ │ s/S │ Next / previous scope │ │ u/d │ Max range up / down (2, 4, 6 mi) │ │ r/l │ Cursor bearing right / left │ │ t/y │ Cursor range increase / decrease │ │ k/j │ Antenna offset right / left │ │ │ (boat heading correction — zero │ │ │ means antenna faces True North) │ │ 1/2 │ Gain increase / decrease │ │ 3/4 │ Rain filter increase / decrease │ │ 5/6 │ Wave filter increase / decrease │ └──────┴────────────────────────────────────┘


PANEL 5 — AIR TRAFFIC CONTROL PPI SCOPE

This is the Airport Surveillance Radar (ASR) display used by air traffic controllers at regional airports in the 1960s. It works on the same principle as the marine PPI but covers a larger area (up to 20 miles) and is optimised for tracking aircraft rather than ships. The S-Band frequency (3 GHz) gives a good balance of range, resolution, and resistance to weather clutter for airspace surveillance.

Controllers used this display to sequence arriving and departing aircraft, identify potential conflicts, and provide navigation guidance to pilots flying in low visibility conditions.

Location: Bellingham International Airport (BLI), dedicated radar tower. Targets: ADS-B equipped aircraft and simulated traffic.

Press s to advance. Press S to go back. Auto-advance in 120 seconds. Any key press resets the timer. Next: Precision Approach Radar →

┌──────┬──────────────────────────────────────────┐ │ KEY │ FUNCTION │ ├──────┼──────────────────────────────────────────┤ │ s/S │ Next / previous scope │ │ u/d │ Max range up / down (5, 10, 15, 20 mi) │ │ r/l │ Cursor bearing right / left │ │ t/y │ Cursor range increase / decrease │ │ k/j │ Antenna offset right / left │ │ 1/2 │ Gain increase / decrease │ │ 3/4 │ Rain filter increase / decrease │ └──────┴──────────────────────────────────────────┘


PANEL 6 — PRECISION APPROACH RADAR (PAR)

The Precision Approach Radar was developed during World War 2 and refined through the 1950s. It gives a controller a precise picture of exactly where an aircraft is on its final approach to the runway — both its left-right position and its altitude.

Unlike instrument landing systems that require special equipment on the aircraft, PAR needed nothing from the pilot's plane. The controller watched the display and talked the pilot down over the radio: "You are slightly right of centerline, begin correcting left. You are above the glide path, begin a slightly steeper descent." This made PAR invaluable when a plane's own instruments failed or when visibility dropped to near zero in heavy fog.

TOP DISPLAY — AZIMUTH (left-right) Shows whether the aircraft is left or right of the runway centerline. The centerline runs horizontally through the middle of the display.

BOTTOM DISPLAY — ELEVATION (up-down) Shows whether the aircraft is above or below the correct glide slope. The ideal descent path runs through the center of the display.

Both displays expand the inner 5 miles to 70% of the screen width — giving maximum precision during the critical final approach phase.

Location: South end of Runway 16/34, Bellingham Airport (BLI). Active runway: 34 (aircraft landing northbound). All traffic is simulated.

Press s to advance. Press S to go back. Auto-advance in 120 seconds. Any key press resets the timer. Next: Exhibit Introduction →

┌──────┬──────────────────────────────┐ │ KEY │ FUNCTION │ ├──────┼──────────────────────────────┤ │ s/S │ Next / previous scope │ │ 1/2 │ Gain increase / decrease │ │ 3/4 │ Rain filter increase / dec │ └──────┴──────────────────────────────┘

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

UNITS

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

All dimensions are stored and computed in METERS throughout the entire system — in shared state, in the database, in shaders, and in all target data structures.

Any incoming value that arrives in feet (e.g. ADS-B altitude, antenna heights from configuration) MUST be converted to meters at the data boundary — in RPiReceiver::parseFrame() or Simulator::poll() — before the value enters any shared data structure. No feet values may appear anywhere inside the system after the conversion point.

Conversion constant in settings.h: FEET_TO_METERS = 0.3048 (exact)

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

DEFAULT TARGET DIMENSIONS

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

When a target is first seen with no database record, the system inserts a row using the defaults below. All values in meters. need_update is set TRUE so the operator knows to fill in real data.

┌──────────────────┬───────────┬──────────────────┬────────────┐ │ Category │ Length (m)│ Width/Beam (m) │ Material │ ├──────────────────┼───────────┼──────────────────┼────────────┤ │ GA aircraft │ 4.0 │ 1.0 (fuselage) │ aluminum │ │ Commercial a/c │ 30.0 │ 5.0 (fuselage) │ aluminum │ │ AIS vessel │ 20.0 │ 5.0 (beam) │ steel │ │ Simulator boat │ 6.0 │ 2.0 (beam) │ fiberglass │ └──────────────────┴───────────┴──────────────────┴────────────┘

Notes:

  • AIS vessels are legally required commercial traffic, so steel is the correct default material.
  • Simulator boats are small pleasure craft, so fiberglass is correct.
  • The system defaults all new aircraft to GA. The operator must update commercial entries via the --database panel.
  • Height above water/ground defaults to 0 for vessels and 0 for aircraft (updated by live data).

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

POSTGRESQL DATABASE

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

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

Schema (all dimensions in METERS):

  1. target_type ENUM('AIS', 'ADSB', 'LOCAL')
  2. target_id BIGINT — MMSI for marine; ICAO 24-bit address for aircraft; simulator may reuse same space
  3. length_m REAL — length in meters
  4. width_m REAL — beam (vessels) or fuselage width (aircraft) in meters
  5. height_m REAL — height above water/ground in meters
  6. material ENUM('fiberglass', 'wood', 'aluminum', 'steel')
  7. need_update BOOLEAN DEFAULT TRUE
  8. last_seen TIMESTAMPTZ — ISO 8601, updated each time target is received from any source
  9. last_updated TIMESTAMPTZ — ISO 8601, updated when operator saves changes via DB panel
  10. track history — deferred to a future version; not in v1 schema

The fields length_m, width_m, height_m, and material are used by the CPU to compute RCS before passing to the shader. They are static per-target data uploaded to GPU via SSBO or uniform array each frame.

Per-frame dynamic fields (location, heading, altitude) are NOT stored in the database. They live in TargetBuffer (Threads 2/4) and are updated from live Raspberry Pi or simulator data each sweep.

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

DATABASE MANAGEMENT PANEL

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

Activated by command-line flag: --database In this mode NO radar display is shown. main.cpp skips all scope and shader initialization and starts the Dear ImGui loop instead.

Toolkit: Dear ImGui, integrated with GLFW/OpenGL 3.3. Source lives in src/imgui/ and is compiled directly into the project (no separate install step required).

The panel provides:

  • Scrollable table of all targets with need_update highlighted
  • Inline edit fields for length, width, height, material
  • Dropdown for target_type
  • Save button that writes to PostgreSQL via libpq
  • Quit button to exit the DB panel (then start main exhibit separately)

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

RADAR EQUATION AND BLOOM

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

The radar equation is evaluated CPU-side to compute received power (P_r) for each target. The result drives target brightness and bloom. Computation happens in TrafficCop after each poll, outside any mutex lock (read-only access to target physical data at that point).

The computed brightness value is uploaded per-target to the GPU via SSBO or uniform array. The phosphor shader reads it to set amplitude.

BLOOM POST-PROCESSING (bloom.vert / bloom.frag): Separate shader pair. Pipeline: 1. Render targets to offscreen FBO at full computed brightness 2. Apply two-pass Gaussian blur to pixels above luminance threshold 3. Additively blend blurred result back onto main framebuffer Constants in settings.h: BLOOM_LUMINANCE_THRESHOLD, BLOOM_BLUR_RADIUS_PX, BLOOM_BLEND_STRENGTH

RADAR PARAMETERS — MARINE (X Band): Frequency: 9225 MHz Peak power: 30 kW Horizontal beamwidth: 0.5 degrees Vertical beamwidth: 20 degrees Antenna length: 15 feet (4.572 m) Antenna height: 50 feet (15.24 m) above surface

RADAR PARAMETERS — ATC (S Band): Frequency: 3000 MHz (3 GHz) Peak power: 25 kW (exhibit value; real ASR is higher) Horizontal beamwidth: 1.4 degrees Vertical beamwidth: 5 degrees Antenna width: 20 feet (6.096 m) Antenna height: 50 feet (15.24 m) above runway

RADAR PARAMETERS — CHAIN HOME (AMES Type 1): Frequency: 30 MHz Peak power: 500 kW Pulse width: 20 microseconds PRF: 25 Hz or 12.5 Hz (operator selectable) Transmit gain (Gt): ~7.5 linear (~8.7 dBi) — floodlight, not a beam formula: G = 30000 / (az_bw_deg × el_bw_deg) with az ~100 deg, el ~40 deg Receive gain (Gr): 4 to 10 linear (610 dBi) System loss: ~8 dB (transmission line) Bistatic: yes — separate transmit and receive antennas RCS resonance: at 30 MHz (lambda ~10 m) aircraft are in the Mie/resonant scattering region; RCS is multiplied by CHAIN_HOME_RCS_RESONANCE_FACTOR (default 3.0) before radar equation computation. Applied to Chain Home targets only.

RADAR PARAMETERS — PAR (X Band): Peak power: 100 kW Frequency: ~10 GHz (lambda ~3 cm) Antenna size: ~5 meters Beamwidth: ~0.34 degrees (lambda/D) Horizontal scan: 20 degrees total Vertical scan: 10 degrees total Pulse width: short (high range resolution) PRF: high (~30 Hz alternating az/el)

All radar parameters shall have corresponding constexpr constants in settings.h so they can be tuned without touching equation code.

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

FILE LAYOUT (COMPLETE — including additions)

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

src/ main.cpp scope_manager.h / scope_manager.cpp scope.h / scope.cpp scope_intro.h / scope_intro.cpp scope_ascope.h / scope_ascope.cpp scope_marine_a.h / scope_marine_a.cpp scope_chain_home.h / scope_chain_home.cpp scope_ppi.h / scope_ppi.cpp scope_marine_ppi.h / scope_marine_ppi.cpp scope_atc_ppi.h / scope_atc_ppi.cpp scope_par.h / scope_par.cpp phosphor.h / phosphor.cpp graticule.h / graticule.cpp left_panel.h / left_panel.cpp shared_render_state.h / shared_render_state.cpp target_buffer.h / target_buffer.cpp traffic_cop.h / traffic_cop.cpp simulator.h / simulator.cpp knob_panel.h / knob_panel.cpp rpi_receiver.h / rpi_receiver.cpp db_panel.h / db_panel.cpp — Dear ImGui DB management panel (--database mode only) settings.h — all constexpr constants; no .cpp

imgui/ — Dear ImGui source, compiled in imgui.h / imgui.cpp imgui_impl_glfw.h / imgui_impl_glfw.cpp imgui_impl_opengl3.h / imgui_impl_opengl3.cpp imgui_draw.cpp / imgui_tables.cpp / imgui_widgets.cpp

shaders/ phosphor.vert / phosphor.frag — P1 and P7 via uniforms graticule.vert / graticule.frag text.vert / text.frag sweep.vert / sweep.frag bloom.vert / bloom.frag — FBO bloom post-processing