diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8510083 --- /dev/null +++ b/.clang-format @@ -0,0 +1,4 @@ +BasedOnStyle: Google +IndentWidth: 4 +ColumnLimit: 100 + diff --git a/CLAUDE.md b/CLAUDE.md index 6ded8ae..530ecf7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,1733 +1,68 @@ -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 +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, +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 +32 GB RAM +AMD Ryzen 9 8945HS w/ Radeon 780M Graphics +We need to render to the Radeon 780M Graphics GPU -# Project: C++ OpenGL Radar Simulation -**Environment:** Ubuntu Linux (Remote SSH from Windows) -**Tech Stack:** C++20, OpenGL 3.3 Core, GLFW, GLAD, FreeType, GDAL (libgdal-dev) +Tech Stack: We are using C++20, OpenGL 3.3 Core, GLFW, GLAD, FreeType, GDAL (libgdal-dev) +Compiler: is g++ (Ubuntu 15.2.0-4ubuntu4) 15.2.0 +FreeType is the text type we use +GDAL is used for reading the LIDAR/ENC chart files +GLFW (graphics library framework) open-source, multi-platform library used to manage windows +GLAD (Multi-Language GL/GLES/WGL/GLX Loader-Generator) Loads the pointers to + OpenGL functions (like glDrawArrays or glCompileShader) -The operating system is Linux (Ubuntu) -Details: +PostgreSQL is installed. Database: radar. User: radar. Password: radar. +User has full privileges on database radar. Table is target_data. + +Operating system details: Distributor ID: Ubuntu Description: Ubuntu 25.10 Release: 25.10 Codename: questing -The compiler is cpp (Ubuntu 15.2.0-4ubuntu4) 15.2.0 +Use cmake for building. -We plan to use the cmake for building. +I will be using SSH from Windows to write code and check with claude. +You may compile the code during an SSH session. +Please do not try to run the code during SSH session. +I will run the code while physically using the Geekom. Please add MIT license header to each file Please add Author: Mark Allyn to each file +Use snake_case for variables and PascalCase for classes +use #pragma once +Use // for single line comments +use /* */ for multiple block comments spanning multiple lines +avoid using auto -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 -./map -./map/lidar_processed -./map/charts_enc -./map/charts_enc/US5WA45M.000 -./map/charts_enc/n48_w123_1arc_v3.tif -./map/lidar_raw -./map/lidar_raw/wa2022_nooksack_dem_J1364940.zip -./map/lidar_raw/wa2016_west_dem_J1364939.zip +Summary of project: +This is a museum exhibit displaying and providing interaction +of vintage 1940's, 1950's, and 1960's radars. A key objective is to +provide interaction and viewing of the frustrations of using +radars in that era. The different radars are: -================================================================== +Scopes in the right panel -GENERAL STUFF +1. A-scope for Chain Home Radar in the 1940's (first radar and could be tricky) +2. A-scope for marine radar in the 1950's (Before PPI radar. Was a bit tedious to operate +3. PPI scope for marine traffic control (uses beam sweeping in all 360 degrees of + rotation; Easier to use than a scope +4. PPI scope for air traffic control; similar to PPI scope for marine, but with different range +5. PPI scope on board a boat. Shows how movement of a boat affects the radar display +6. Precision Approach Radar (Two scopes; one showing horizontal movement of a plane + in the glide path toward the runway, and the other showing vertical movement of a plane + as it glides vertically down to the runway. -================================================================== +Text window in the left panel for descriptions of the scopes and a listing of controls -COLORS - -1. P7 Strike Color (when things are written to p7 = Blue white Hex #F0F8FF -2. P7 Persistence (during persistence after strike = Yellow Hex #F2BF1C -3. All Graticules and PPI Cursors are incandescent #47 lamp Hex #FFF4CC - This is for bearing rings on ppi scope as well as bearing ticks and - bearing degree texts -4. Left Panel Text for description is white hex #FFFFFF except for controls - as described below -5. Control operation via keyboard key strokes are light red Hex #FFCCCB -6. Control operation description are light green #64E3A1 -7. Status text panel under scope are cyan hex #00FFFF -8. Note that all targets are to be p7; this includes land features and - active targets. Range rings on ppi are also p7 - Please note that range rings shall be fixed brightness; not affected - by sensitivity. -9. Range markings and range calibration pips shall be p7 on the chain - home scope. -10. Center line on precision approach radar horizontal and guide path - on precision approach radar vertical shall be p7 -11. Rain noise as well as water wave noise shall also be p7 and be - affected by gain as well as noise filtering controls -12. Please note for each color in this table, there shall be a variable - in the settings.h file that can be used to change the color. Values should be in hex. -13. The marine a scope display is p1 phosphor (green) -14. The chain home scope display is p2 phosphor (blue green with some persistence) - -================================================================== - -Please note that all on-screen text shall be white and fully -illuminated and is not subject to phosphor persistence or decay. - -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. - -Please note that controls are used for the current scope that is selected. When you move to another scope or to scope -1 after leaving the Exhibit introduction screen; the controls must be reset for the scope you are entering - -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 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 max range setting shall be included in the status text window below the scope - - 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. - - The sliding out, bare crt, and sliding in should all be settable in settings.h - - 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. - - The status for goniometer switch shall be shown in the status text windo below - the scope as well as the goniometer setting in degrees as selected by the switch - - 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. - - The setting for the range should be on the status text panel below the scope. - - 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. All PPI Scopes (common features) - All three ppi scopes (marine stationary; marine police boat; and Air Traffic Control - Have the following common features: - - When the operator changes max range (u/d keys), the new ring geometry - takes effect only for the sector the sweep is currently painting. The - rest of the scope still holds the old ring positions in the phosphor, - glowing and fading normally. As the sweep continues rotating, each - sector it reaches is repainted with the new ring geometry, replacing the - old. One full sweep rotation after the change, the transition is complete - and only the new rings remain. This is physically correct — the phosphor - holds whatever the beam last wrote. No special transition animation is - needed; the behavior emerges naturally from the phosphor model. - - Note on range. If cursor range is beyond 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 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 holding key down. - - 5. Marine PPI Scope specific items: - - This scope is for a fixed location marine radar. It is for marine traffic control. - 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 - - - - 6. Police Patrol Boat PPI - - This scope shows the radar display aboard a simulated Bellingham Police - Department patrol vessel making its routine waterfront patrol. Unlike all - other scopes in this exhibit the radar origin is not fixed — it moves with - the boat, making this a genuinely different operational experience. - - THE PATROL VESSEL AND ITS RADAR - The vessel carries a professional-grade open-array radar: 6 kW peak power, - 24-inch open array, ~1.9° horizontal beamwidth, mounted 3–4 m above the - waterline on a flybridge or radar arch. This is a working law-enforcement - vessel, not a recreational boat; the equipment reflects that. The narrower - beamwidth (vs a recreational radome's 4–6°) gives better azimuth resolution - and less sea clutter per range cell — important when searching for small - targets such as kayakers and stand-up paddleboards near the ferry lane. - - MAX RANGE: 2 miles. Range steps: 0.5 / 1 / 2 miles. - The patrol mission is close-in situational awareness. The tight range steps - let the officer zoom into the marina entrance, Whatcom Waterway, or the - ferry lane without switching to a different scope. - - RADAR HORIZON: ~4.3 nm at 3.5 m antenna height. Range is mission-limited - (2 miles) rather than horizon-limited. - - PATROL ROUTE (v1 — open water only) - The simulated vessel follows a continuous back-and-forth patrol of the - working waterfront at variable speed. Entry into Squalicum Marina and - Whatcom Waterway is deferred to a future version; v1 stays in open water - where the radar geometry is straightforward. - - Speed by zone: - Open waterfront / ferry lane: 10 knots - Near docks and breakwater: 4 knots - Route (loaded from data/patrol_route.json at startup): - Whatcom Waterway entrance → ferry terminal → Boulevard Park → - Taylor Dock → Community Boating Center → reverse and repeat - - SMALL TARGET SCENARIO — FERRY LANE - The Bellingham terminal serves Alaska Marine Highway ferries up to 400 ft. - The simulator places a scripted stand-up paddleboarder on a slow drift - across the ferry departure lane, plus random kayakers near the harbor mouth. - These are marginal radar targets: low RCS, no metal, minimal freeboard. - At 10 kt approach in light chop the paddleboarder may show as a faint - intermittent blip or vanish into sea clutter entirely. Visitors can try the - wave clutter filter (keys 5/6) and observe how suppressing clutter can also - suppress the very target they are looking for. This is the central teaching - moment of this scope: radar does not see everything. - - DISPLAY ORIENTATION - Default is North-up (000° at top). The k/j keys rotate the display offset. - Matching the offset to the boat's heading puts the bow at the top — Head-up - mode. The left panel labels the current mode and shows the heading marker, - a white dashed line from scope center toward the bow, drawn after all - phosphor content so it never decays. - - The heading and the range max settings shall be indicated on the status text - panel below the scope - - TERRAIN AND BREAKWATER CLUTTER - The concrete Squalicum Harbor outer breakwater is a strong radar return and - a significant shadow-caster. Everything behind the breakwater from the - patrol boat's point of view is shadowed — no return. The marina interior is - not visible from open water. This is realistic and visible on the scope. - Coastline, piers, and the ferry terminal structure also appear as clutter. - Shadow masks are pre-computed for patrol route waypoints by - terrain_preprocess and selected at runtime by nearest-waypoint lookup. - - Left panel status (below description text): - Zone: [plain text, e.g. "Ferry lane — open waterfront"] - Boat pos: XX.XXXX°N XXX.XXXX°W - Boat heading: XXX°T - Boat speed: X.X kt - Display mode: North-up (or Head-up) - Cursor range: X.X nm - Cursor brg: XXX°T - Max range: X.X mi - - 7. Air Traffic PPI Scope - - - 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 beyond 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. - 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 increase movement for holding key down. - - 8. 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). - Both the azimuth scope and the elevation scope should be the same size - 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 │ Boat 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 │ Display offset right (boat heading) │ │ │ │ ✓ │ ✓ │ │ │ - ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤ - │ j │ Display offset left (boat heading) │ │ │ │ ✓ │ ✓ │ │ │ - ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤ - │ [ │ 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 │ │ ✓ │ ✓ │ ✓ │ ✓ │ │ │ - └─────┴─────────────────────────────────────┴───────┴──────────┴──────────────┴────────────┴──────────┴─────────┴─────┘ - -Note: k/j (display bearing offset) are for marine PPI scopes only. The fixed ATC tower -at BLI has no heading offset need. On the fixed Marine PPI, k/j demonstrate North-up vs. -Head-up orientation as a teaching aid. On the Boat PPI, k/j are operationally meaningful: -zero offset = North-up (chart-style); offset matching boat heading = Head-up (bow at top). - -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. - -The text status panel under each scope shall show, in addition to notes above, -the bearing and range for the cursor in the status text panel below the scope. -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. - -================================================================== - -CLASS DESIGN AND FILE LAYOUT - -================================================================== - -Class Hierarchy: - - Scope (abstract base) - ├── ExhibitIntro - ├── AScope (abstract) - │ ├── MarineAScope - │ └── ChainHomeAScope - ├── PPIScope (abstract) - │ ├── MarinePPIScope - │ ├── BoatPPIScope - │ └── 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 - -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 - - P2 phosphor (blue green) - - 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) - - Incandescent 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 - - There shall be a gain independent channel for the p7 phosphor - that shall not be impacted by the operator gain control. - - Range rings are beam-painted per sweep sector with P7 persistence and decay; - operator gain does not dim the rings - - No range ring labels. That era had no text in the p7. - - IMPLEMENTER CHECKLIST — required in every new PPIScope subclass: - 1. computeRingRadii(): multiply each normalised ring radius by - GRAT_INNER_RING_FRAC (same as MarinePPIScope). Omitting this - places the outer ring at the scope boundary where it is clipped - and hidden behind the graticule. Target positions are scaled - automatically by PhosphorRenderer::update(); ring radii are not. - 2. No labeling of range rings.That era did not have them - -RANGE POSITION NORMALISATION - -All ring radii and target range values are normalised so that -max-range maps to GRAT_INNER_RING_FRAC (0.915), NOT 1.0. - -Normalised 1.0 is the outer edge of the phosphor circle (scope boundary). -The bearing graticule overlay occupies 0.915 to 0.985 of scope radius. -If max-range mapped to 1.0, the outer ring would sit at the scope -boundary — half-clipped by the sweep shader's rng > 1.0 early-exit and -visually hidden behind the graticule outer ring. - -================================================================== - - -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 - -BoatPPIScope : public PPIScope - - Direct subclass of PPIScope (not of MarinePPIScope) - - Range steps: 0.5 / 1 / 2 miles; sweep time 4 s; phosphor P7 - - Radar parameters: 6 kW, 1.9° open array (distinct from 30 kW / 0.5° coastal) - - Radar origin = boat lat/lon from SharedRenderState, updated every sweep - - Variable patrol speed per route segment (loaded from data/patrol_route.json) - - Heading marker: white dashed line from scope center toward boat heading; - drawn after all phosphor content so it always appears fully bright - - Nearest pre-computed shadow mask selected each sweep via - TerrainMap::selectNearestBoatMask() — no runtime ray-marching - - Display mode indicator: "North-up" / "Head-up" based on offset vs heading - - Left panel status: zone text, lat/lon, boat heading, speed, display mode - - Keys: u/d (range 0.5/1/2), r/l (cursor bearing), t/y (cursor range), - k/j (display offset), 1/2 (gain), 3/4 (rain filter), 5/6 (wave filter) - -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 on top and elevation below - - 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, P2 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/scope.cpp / scope.h = scope abstract -src/ascope.cpp /ascope.h = a scope abstract and shared a scope logic -src/marine_ascope.cpp / marine_ascope.h - MarineAScope -src/chain_ascope.cpp / chain_ascope.h - ChainHomeAScope -src/ppi_scope.cpp / ppi_scope.h - ppi scope abstract -src/ppi_scope_marine.cpp / ppi_scope_marine.h - marine ppi scope abstract -src/ppi_scope_marine_tower_scope.cpp / ppi_scope_marine_tower_scope.h - MarineTowerScope -src/ppi_scope_police_boat.cpp /ppi_scope_police_boat.h - MarinePoliceScope -src/ppi_scope_atc.cpp / ppi_scope_atc.h - AirTrafficScope -src/par_scope.cpp / src/par_scope.h - PARScope - note; this is for precision approach radar -src/status_text.cpp / status_text.h - StatusText - note; this is for status text below scope -src/land_feature.cpp / land_feature.h - LandFeature - note; this is for land feature terrain, shore, lidar -src/descriptive_text.cpp / descriptive_text.h - DescriptiveText note; this is for left panel text - including keyboard control descriptions -src/traffic_cop.cpp / src/traffic_cop.h - InputTrafficCop note; receiving data from rpis and simulator -src/simulator.cpp / simulator.h - TargetSimulator -src/rpi_receiver.cpp / src/rpi_receiver.h - RPIReceiver -src/keyboard_control.cpp / keyboard_control.h - KeyBoardControls -src/physical_control.cpp / physical_control.h - PhysicalControls -src/shared_render_state.cpp / shared_render.h - SharedRenderState -src/scope_manager.cpp / scope_manager.h - ScopeManager - -Shader pairs (include .vert and .frag - -P7_persistent_stage - having to do with the persistent (yellow green) of the p7 phosphor -marine_ascope_targets - handling marine ascope targets -marine_ascope_land - handling land for ascope -marine_ascope_bearing - handling marine a scope bearing -marine_ascope_graticule - handling changing marine ascope graticules -chainhome_ascope_graticules - handling chain home a scope graticules -chainhome_ascope_targets - handling chain home targets including the - goniometer operation -ppi_scope_graticules - handling all ppi scope graticules -ppi_scope_target - handling target for all ppi scopes -terrain_clutter - for terrain -par_scope_graticule - handling graticules for Precision Approach Radar Scopes -par_scope_targets - handling targets for Precision Approach Radar Scopes -bloom - two pass gaussian blur on pixels above luminance threshold, three tuning constants in settings.h -rain_noise - noise caused by rain amount to be set by a random CPU function -sea_wave_noise - noise caused by wind on waves; stronger when close to radar ; amount to be set by CPU function - -================================================================== - -PHOSPHOR FBO ARCHITECTURE - -================================================================== - -The P7 phosphor simulation uses a single floating-point offscreen -texture (the phosphor FBO) to store the current glow state of every -pixel in the scope. The texture format is RGBA16F. - -CHANNEL ASSIGNMENTS - R channel — gain-affected content: targets, land clutter, noise. - Multiplied by u_gain during the display pass. - G channel — gain-independent content: range rings only. - Displayed at full brightness regardless of u_gain. - B, A — reserved for future use. - -THREE PASSES PER FRAME - - 1. DECAY PASS (P7_persistent_stage.vert / .frag) - Reads the phosphor FBO and writes back to it with both R and G - channels multiplied by P7_DECAY_FACTOR (default ~0.985 at 60 Hz, - settable in settings.h). Both channels decay at the same rate. - When P7_PERSISTENCE_ENABLED is false in settings.h this pass is - skipped — the FBO is cleared each frame — allowing debugging of - raw strike data only. - - 2. STRIKE PASS (per-scope target shaders and terrain_clutter) - Runs during the sweep. Each fragment that the beam illuminates - writes a brightness value into the appropriate channel: - - Targets, clutter, noise → write to R, leave G unchanged - - Range rings → write to G, leave R unchanged - The brightness value is computed from the radar equation result - uploaded per-target via SSBO or uniform array. - - 3. DISPLAY PASS (composited in each scope's render method) - Reads both channels and maps to P7 colors: - target_glow = phosphorFBO.R × u_gain - ring_glow = phosphorFBO.G (no gain factor) - output = P7_color(target_glow) + P7_color(ring_glow) - P7_color() maps the glow value to the P7 palette: high values - produce P7_STRIKE_COLOR (#F0F8FF), lower values produce - P7_PERSISTENCE_COLOR (#F2BF1C). - -DEBUGGING SUPPORT - P7_PERSISTENCE_ENABLED (settings.h) — set false to skip decay pass; - each frame shows only raw strike data. - P7_STRIKE_DEBUG_ALPHA (settings.h) — scales the strike brightness - to evaluate persistence timing by eye. - -================================================================== - -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 seven 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: Marine PPI — Boat Scenario → - - ┌──────┬────────────────────────────────────────────────┐ - │ 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 │ Display offset right / left │ - │ │ (zero = North-up; rotate to match heading │ - │ │ = Head-up — a teaching aid on fixed radar) │ - │ 1/2 │ Gain increase / decrease │ - │ 3/4 │ Rain filter increase / decrease │ - │ 5/6 │ Wave filter increase / decrease │ - └──────┴────────────────────────────────────────────────┘ - ------------------------------------------------------------------- -PANEL 5 — POLICE PATROL BOAT PPI ------------------------------------------------------------------- - - This display is from the radar aboard a Bellingham Police Department - patrol vessel making its routine waterfront patrol. The scope center - is the boat — it moves with the patrol route, not fixed in the bay. - - THE RADAR IS DIFFERENT HERE - The patrol boat carries a professional 6-kilowatt open-array radar - with a 1.9-degree beam — narrower than a cheap boat radar, but wider - than the big 30-kilowatt coastal radar you saw two displays back. The - blips look noticeably fatter than on the coastal radar. The maximum - range here is 2 miles: the patrol mission is close-in harbor work, - not long-range scanning. - - THE FERRY LANE PROBLEM - The Bellingham terminal serves Alaska state ferries up to 400 feet - long. When a ferry departs, anyone in the departure lane — a kayaker, - a paddleboarder — must get clear. Watch the scope carefully: there - is a stand-up paddleboarder drifting slowly near the ferry lane. - Can you spot it? - - Stand-up paddleboards are very hard to see on radar. No metal, almost - no freeboard, small size — the radar echo is barely above the noise. - Try adjusting the wave clutter filter (keys 5 and 6). Turning it up - reduces the sea clutter that is hiding the target — but it may also - suppress the target itself. This trade-off is real. Radar does not - see everything. - - NORTH-UP AND HEAD-UP - By default the scope shows North-up: True North at the top, like a - chart. Press k or j to rotate the display. When the heading marker - (white dashed line) points straight up, you are in Head-up mode — - the bow is at the top. This is how most early marine radars worked. - - Location: Bellingham waterfront patrol, Bellingham Bay, WA. - Targets: AIS vessels, paddleboarder, random kayakers. - - 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 (0.5, 1, 2 mi) │ - │ r/l │ Cursor bearing right / left │ - │ t/y │ Cursor range increase / decrease │ - │ k/j │ Display offset right / left │ - │ │ (0° = North-up; match heading = Head-up) │ - │ 1/2 │ Gain increase / decrease │ - │ 3/4 │ Rain filter increase / decrease │ - │ 5/6 │ Wave filter increase / decrease │ - └──────┴────────────────────────────────────────────────┘ - ------------------------------------------------------------------- -PANEL 6 — 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 │ - │ 1/2 │ Gain increase / decrease │ - │ 3/4 │ Rain filter increase / decrease │ - └──────┴──────────────────────────────────────────┘ - ------------------------------------------------------------------- -PANEL 7 — 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. Table is target_data. - -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 (6–10 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) - -RADAR PARAMETERS — PATROL BOAT (X Band): - Frequency: 9300–9500 MHz (X-band; treat same as marine for exhibit) - Peak power: 6 kW - Antenna type: Open array, 24-inch (610 mm) - Horizontal beamwidth: 1.9 degrees - Vertical beamwidth: 20 degrees - Antenna height: 3.5 m above waterline (flybridge or radar arch) - Radar horizon: ~4.3 nm to sea-level target - Operational max range: 2 miles (mission-limited, not horizon-limited) - - NOTE — beamwidth comparison: - Fixed coastal marine: 0.5° — sharp blips, high azimuth resolution - Police patrol boat: 1.9° — noticeably fatter blips; good exhibit contrast - Consumer radome: 4–6° — poorest resolution (not used in this exhibit) - - NOTE — small target detection in sea clutter: - The narrower 1.9° beam illuminates ~1/3 the sea surface area per range - cell compared to a 5° radome, improving signal-to-clutter ratio by ~5 dB. - Even so, a stand-up paddleboard (RCS ~0.1–0.5 m²) is marginal in any chop. - Detection is realistic only in near-calm conditions at ≤1 mile. - -All radar parameters shall have corresponding constexpr constants -in settings.h so they can be tuned without touching equation code. - -================================================================== - -TERRAIN AND LAND CLUTTER - -================================================================== - -DATA SOURCES - map/charts_enc/US5WA45M.000 - S-57 Electronic Navigational Chart; coastline polygon, - pier/breakwater geometry, named landmarks. Parsed with GDAL/OGR. - - map/charts_enc/n48_w123_1arc_v3.tif - USGS 1 arc-second (~30 m) DEM GeoTIFF. Terrain elevation for the - Bellingham area — Cascades, Chuckanut Mountain, coastal lowlands. - - map/lidar_raw/wa2016_west_dem_J1364939.zip - 2016 western WA LiDAR DEM. Resolves waterfront structures, - breakwaters, piers, Boulevard Park boardwalk, and other man-made - features at ~1 m resolution. - - map/lidar_raw/wa2022_nooksack_dem_J1364940.zip - 2022 Nooksack basin LiDAR DEM. Covers the delta and lowlands - northeast of Bellingham Bay; used for terrain shadowing from the - northeast quadrant. - - All four sources are processed offline by terrain_preprocess into - binary grids in map/lidar_processed/. The raw files are never opened - at exhibit runtime. See TERRAIN PREPROCESSING section below. - - GDAL (libgdal-dev) is a required build dependency for - terrain_preprocess. It is NOT linked into the main radar binary. - -TERRAIN MATERIALS AND RCS - - Each terrain cell is classified as one of four materials. Normalized - backscatter coefficient σ° (linear, m²/m²) is defined in settings.h: - - TERRAIN_SIGMA0_SOIL ~0.010 (−20 dB) — moist vegetated soil - TERRAIN_SIGMA0_ROCK ~0.032 (−15 dB) — exposed rock, rough face - TERRAIN_SIGMA0_CONCRETE ~0.100 (−10 dB) — smooth hard surface; - structures produce - corner-reflector effect - TERRAIN_SIGMA0_WATER_CALM ~0.0003 (−35 dB) — open water, calm; - mostly specular, low - backscatter - TERRAIN_SIGMA0_WATER_ROUGH ~0.010 (−20 dB) — choppy sea, sea clutter - - Classification rules applied by terrain_preprocess at build time: - - Below sea level or inside S-57 coastline polygon → water - - Inside S-57 pier/breakwater/wharf feature → concrete - - Elevation > TERRAIN_ROCK_THRESHOLD_M AND slope - > TERRAIN_ROCK_SLOPE_THRESHOLD_DEG → rock - - All remaining land cells → soil - -RADAR EQUATION FOR TERRAIN (area-extensive / clutter form) - - P_r = (P_t × G² × λ² × σ° × A_cell) / ((4π)³ × R⁴) - - A_cell is the terrain cell area projected along the beam. - Evaluated CPU-side in LandClutter for each illuminated cell. - Result drives per-cell brightness in the clutter texture. - Same radar parameters (P_t, G, λ) used for point targets. - -SHADOW / LINE-OF-SIGHT MASKING - - Computed by terrain_preprocess for each fixed radar location and - stored in the processed data as uint8 shadow masks. Algorithm: march - outward from the radar along each azimuth radial, tracking the maximum - elevation angle seen. Any cell whose surface angle falls below that - maximum is shadowed — clutter amplitude = 0 and target returns through - that cell are attenuated proportionally. - - Shadow masks stored per radar location: - map/lidar_processed/shadow_marine.u8 — marine bay platform - map/lidar_processed/shadow_atc.u8 — BLI ATC tower - - Bearing offset (k/j keys on PPI scopes): - The k/j keys change display heading only — which direction appears - at the top of the scope. They do NOT move the radar geographically. - The shadow mask is unchanged. The terrain clutter shader receives - the offset as a rotation uniform and samples the polar texture at - the offset angle. Zero CPU overhead; no shadow recomputation. - - Boat PPI scope scenario: - When BoatPPIScope is active, SharedRenderState.boatModeActive is set - TRUE. LandClutter uses boatLatDeg / boatLonDeg as the polar grid origin - instead of the fixed marine platform position. - - Shadow mask selection: terrain_preprocess pre-computes BOAT_CLUTTER_MASK_COUNT - shadow masks for waypoints evenly spaced around the simulated route. - These are written as: - map/lidar_processed/shadow_boat_NNN.u8 — one file per boat mask waypoint - NNN = zero-padded index - At runtime, TerrainMap::selectNearestBoatMask() scans the BOAT_CLUTTER_MASK_COUNT - waypoints and returns the index whose position is nearest to the current boat - lat/lon (straight-line distance). The selected mask changes at most once per - 4-second sweep when the boat has moved more than BOAT_MASK_SWITCH_THRESHOLD_M - (default 500 m) from the last selected waypoint position. - - Since Bellingham Bay is open water, the major terrain shadowing features - (Chuckanut Mountain, Eliza Island, Lummi Island) are visible from most bay - positions. The nearest-mask approximation introduces negligible error within - the 500 m switching threshold. - - The terrain clutter shader receives the boat position offset as a translation - uniform (u_radarOffsetM, a vec2) in addition to the bearing offset rotation. - The shader adds this offset when sampling the polar texture so coastline - features appear at their correct positions relative to the moving radar. - -PER-SCOPE TERRAIN BEHAVIOR - - Marine A-Scope: - Land returns appear as stable blips at fixed ranges on the current - antenna bearing. Concrete structures give strong returns; soil/hills - give moderate returns; shadowed areas return nothing. Period - operators were trained to discard stable blips when searching for - moving targets. - - Marine PPI (fixed platform and boat heading offset): - Land clutter is fully visible. Coastline, hills, piers, and - breakwaters paint exactly as on a real period marine radar. The - clutter texture updates once per 4-second sweep rotation. - - ATC PPI: - Moving Target Indicator (MTI) cancellation suppresses land clutter. - Controlled by ATC_TERRAIN_CLUTTER_SUPPRESSED (default true) in - settings.h. Terrain shadowing of aircraft IS applied — controlled - by ATC_TERRAIN_SHADOW_ENABLED (default true). Aircraft approaching - behind a ridge appear at reduced amplitude or disappear until they - clear the ridge, as on period ASR equipment. - - Chain Home A-Scope: - No terrain data applied. The exhibit scenario faces the English - Channel; the transmitter floodlights the sea. Land returns are - not simulated for this scope. - - PAR: - No terrain clutter. PAR points at a single fixed approach path; - the narrow beam and short range make land returns negligible. - -TERRAIN CLUTTER SHADER - - terrain_clutter.vert / terrain_clutter.frag - Renders the polar clutter texture as a quad overlay on the PPI - scope. Converts screen coordinates to polar, samples the clutter - texture, and outputs P7-compatible phosphor color and alpha so - terrain returns decay on the same timescale as target echoes. - Uniforms: - u_bearingOffsetDeg — display heading correction (default 0.0) - u_radarOffsetM — vec2 (dx_m, dy_m) from fixed marine platform origin - to current radar position; (0,0) for fixed scopes, - non-zero for Boat PPI as boat moves around the bay - u_clutterSuppressed — bool; 1 = suppress (ATC mode) - u_maxRangeM — current scope max range in meters - u_clutterBrightness — TERRAIN_MARINE_CLUTTER_BRIGHTNESS scale - Used by MarinePPIScope, BoatPPIScope, and ATCPPIScope. - -================================================================== - -TERRAIN PREPROCESSING - -================================================================== - -All four raw terrain sources are processed in a single pass by the -offline tool terrain_preprocess, which writes ready-to-use binary grids -to map/lidar_processed/. Must be run before first launch and re-run -whenever TERRAIN_BBOX_* or TERRAIN_PROCESSED_CELL_DEG constants in -settings.h change. - -Sources processed: - map/charts_enc/n48_w123_1arc_v3.tif — USGS 1 arc-second DEM - (~30 m); already WGS84; - covers full ATC radar range - including distant ridges - map/lidar_raw/wa2016_west_dem_J1364939.zip — 2016 LiDAR DEM (~1 m); - projected; requires warp - map/lidar_raw/wa2022_nooksack_dem_J1364940.zip — 2022 LiDAR DEM (~1 m); - projected; requires warp - map/charts_enc/US5WA45M.000 — S-57 ENC; material - classification only; no - elevation data - -TOOL - Build target: terrain_preprocess (separate CMake executable) - Source: src/terrain_preprocess.cpp - Links: GDAL only; NOT linked into the main radar binary - Run: ./terrain_preprocess (from build directory) - -PIPELINE (runs in order) - 1. Open USGS DEM (n48_w123_1arc_v3.tif) directly via GDAL — already - in WGS84, no warp needed. Crop to TERRAIN_BBOX_LAT/LON bounds. - 2. Unzip both LiDAR archives to a temp directory. - 3. Inventory LiDAR tiles — enumerate .tif / .img files, check CRS - and native resolution of each survey. - 4. Merge tiles within each LiDAR survey into a GDAL VRT mosaic. - 5. Warp both LiDAR surveys to WGS84 (EPSG:4326) at - TERRAIN_PROCESSED_CELL_DEG resolution. Crop to bounding box. - 6. Three-way elevation merge — priority order, highest to lowest: - LiDAR 2022 (Nooksack survey) — highest resolution, newest vintage - LiDAR 2016 (western survey) — fills gaps in 2022 coverage - USGS DEM (1 arc-second) — floor; covers full extent of all - radar scopes including distant - terrain beyond LiDAR coverage - (Cascades, far ridges for ATC shadow) - Any cell with no data from any source → elevation 0.0 m (water). - 7. Material classification: - Load S-57 ENC (US5WA45M.000) via GDAL/OGR. - Apply classification rules described in TERRAIN section above. - 8. Compute shadow masks using radial elevation-angle march along each - azimuth bearing for each of the following radar positions: - - Fixed marine platform (lat 48.7436, lon -122.5647) - - ATC tower at BLI - - BOAT_CLUTTER_MASK_COUNT boat waypoints from BOAT_SIM_WAYPOINTS[] - All shadow masks use the same algorithm; only the origin differs. - 9. Write to map/lidar_processed/: - elevation.f32 float32 row-major grid, meters, WGS84 - material.u8 uint8 per cell (0=water 1=soil 2=rock 3=concrete) - shadow_marine.u8 uint8 visibility mask for marine radar - shadow_atc.u8 uint8 visibility mask for ATC radar - shadow_boat_000.u8 … - shadow_boat_NNN.u8 uint8 visibility masks for boat waypoints; - NNN = zero-padded waypoint index - terrain_meta.json grid dimensions, lat/lon origin, cell size, - checksums for all four source files, - processing date, - boat mask waypoint lat/lon list - -RUNTIME VALIDATION - TerrainMap reads terrain_meta.json at startup and compares the stored - bounding box and cell-size values against current settings.h constants. - If they differ it prints a warning and continues with stale data: - WARNING: terrain data was built with different bounding box or - cell size — re-run terrain_preprocess before exhibit launch. - The exhibit does not crash; it runs with the old grid. - -OUTPUT FILES (map/lidar_processed/) - elevation.f32 — float32 elevation grid - material.u8 — uint8 material classification grid - shadow_marine.u8 — uint8 line-of-sight mask, marine radar - shadow_atc.u8 — uint8 line-of-sight mask, ATC radar - shadow_boat_000.u8 … — uint8 line-of-sight masks for boat waypoints - shadow_boat_NNN.u8 (BOAT_CLUTTER_MASK_COUNT files total) - terrain_meta.json — metadata, provenance record, and boat mask - waypoint lat/lon list used for nearest-mask lookup - - These files are the only terrain inputs at runtime. - The raw source files in map/lidar_raw/ and map/charts_enc/ are - never opened by the exhibit binary. - -================================================================== - -BOAT SCENARIO - -================================================================== - -The boat scenario (scope 6 — Police Patrol Boat PPI) simulates a Bellingham -Police Department patrol vessel making its waterfront patrol. The radar is a -6 kW professional open-array unit (1.9° beamwidth), not the same hardware as -the fixed coastal marine radar. The radar origin moves with the boat every sweep. - -PATROL ROUTE FILE — data/patrol_route.json - - Loaded by the Simulator at startup. Not compiled in — the route can be - refined without a rebuild. Format (approximate): - - { - "waypoints": [ - { "lat": 48.7530, "lon": -122.5150, "speed_kt": 10.0, - "zone": "Ferry lane — open waterfront" }, - { "lat": 48.7480, "lon": -122.5050, "speed_kt": 4.0, - "zone": "Near Squalicum breakwater" }, - { "lat": 48.7460, "lon": -122.5120, "speed_kt": 10.0, - "zone": "Open waterfront west" }, - { "lat": 48.7380, "lon": -122.5200, "speed_kt": 10.0, - "zone": "Boulevard Park approach" }, - { "lat": 48.7340, "lon": -122.5150, "speed_kt": 4.0, - "zone": "Taylor Dock area" }, - { "lat": 48.7320, "lon": -122.5050, "speed_kt": 4.0, - "zone": "Community Boating Center" } - ], - "loop": "reverse" - } - - "loop": "reverse" means the boat reaches the last waypoint then reverses - direction back through the list — a back-and-forth patrol, not a closed loop. - All coordinates are open water; marina and Whatcom Waterway entry deferred. - Adjust lat/lon values to keep the vessel in navigable water once the ENC - coastline is loaded. Values above are starting approximations. - -BOAT NAVIGATION SIMULATION (Simulator, Thread 4) - - The Simulator maintains a BoatNavigator sub-object that loads the JSON route - at startup and advances the vessel each time TrafficCop polls. Data returned - to TrafficCop alongside the regular target list: - boat_lat_deg — current latitude (degrees WGS84) - boat_lon_deg — current longitude (degrees WGS84) - boat_heading_deg — current true heading (degrees, 0 = north) - boat_speed_kts — current speed from active waypoint segment - boat_zone_str — zone label string for left panel display - - TrafficCop writes these to SharedRenderState under Mutex A. - Thread 1 reads them every frame when BoatPPIScope is active. - - Navigation algorithm (runs in Simulator::poll(), Thread 4): - 1. Compute great-circle bearing from current position to next waypoint. - 2. Rotate boatHeadingDeg toward that bearing at up to - BOAT_HEADING_TURN_RATE_DEG_S per elapsed second (clamped). - 3. Advance position along current heading at the current segment speed_kt. - 4. If distance to next waypoint < BOAT_WAYPOINT_ARRIVAL_M, advance index. - On reverse-loop: flip traversal direction at each end. - 5. Store updated state in BoatNavigator; return to TrafficCop on poll. - -TARGET PROJECTION FOR MOVING RADAR ORIGIN - - For fixed scopes, target positions are projected from a known constant origin. - For the Boat PPI, TrafficCop recalculates each target's polar coordinates - relative to the boat's current position after every poll. - - Flat-earth projection (adequate for 2 nm max range): - dx_m = (target_lon − boat_lon) × cos(boat_lat × π/180) × METERS_PER_DEGREE - dy_m = (target_lat − boat_lat) × METERS_PER_DEGREE - range_m = sqrt(dx_m² + dy_m²) - bearing_deg = atan2(dx_m, dy_m) × 180/π (adjusted to 0–360, CW from north) - - METERS_PER_DEGREE = 111320.0 (constexpr in settings.h). - Result (range_m, bearing_deg, brightness) stored in TargetBuffer under Mutex B. - -HEADING MARKER RENDERING - - BoatPPIScope::renderHeadingMarker() runs inside render() after all phosphor - content, using the graticule shader so the line never decays. - - Geometry: dashed line from scope center to - center + BOAT_HEADING_MARKER_FRACTION × scope_radius - in the direction (boatHeadingDeg + bearingOffsetDeg). - Color: BOAT_HEADING_MARKER_COLOR (default white). - Dash/gap: BOAT_HEADING_MARKER_DASH_PX / BOAT_HEADING_MARKER_GAP_PX. - -DISPLAY MODE LOGIC - - Every frame, BoatPPIScope computes: - float diff = fabs(fmod(bearingOffsetDeg − boatHeadingDeg + 540.0f, 360.0f) − 180.0f) - mode = (diff <= BOAT_HEADUP_TOLERANCE_DEG) ? "Head-up" : "North-up" - Rendered as white text in the left-panel status area. - -TERRAIN CLUTTER AND BREAKWATER SHADOWS - - See SHADOW / LINE-OF-SIGHT MASKING → Boat PPI scope scenario for the - mask-selection algorithm and shadow_boat_NNN.u8 file set. - - The Squalicum Harbor outer breakwater is a significant shadow-caster. - From any open-water patrol position the interior of the marina basin is - shadowed — nothing behind the breakwater is visible. This is realistic - and is visible on the scope as a sharp shadow arc on the far side of the - breakwater return. - - At the start of each 4-second sweep, BoatPPIScope::updateLandClutter() calls: - 1. TerrainMap::selectNearestBoatMask(boatLatDeg, boatLonDeg) - 2. LandClutter::generateForBoat(boatLatDeg, boatLonDeg, maskIndex) - 3. Upload new polar clutter texture to GPU. - If the mask index is unchanged from the previous sweep, steps 2–3 are skipped. - - Terrain clutter shader receives: - u_radarOffsetM = vec2( - (boatLon − MARINE_PLATFORM_LON) × cos(boatLat × π/180) × METERS_PER_DEGREE, - (boatLat − MARINE_PLATFORM_LAT) × METERS_PER_DEGREE) - -V1 GEOMETRY SCOPE (open water only — marina deferred) - - - Outer shoreline of Bellingham Bay - - Squalicum Harbor outer breakwater (solid, strong return, shadow-caster) - - Ferry terminal structure (Bellingham Cruise Terminal area) - - Taylor Dock pier outline (weak return — wood, but pilings visible) - - Boulevard Park shoreline - Internal marina dock fingers, Whatcom Waterway channel walls, and Georgia - Pacific / Waterfront District structures are deferred until the patrol - route is extended into those areas in a future version. - - -IMPORTANT NOTES ON IMPLEMENTATION - -To facilitate debugging: - -1. All p7 persistent stuff should be in one shader; with uniforms that are set by - the settings.h file for time of decay and anything else to facilitate debugging - The p7 persistent shader itself shall be able to be turned off by way of the settings - file. This would allow debugging of the strike (blue) data for each scope. -2. The blue strike activity (prior to persistence) should have a debugging variable - in settings.h so I could do a persistence evaluation by my eyes without the persistence - shader -3. each scope shall have its own shaders for the target activity (what is struck in - the blue color on the screen). This will allow debugging. Note that these shall - have on/off toggles. -4. The three land features (shoreline, terrain, and lidar) should each have their own shaders - to facilitate debugging. Each of these shaders should be turned off and on via settings in - the settings.h file so that I can try each one one at a time. - -C++ files for the CPU should be divided as follows: - -1. Programs that feed the shaders for each scope -2. Receiving program (the traffic cop) for handling targets for each raspberry pi as well as - the simulator. -3. The simulator -4. Initialization of the display -5. Loading the shaders -6. handling controls from keyboard for each scope -7. Each scope's general operation including targets, graticules, max range, -8. The code (now stubs) that handles input from a real control panel with knobs. - - -DO NOT CODE ANYTHING +Controls to affect the behavior of the scopes; (these first implemented using keyboard +strokes; later when physical controls are completed, the keyboard controls will be removed. diff --git a/build_progress.md b/build_progress.md index e12c017..e69de29 100644 --- a/build_progress.md +++ b/build_progress.md @@ -1,152 +0,0 @@ -# Build Progress Checklist -# Museum Vintage Radar Exhibit — C++ / OpenGL -# Update this file as each item is completed. -# At the start of a new session, read this file to Claude to resume. - -## HOW TO USE -- Mark completed files with [x] -- Note any partial files with [~] and add a comment -- If a session ends mid-task, note it under RESUME POINT below - ---- - -## RESUME POINT -*(update this when a session ends mid-task)* - -Last completed: Marine PPI builds and runs cleanly (build_progress.md updated). -In-progress: — -Next task: Marine A-Scope (scope_ascope + scope_marine_a) or ATC PPI - ---- - -## CONFIGURATION - -- [x] src/settings.h — all constexpr constants -- [x] CMakeLists.txt — builds main radar binary; terrain_preprocess commented out - ---- - -## CORE / INFRASTRUCTURE - -- [x] src/main.cpp — entry point; GLFW/GLAD init; thread launch -- [x] src/shared_render_state.h — Mutex A; state vars Thread 1 reads each frame -- [x] src/shared_render_state.cpp -- [x] src/target_buffer.h — Mutex B; target data between TrafficCop & Simulator -- [x] src/target_buffer.cpp - ---- - -## SCOPE FRAMEWORK - -- [x] src/scope.h — abstract Scope base; auto-advance timer -- [x] src/scope.cpp -- [x] src/scope_manager.h — owns scope list; s/S switching; 120s timer -- [x] src/scope_manager.cpp - ---- - -## SCOPES — INDIVIDUAL - -- [x] src/scope_intro.h — Exhibit Introduction (text only) -- [x] src/scope_intro.cpp -- [ ] src/scope_ascope.h — abstract AScope (A-scope shared behavior) -- [ ] src/scope_ascope.cpp -- [ ] src/scope_marine_a.h — Marine A-Scope; P1; graticule swap animation -- [ ] src/scope_marine_a.cpp -- [ ] src/scope_chain_home.h — Chain Home A-Scope; P2; goniometer; PRF; calibrator -- [ ] src/scope_chain_home.cpp -- [x] src/scope_ppi.h — abstract PPIScope; P7 phosphor; graticule; cursor; held-keys -- [x] src/scope_ppi.cpp -- [x] src/scope_marine_ppi.h — Marine PPI; 4s sweep; 2/4/6 mi ranges; beam-painted rings -- [x] src/scope_marine_ppi.cpp -- [ ] src/scope_boat_ppi.h — Boat PPI; moving origin; heading marker; patrol route -- [ ] src/scope_boat_ppi.cpp -- [ ] src/scope_atc_ppi.h — ATC PPI; 5s sweep; 5/10/15/20 mi ranges -- [ ] src/scope_atc_ppi.cpp -- [ ] src/scope_par.h — PAR; dual stacked scopes; az + el; 30 Hz -- [ ] src/scope_par.cpp - ---- - -## RENDERING SUPPORT - -- [x] src/phosphor.h — P7 decay/persistence; ping-pong FBO -- [x] src/phosphor.cpp -- [x] src/graticule.h — incandescent bearing ring; ticks; 15° labels; cursor -- [x] src/graticule.cpp -- [x] src/left_panel.h — FreeType TextRenderer + LeftPanel description pane -- [x] src/left_panel.cpp - ---- - -## TARGET PIPELINE (THREADS 2 & 4) - -- [x] src/traffic_cop.h — Thread 2; polls Simulator & RPi receivers at 4 Hz -- [x] src/traffic_cop.cpp -- [x] src/simulator.h — Thread 4; 7 simulated targets; flat-earth projection -- [x] src/simulator.cpp -- [x] src/rpi_receiver.h — stub; always returns 0 targets -- [x] src/rpi_receiver.cpp -- [x] src/knob_panel.h — Thread 3; hardware stub; idles until encoders wired -- [x] src/knob_panel.cpp - ---- - -## TERRAIN (deferred — not part of Marine PPI phase) - -- [ ] src/terrain_map.h -- [ ] src/terrain_map.cpp -- [ ] src/land_clutter.h -- [ ] src/land_clutter.cpp -- [ ] src/terrain_preprocess.cpp — STANDALONE offline tool; links GDAL only - ---- - -## DATABASE PANEL (deferred) - -- [ ] src/db_panel.h -- [ ] src/db_panel.cpp - ---- - -## DEAR IMGUI (deferred — needed only for --database mode) - -- [ ] src/imgui/imgui.h -- [ ] src/imgui/imgui.cpp -- [ ] src/imgui/imgui_impl_glfw.h -- [ ] src/imgui/imgui_impl_glfw.cpp -- [ ] src/imgui/imgui_impl_opengl3.h -- [ ] src/imgui/imgui_impl_opengl3.cpp -- [ ] src/imgui/imgui_draw.cpp -- [ ] src/imgui/imgui_tables.cpp -- [ ] src/imgui/imgui_widgets.cpp - ---- - -## SHADERS - -- [x] shaders/sweep.vert — fullscreen quad pass-through -- [x] shaders/sweep.frag — P7 ping-pong decay + beam + rings + targets -- [x] shaders/phosphor.vert — fullscreen quad pass-through -- [x] shaders/phosphor.frag — P7 color curve; bloom; scope circle clip -- [x] shaders/graticule.vert — screen-pixel → NDC for line geometry -- [x] shaders/graticule.frag -- [x] shaders/text.vert — glyph quad (xy=screen pos, zw=atlas UV) -- [x] shaders/text.frag — samples GL_RED atlas; applies text color -- [x] shaders/bloom.vert — stub (bloom post-process; not yet wired in) -- [x] shaders/bloom.frag -- [ ] shaders/terrain_clutter.vert — deferred with terrain -- [ ] shaders/terrain_clutter.frag - ---- - -## DATA FILES - -- [x] data/patrol_route.json — boat waypoints stub (used by future BoatPPIScope) - ---- - -## COUNTS -Total items: 68 -Completed: 38 -Remaining: 30 (A-scopes, Boat PPI, ATC PPI, PAR, terrain, DB panel, ImGui, 2 shaders) diff --git a/claude.save b/claude.save new file mode 100644 index 0000000..6ded8ae --- /dev/null +++ b/claude.save @@ -0,0 +1,1733 @@ +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, GDAL (libgdal-dev) + + +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 +./map +./map/lidar_processed +./map/charts_enc +./map/charts_enc/US5WA45M.000 +./map/charts_enc/n48_w123_1arc_v3.tif +./map/lidar_raw +./map/lidar_raw/wa2022_nooksack_dem_J1364940.zip +./map/lidar_raw/wa2016_west_dem_J1364939.zip + + +================================================================== + +GENERAL STUFF + +================================================================== + +COLORS + +1. P7 Strike Color (when things are written to p7 = Blue white Hex #F0F8FF +2. P7 Persistence (during persistence after strike = Yellow Hex #F2BF1C +3. All Graticules and PPI Cursors are incandescent #47 lamp Hex #FFF4CC + This is for bearing rings on ppi scope as well as bearing ticks and + bearing degree texts +4. Left Panel Text for description is white hex #FFFFFF except for controls + as described below +5. Control operation via keyboard key strokes are light red Hex #FFCCCB +6. Control operation description are light green #64E3A1 +7. Status text panel under scope are cyan hex #00FFFF +8. Note that all targets are to be p7; this includes land features and + active targets. Range rings on ppi are also p7 + Please note that range rings shall be fixed brightness; not affected + by sensitivity. +9. Range markings and range calibration pips shall be p7 on the chain + home scope. +10. Center line on precision approach radar horizontal and guide path + on precision approach radar vertical shall be p7 +11. Rain noise as well as water wave noise shall also be p7 and be + affected by gain as well as noise filtering controls +12. Please note for each color in this table, there shall be a variable + in the settings.h file that can be used to change the color. Values should be in hex. +13. The marine a scope display is p1 phosphor (green) +14. The chain home scope display is p2 phosphor (blue green with some persistence) + +================================================================== + +Please note that all on-screen text shall be white and fully +illuminated and is not subject to phosphor persistence or decay. + +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. + +Please note that controls are used for the current scope that is selected. When you move to another scope or to scope +1 after leaving the Exhibit introduction screen; the controls must be reset for the scope you are entering + +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 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 max range setting shall be included in the status text window below the scope + + 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. + + The sliding out, bare crt, and sliding in should all be settable in settings.h + + 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. + + The status for goniometer switch shall be shown in the status text windo below + the scope as well as the goniometer setting in degrees as selected by the switch + + 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. + + The setting for the range should be on the status text panel below the scope. + + 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. All PPI Scopes (common features) + All three ppi scopes (marine stationary; marine police boat; and Air Traffic Control + Have the following common features: + + When the operator changes max range (u/d keys), the new ring geometry + takes effect only for the sector the sweep is currently painting. The + rest of the scope still holds the old ring positions in the phosphor, + glowing and fading normally. As the sweep continues rotating, each + sector it reaches is repainted with the new ring geometry, replacing the + old. One full sweep rotation after the change, the transition is complete + and only the new rings remain. This is physically correct — the phosphor + holds whatever the beam last wrote. No special transition animation is + needed; the behavior emerges naturally from the phosphor model. + + Note on range. If cursor range is beyond 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 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 holding key down. + + 5. Marine PPI Scope specific items: + + This scope is for a fixed location marine radar. It is for marine traffic control. + 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 + + + + 6. Police Patrol Boat PPI + + This scope shows the radar display aboard a simulated Bellingham Police + Department patrol vessel making its routine waterfront patrol. Unlike all + other scopes in this exhibit the radar origin is not fixed — it moves with + the boat, making this a genuinely different operational experience. + + THE PATROL VESSEL AND ITS RADAR + The vessel carries a professional-grade open-array radar: 6 kW peak power, + 24-inch open array, ~1.9° horizontal beamwidth, mounted 3–4 m above the + waterline on a flybridge or radar arch. This is a working law-enforcement + vessel, not a recreational boat; the equipment reflects that. The narrower + beamwidth (vs a recreational radome's 4–6°) gives better azimuth resolution + and less sea clutter per range cell — important when searching for small + targets such as kayakers and stand-up paddleboards near the ferry lane. + + MAX RANGE: 2 miles. Range steps: 0.5 / 1 / 2 miles. + The patrol mission is close-in situational awareness. The tight range steps + let the officer zoom into the marina entrance, Whatcom Waterway, or the + ferry lane without switching to a different scope. + + RADAR HORIZON: ~4.3 nm at 3.5 m antenna height. Range is mission-limited + (2 miles) rather than horizon-limited. + + PATROL ROUTE (v1 — open water only) + The simulated vessel follows a continuous back-and-forth patrol of the + working waterfront at variable speed. Entry into Squalicum Marina and + Whatcom Waterway is deferred to a future version; v1 stays in open water + where the radar geometry is straightforward. + + Speed by zone: + Open waterfront / ferry lane: 10 knots + Near docks and breakwater: 4 knots + Route (loaded from data/patrol_route.json at startup): + Whatcom Waterway entrance → ferry terminal → Boulevard Park → + Taylor Dock → Community Boating Center → reverse and repeat + + SMALL TARGET SCENARIO — FERRY LANE + The Bellingham terminal serves Alaska Marine Highway ferries up to 400 ft. + The simulator places a scripted stand-up paddleboarder on a slow drift + across the ferry departure lane, plus random kayakers near the harbor mouth. + These are marginal radar targets: low RCS, no metal, minimal freeboard. + At 10 kt approach in light chop the paddleboarder may show as a faint + intermittent blip or vanish into sea clutter entirely. Visitors can try the + wave clutter filter (keys 5/6) and observe how suppressing clutter can also + suppress the very target they are looking for. This is the central teaching + moment of this scope: radar does not see everything. + + DISPLAY ORIENTATION + Default is North-up (000° at top). The k/j keys rotate the display offset. + Matching the offset to the boat's heading puts the bow at the top — Head-up + mode. The left panel labels the current mode and shows the heading marker, + a white dashed line from scope center toward the bow, drawn after all + phosphor content so it never decays. + + The heading and the range max settings shall be indicated on the status text + panel below the scope + + TERRAIN AND BREAKWATER CLUTTER + The concrete Squalicum Harbor outer breakwater is a strong radar return and + a significant shadow-caster. Everything behind the breakwater from the + patrol boat's point of view is shadowed — no return. The marina interior is + not visible from open water. This is realistic and visible on the scope. + Coastline, piers, and the ferry terminal structure also appear as clutter. + Shadow masks are pre-computed for patrol route waypoints by + terrain_preprocess and selected at runtime by nearest-waypoint lookup. + + Left panel status (below description text): + Zone: [plain text, e.g. "Ferry lane — open waterfront"] + Boat pos: XX.XXXX°N XXX.XXXX°W + Boat heading: XXX°T + Boat speed: X.X kt + Display mode: North-up (or Head-up) + Cursor range: X.X nm + Cursor brg: XXX°T + Max range: X.X mi + + 7. Air Traffic PPI Scope - + + 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 beyond 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. + 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 increase movement for holding key down. + + 8. 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). + Both the azimuth scope and the elevation scope should be the same size + 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 │ Boat 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 │ Display offset right (boat heading) │ │ │ │ ✓ │ ✓ │ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤ + │ j │ Display offset left (boat heading) │ │ │ │ ✓ │ ✓ │ │ │ + ├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤ + │ [ │ 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 │ │ ✓ │ ✓ │ ✓ │ ✓ │ │ │ + └─────┴─────────────────────────────────────┴───────┴──────────┴──────────────┴────────────┴──────────┴─────────┴─────┘ + +Note: k/j (display bearing offset) are for marine PPI scopes only. The fixed ATC tower +at BLI has no heading offset need. On the fixed Marine PPI, k/j demonstrate North-up vs. +Head-up orientation as a teaching aid. On the Boat PPI, k/j are operationally meaningful: +zero offset = North-up (chart-style); offset matching boat heading = Head-up (bow at top). + +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. + +The text status panel under each scope shall show, in addition to notes above, +the bearing and range for the cursor in the status text panel below the scope. +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. + +================================================================== + +CLASS DESIGN AND FILE LAYOUT + +================================================================== + +Class Hierarchy: + + Scope (abstract base) + ├── ExhibitIntro + ├── AScope (abstract) + │ ├── MarineAScope + │ └── ChainHomeAScope + ├── PPIScope (abstract) + │ ├── MarinePPIScope + │ ├── BoatPPIScope + │ └── 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 + +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 + - P2 phosphor (blue green) + - 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) + - Incandescent 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 + - There shall be a gain independent channel for the p7 phosphor + that shall not be impacted by the operator gain control. + - Range rings are beam-painted per sweep sector with P7 persistence and decay; + operator gain does not dim the rings + - No range ring labels. That era had no text in the p7. + + IMPLEMENTER CHECKLIST — required in every new PPIScope subclass: + 1. computeRingRadii(): multiply each normalised ring radius by + GRAT_INNER_RING_FRAC (same as MarinePPIScope). Omitting this + places the outer ring at the scope boundary where it is clipped + and hidden behind the graticule. Target positions are scaled + automatically by PhosphorRenderer::update(); ring radii are not. + 2. No labeling of range rings.That era did not have them + +RANGE POSITION NORMALISATION + +All ring radii and target range values are normalised so that +max-range maps to GRAT_INNER_RING_FRAC (0.915), NOT 1.0. + +Normalised 1.0 is the outer edge of the phosphor circle (scope boundary). +The bearing graticule overlay occupies 0.915 to 0.985 of scope radius. +If max-range mapped to 1.0, the outer ring would sit at the scope +boundary — half-clipped by the sweep shader's rng > 1.0 early-exit and +visually hidden behind the graticule outer ring. + +================================================================== + + +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 + +BoatPPIScope : public PPIScope + - Direct subclass of PPIScope (not of MarinePPIScope) + - Range steps: 0.5 / 1 / 2 miles; sweep time 4 s; phosphor P7 + - Radar parameters: 6 kW, 1.9° open array (distinct from 30 kW / 0.5° coastal) + - Radar origin = boat lat/lon from SharedRenderState, updated every sweep + - Variable patrol speed per route segment (loaded from data/patrol_route.json) + - Heading marker: white dashed line from scope center toward boat heading; + drawn after all phosphor content so it always appears fully bright + - Nearest pre-computed shadow mask selected each sweep via + TerrainMap::selectNearestBoatMask() — no runtime ray-marching + - Display mode indicator: "North-up" / "Head-up" based on offset vs heading + - Left panel status: zone text, lat/lon, boat heading, speed, display mode + - Keys: u/d (range 0.5/1/2), r/l (cursor bearing), t/y (cursor range), + k/j (display offset), 1/2 (gain), 3/4 (rain filter), 5/6 (wave filter) + +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 on top and elevation below + - 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, P2 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/scope.cpp / scope.h = scope abstract +src/ascope.cpp /ascope.h = a scope abstract and shared a scope logic +src/marine_ascope.cpp / marine_ascope.h - MarineAScope +src/chain_ascope.cpp / chain_ascope.h - ChainHomeAScope +src/ppi_scope.cpp / ppi_scope.h - ppi scope abstract +src/ppi_scope_marine.cpp / ppi_scope_marine.h - marine ppi scope abstract +src/ppi_scope_marine_tower_scope.cpp / ppi_scope_marine_tower_scope.h - MarineTowerScope +src/ppi_scope_police_boat.cpp /ppi_scope_police_boat.h - MarinePoliceScope +src/ppi_scope_atc.cpp / ppi_scope_atc.h - AirTrafficScope +src/par_scope.cpp / src/par_scope.h - PARScope - note; this is for precision approach radar +src/status_text.cpp / status_text.h - StatusText - note; this is for status text below scope +src/land_feature.cpp / land_feature.h - LandFeature - note; this is for land feature terrain, shore, lidar +src/descriptive_text.cpp / descriptive_text.h - DescriptiveText note; this is for left panel text + including keyboard control descriptions +src/traffic_cop.cpp / src/traffic_cop.h - InputTrafficCop note; receiving data from rpis and simulator +src/simulator.cpp / simulator.h - TargetSimulator +src/rpi_receiver.cpp / src/rpi_receiver.h - RPIReceiver +src/keyboard_control.cpp / keyboard_control.h - KeyBoardControls +src/physical_control.cpp / physical_control.h - PhysicalControls +src/shared_render_state.cpp / shared_render.h - SharedRenderState +src/scope_manager.cpp / scope_manager.h - ScopeManager + +Shader pairs (include .vert and .frag + +P7_persistent_stage - having to do with the persistent (yellow green) of the p7 phosphor +marine_ascope_targets - handling marine ascope targets +marine_ascope_land - handling land for ascope +marine_ascope_bearing - handling marine a scope bearing +marine_ascope_graticule - handling changing marine ascope graticules +chainhome_ascope_graticules - handling chain home a scope graticules +chainhome_ascope_targets - handling chain home targets including the + goniometer operation +ppi_scope_graticules - handling all ppi scope graticules +ppi_scope_target - handling target for all ppi scopes +terrain_clutter - for terrain +par_scope_graticule - handling graticules for Precision Approach Radar Scopes +par_scope_targets - handling targets for Precision Approach Radar Scopes +bloom - two pass gaussian blur on pixels above luminance threshold, three tuning constants in settings.h +rain_noise - noise caused by rain amount to be set by a random CPU function +sea_wave_noise - noise caused by wind on waves; stronger when close to radar ; amount to be set by CPU function + +================================================================== + +PHOSPHOR FBO ARCHITECTURE + +================================================================== + +The P7 phosphor simulation uses a single floating-point offscreen +texture (the phosphor FBO) to store the current glow state of every +pixel in the scope. The texture format is RGBA16F. + +CHANNEL ASSIGNMENTS + R channel — gain-affected content: targets, land clutter, noise. + Multiplied by u_gain during the display pass. + G channel — gain-independent content: range rings only. + Displayed at full brightness regardless of u_gain. + B, A — reserved for future use. + +THREE PASSES PER FRAME + + 1. DECAY PASS (P7_persistent_stage.vert / .frag) + Reads the phosphor FBO and writes back to it with both R and G + channels multiplied by P7_DECAY_FACTOR (default ~0.985 at 60 Hz, + settable in settings.h). Both channels decay at the same rate. + When P7_PERSISTENCE_ENABLED is false in settings.h this pass is + skipped — the FBO is cleared each frame — allowing debugging of + raw strike data only. + + 2. STRIKE PASS (per-scope target shaders and terrain_clutter) + Runs during the sweep. Each fragment that the beam illuminates + writes a brightness value into the appropriate channel: + - Targets, clutter, noise → write to R, leave G unchanged + - Range rings → write to G, leave R unchanged + The brightness value is computed from the radar equation result + uploaded per-target via SSBO or uniform array. + + 3. DISPLAY PASS (composited in each scope's render method) + Reads both channels and maps to P7 colors: + target_glow = phosphorFBO.R × u_gain + ring_glow = phosphorFBO.G (no gain factor) + output = P7_color(target_glow) + P7_color(ring_glow) + P7_color() maps the glow value to the P7 palette: high values + produce P7_STRIKE_COLOR (#F0F8FF), lower values produce + P7_PERSISTENCE_COLOR (#F2BF1C). + +DEBUGGING SUPPORT + P7_PERSISTENCE_ENABLED (settings.h) — set false to skip decay pass; + each frame shows only raw strike data. + P7_STRIKE_DEBUG_ALPHA (settings.h) — scales the strike brightness + to evaluate persistence timing by eye. + +================================================================== + +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 seven 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: Marine PPI — Boat Scenario → + + ┌──────┬────────────────────────────────────────────────┐ + │ 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 │ Display offset right / left │ + │ │ (zero = North-up; rotate to match heading │ + │ │ = Head-up — a teaching aid on fixed radar) │ + │ 1/2 │ Gain increase / decrease │ + │ 3/4 │ Rain filter increase / decrease │ + │ 5/6 │ Wave filter increase / decrease │ + └──────┴────────────────────────────────────────────────┘ + +------------------------------------------------------------------ +PANEL 5 — POLICE PATROL BOAT PPI +------------------------------------------------------------------ + + This display is from the radar aboard a Bellingham Police Department + patrol vessel making its routine waterfront patrol. The scope center + is the boat — it moves with the patrol route, not fixed in the bay. + + THE RADAR IS DIFFERENT HERE + The patrol boat carries a professional 6-kilowatt open-array radar + with a 1.9-degree beam — narrower than a cheap boat radar, but wider + than the big 30-kilowatt coastal radar you saw two displays back. The + blips look noticeably fatter than on the coastal radar. The maximum + range here is 2 miles: the patrol mission is close-in harbor work, + not long-range scanning. + + THE FERRY LANE PROBLEM + The Bellingham terminal serves Alaska state ferries up to 400 feet + long. When a ferry departs, anyone in the departure lane — a kayaker, + a paddleboarder — must get clear. Watch the scope carefully: there + is a stand-up paddleboarder drifting slowly near the ferry lane. + Can you spot it? + + Stand-up paddleboards are very hard to see on radar. No metal, almost + no freeboard, small size — the radar echo is barely above the noise. + Try adjusting the wave clutter filter (keys 5 and 6). Turning it up + reduces the sea clutter that is hiding the target — but it may also + suppress the target itself. This trade-off is real. Radar does not + see everything. + + NORTH-UP AND HEAD-UP + By default the scope shows North-up: True North at the top, like a + chart. Press k or j to rotate the display. When the heading marker + (white dashed line) points straight up, you are in Head-up mode — + the bow is at the top. This is how most early marine radars worked. + + Location: Bellingham waterfront patrol, Bellingham Bay, WA. + Targets: AIS vessels, paddleboarder, random kayakers. + + 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 (0.5, 1, 2 mi) │ + │ r/l │ Cursor bearing right / left │ + │ t/y │ Cursor range increase / decrease │ + │ k/j │ Display offset right / left │ + │ │ (0° = North-up; match heading = Head-up) │ + │ 1/2 │ Gain increase / decrease │ + │ 3/4 │ Rain filter increase / decrease │ + │ 5/6 │ Wave filter increase / decrease │ + └──────┴────────────────────────────────────────────────┘ + +------------------------------------------------------------------ +PANEL 6 — 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 │ + │ 1/2 │ Gain increase / decrease │ + │ 3/4 │ Rain filter increase / decrease │ + └──────┴──────────────────────────────────────────┘ + +------------------------------------------------------------------ +PANEL 7 — 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. Table is target_data. + +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 (6–10 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) + +RADAR PARAMETERS — PATROL BOAT (X Band): + Frequency: 9300–9500 MHz (X-band; treat same as marine for exhibit) + Peak power: 6 kW + Antenna type: Open array, 24-inch (610 mm) + Horizontal beamwidth: 1.9 degrees + Vertical beamwidth: 20 degrees + Antenna height: 3.5 m above waterline (flybridge or radar arch) + Radar horizon: ~4.3 nm to sea-level target + Operational max range: 2 miles (mission-limited, not horizon-limited) + + NOTE — beamwidth comparison: + Fixed coastal marine: 0.5° — sharp blips, high azimuth resolution + Police patrol boat: 1.9° — noticeably fatter blips; good exhibit contrast + Consumer radome: 4–6° — poorest resolution (not used in this exhibit) + + NOTE — small target detection in sea clutter: + The narrower 1.9° beam illuminates ~1/3 the sea surface area per range + cell compared to a 5° radome, improving signal-to-clutter ratio by ~5 dB. + Even so, a stand-up paddleboard (RCS ~0.1–0.5 m²) is marginal in any chop. + Detection is realistic only in near-calm conditions at ≤1 mile. + +All radar parameters shall have corresponding constexpr constants +in settings.h so they can be tuned without touching equation code. + +================================================================== + +TERRAIN AND LAND CLUTTER + +================================================================== + +DATA SOURCES + map/charts_enc/US5WA45M.000 + S-57 Electronic Navigational Chart; coastline polygon, + pier/breakwater geometry, named landmarks. Parsed with GDAL/OGR. + + map/charts_enc/n48_w123_1arc_v3.tif + USGS 1 arc-second (~30 m) DEM GeoTIFF. Terrain elevation for the + Bellingham area — Cascades, Chuckanut Mountain, coastal lowlands. + + map/lidar_raw/wa2016_west_dem_J1364939.zip + 2016 western WA LiDAR DEM. Resolves waterfront structures, + breakwaters, piers, Boulevard Park boardwalk, and other man-made + features at ~1 m resolution. + + map/lidar_raw/wa2022_nooksack_dem_J1364940.zip + 2022 Nooksack basin LiDAR DEM. Covers the delta and lowlands + northeast of Bellingham Bay; used for terrain shadowing from the + northeast quadrant. + + All four sources are processed offline by terrain_preprocess into + binary grids in map/lidar_processed/. The raw files are never opened + at exhibit runtime. See TERRAIN PREPROCESSING section below. + + GDAL (libgdal-dev) is a required build dependency for + terrain_preprocess. It is NOT linked into the main radar binary. + +TERRAIN MATERIALS AND RCS + + Each terrain cell is classified as one of four materials. Normalized + backscatter coefficient σ° (linear, m²/m²) is defined in settings.h: + + TERRAIN_SIGMA0_SOIL ~0.010 (−20 dB) — moist vegetated soil + TERRAIN_SIGMA0_ROCK ~0.032 (−15 dB) — exposed rock, rough face + TERRAIN_SIGMA0_CONCRETE ~0.100 (−10 dB) — smooth hard surface; + structures produce + corner-reflector effect + TERRAIN_SIGMA0_WATER_CALM ~0.0003 (−35 dB) — open water, calm; + mostly specular, low + backscatter + TERRAIN_SIGMA0_WATER_ROUGH ~0.010 (−20 dB) — choppy sea, sea clutter + + Classification rules applied by terrain_preprocess at build time: + - Below sea level or inside S-57 coastline polygon → water + - Inside S-57 pier/breakwater/wharf feature → concrete + - Elevation > TERRAIN_ROCK_THRESHOLD_M AND slope + > TERRAIN_ROCK_SLOPE_THRESHOLD_DEG → rock + - All remaining land cells → soil + +RADAR EQUATION FOR TERRAIN (area-extensive / clutter form) + + P_r = (P_t × G² × λ² × σ° × A_cell) / ((4π)³ × R⁴) + + A_cell is the terrain cell area projected along the beam. + Evaluated CPU-side in LandClutter for each illuminated cell. + Result drives per-cell brightness in the clutter texture. + Same radar parameters (P_t, G, λ) used for point targets. + +SHADOW / LINE-OF-SIGHT MASKING + + Computed by terrain_preprocess for each fixed radar location and + stored in the processed data as uint8 shadow masks. Algorithm: march + outward from the radar along each azimuth radial, tracking the maximum + elevation angle seen. Any cell whose surface angle falls below that + maximum is shadowed — clutter amplitude = 0 and target returns through + that cell are attenuated proportionally. + + Shadow masks stored per radar location: + map/lidar_processed/shadow_marine.u8 — marine bay platform + map/lidar_processed/shadow_atc.u8 — BLI ATC tower + + Bearing offset (k/j keys on PPI scopes): + The k/j keys change display heading only — which direction appears + at the top of the scope. They do NOT move the radar geographically. + The shadow mask is unchanged. The terrain clutter shader receives + the offset as a rotation uniform and samples the polar texture at + the offset angle. Zero CPU overhead; no shadow recomputation. + + Boat PPI scope scenario: + When BoatPPIScope is active, SharedRenderState.boatModeActive is set + TRUE. LandClutter uses boatLatDeg / boatLonDeg as the polar grid origin + instead of the fixed marine platform position. + + Shadow mask selection: terrain_preprocess pre-computes BOAT_CLUTTER_MASK_COUNT + shadow masks for waypoints evenly spaced around the simulated route. + These are written as: + map/lidar_processed/shadow_boat_NNN.u8 — one file per boat mask waypoint + NNN = zero-padded index + At runtime, TerrainMap::selectNearestBoatMask() scans the BOAT_CLUTTER_MASK_COUNT + waypoints and returns the index whose position is nearest to the current boat + lat/lon (straight-line distance). The selected mask changes at most once per + 4-second sweep when the boat has moved more than BOAT_MASK_SWITCH_THRESHOLD_M + (default 500 m) from the last selected waypoint position. + + Since Bellingham Bay is open water, the major terrain shadowing features + (Chuckanut Mountain, Eliza Island, Lummi Island) are visible from most bay + positions. The nearest-mask approximation introduces negligible error within + the 500 m switching threshold. + + The terrain clutter shader receives the boat position offset as a translation + uniform (u_radarOffsetM, a vec2) in addition to the bearing offset rotation. + The shader adds this offset when sampling the polar texture so coastline + features appear at their correct positions relative to the moving radar. + +PER-SCOPE TERRAIN BEHAVIOR + + Marine A-Scope: + Land returns appear as stable blips at fixed ranges on the current + antenna bearing. Concrete structures give strong returns; soil/hills + give moderate returns; shadowed areas return nothing. Period + operators were trained to discard stable blips when searching for + moving targets. + + Marine PPI (fixed platform and boat heading offset): + Land clutter is fully visible. Coastline, hills, piers, and + breakwaters paint exactly as on a real period marine radar. The + clutter texture updates once per 4-second sweep rotation. + + ATC PPI: + Moving Target Indicator (MTI) cancellation suppresses land clutter. + Controlled by ATC_TERRAIN_CLUTTER_SUPPRESSED (default true) in + settings.h. Terrain shadowing of aircraft IS applied — controlled + by ATC_TERRAIN_SHADOW_ENABLED (default true). Aircraft approaching + behind a ridge appear at reduced amplitude or disappear until they + clear the ridge, as on period ASR equipment. + + Chain Home A-Scope: + No terrain data applied. The exhibit scenario faces the English + Channel; the transmitter floodlights the sea. Land returns are + not simulated for this scope. + + PAR: + No terrain clutter. PAR points at a single fixed approach path; + the narrow beam and short range make land returns negligible. + +TERRAIN CLUTTER SHADER + + terrain_clutter.vert / terrain_clutter.frag + Renders the polar clutter texture as a quad overlay on the PPI + scope. Converts screen coordinates to polar, samples the clutter + texture, and outputs P7-compatible phosphor color and alpha so + terrain returns decay on the same timescale as target echoes. + Uniforms: + u_bearingOffsetDeg — display heading correction (default 0.0) + u_radarOffsetM — vec2 (dx_m, dy_m) from fixed marine platform origin + to current radar position; (0,0) for fixed scopes, + non-zero for Boat PPI as boat moves around the bay + u_clutterSuppressed — bool; 1 = suppress (ATC mode) + u_maxRangeM — current scope max range in meters + u_clutterBrightness — TERRAIN_MARINE_CLUTTER_BRIGHTNESS scale + Used by MarinePPIScope, BoatPPIScope, and ATCPPIScope. + +================================================================== + +TERRAIN PREPROCESSING + +================================================================== + +All four raw terrain sources are processed in a single pass by the +offline tool terrain_preprocess, which writes ready-to-use binary grids +to map/lidar_processed/. Must be run before first launch and re-run +whenever TERRAIN_BBOX_* or TERRAIN_PROCESSED_CELL_DEG constants in +settings.h change. + +Sources processed: + map/charts_enc/n48_w123_1arc_v3.tif — USGS 1 arc-second DEM + (~30 m); already WGS84; + covers full ATC radar range + including distant ridges + map/lidar_raw/wa2016_west_dem_J1364939.zip — 2016 LiDAR DEM (~1 m); + projected; requires warp + map/lidar_raw/wa2022_nooksack_dem_J1364940.zip — 2022 LiDAR DEM (~1 m); + projected; requires warp + map/charts_enc/US5WA45M.000 — S-57 ENC; material + classification only; no + elevation data + +TOOL + Build target: terrain_preprocess (separate CMake executable) + Source: src/terrain_preprocess.cpp + Links: GDAL only; NOT linked into the main radar binary + Run: ./terrain_preprocess (from build directory) + +PIPELINE (runs in order) + 1. Open USGS DEM (n48_w123_1arc_v3.tif) directly via GDAL — already + in WGS84, no warp needed. Crop to TERRAIN_BBOX_LAT/LON bounds. + 2. Unzip both LiDAR archives to a temp directory. + 3. Inventory LiDAR tiles — enumerate .tif / .img files, check CRS + and native resolution of each survey. + 4. Merge tiles within each LiDAR survey into a GDAL VRT mosaic. + 5. Warp both LiDAR surveys to WGS84 (EPSG:4326) at + TERRAIN_PROCESSED_CELL_DEG resolution. Crop to bounding box. + 6. Three-way elevation merge — priority order, highest to lowest: + LiDAR 2022 (Nooksack survey) — highest resolution, newest vintage + LiDAR 2016 (western survey) — fills gaps in 2022 coverage + USGS DEM (1 arc-second) — floor; covers full extent of all + radar scopes including distant + terrain beyond LiDAR coverage + (Cascades, far ridges for ATC shadow) + Any cell with no data from any source → elevation 0.0 m (water). + 7. Material classification: + Load S-57 ENC (US5WA45M.000) via GDAL/OGR. + Apply classification rules described in TERRAIN section above. + 8. Compute shadow masks using radial elevation-angle march along each + azimuth bearing for each of the following radar positions: + - Fixed marine platform (lat 48.7436, lon -122.5647) + - ATC tower at BLI + - BOAT_CLUTTER_MASK_COUNT boat waypoints from BOAT_SIM_WAYPOINTS[] + All shadow masks use the same algorithm; only the origin differs. + 9. Write to map/lidar_processed/: + elevation.f32 float32 row-major grid, meters, WGS84 + material.u8 uint8 per cell (0=water 1=soil 2=rock 3=concrete) + shadow_marine.u8 uint8 visibility mask for marine radar + shadow_atc.u8 uint8 visibility mask for ATC radar + shadow_boat_000.u8 … + shadow_boat_NNN.u8 uint8 visibility masks for boat waypoints; + NNN = zero-padded waypoint index + terrain_meta.json grid dimensions, lat/lon origin, cell size, + checksums for all four source files, + processing date, + boat mask waypoint lat/lon list + +RUNTIME VALIDATION + TerrainMap reads terrain_meta.json at startup and compares the stored + bounding box and cell-size values against current settings.h constants. + If they differ it prints a warning and continues with stale data: + WARNING: terrain data was built with different bounding box or + cell size — re-run terrain_preprocess before exhibit launch. + The exhibit does not crash; it runs with the old grid. + +OUTPUT FILES (map/lidar_processed/) + elevation.f32 — float32 elevation grid + material.u8 — uint8 material classification grid + shadow_marine.u8 — uint8 line-of-sight mask, marine radar + shadow_atc.u8 — uint8 line-of-sight mask, ATC radar + shadow_boat_000.u8 … — uint8 line-of-sight masks for boat waypoints + shadow_boat_NNN.u8 (BOAT_CLUTTER_MASK_COUNT files total) + terrain_meta.json — metadata, provenance record, and boat mask + waypoint lat/lon list used for nearest-mask lookup + + These files are the only terrain inputs at runtime. + The raw source files in map/lidar_raw/ and map/charts_enc/ are + never opened by the exhibit binary. + +================================================================== + +BOAT SCENARIO + +================================================================== + +The boat scenario (scope 6 — Police Patrol Boat PPI) simulates a Bellingham +Police Department patrol vessel making its waterfront patrol. The radar is a +6 kW professional open-array unit (1.9° beamwidth), not the same hardware as +the fixed coastal marine radar. The radar origin moves with the boat every sweep. + +PATROL ROUTE FILE — data/patrol_route.json + + Loaded by the Simulator at startup. Not compiled in — the route can be + refined without a rebuild. Format (approximate): + + { + "waypoints": [ + { "lat": 48.7530, "lon": -122.5150, "speed_kt": 10.0, + "zone": "Ferry lane — open waterfront" }, + { "lat": 48.7480, "lon": -122.5050, "speed_kt": 4.0, + "zone": "Near Squalicum breakwater" }, + { "lat": 48.7460, "lon": -122.5120, "speed_kt": 10.0, + "zone": "Open waterfront west" }, + { "lat": 48.7380, "lon": -122.5200, "speed_kt": 10.0, + "zone": "Boulevard Park approach" }, + { "lat": 48.7340, "lon": -122.5150, "speed_kt": 4.0, + "zone": "Taylor Dock area" }, + { "lat": 48.7320, "lon": -122.5050, "speed_kt": 4.0, + "zone": "Community Boating Center" } + ], + "loop": "reverse" + } + + "loop": "reverse" means the boat reaches the last waypoint then reverses + direction back through the list — a back-and-forth patrol, not a closed loop. + All coordinates are open water; marina and Whatcom Waterway entry deferred. + Adjust lat/lon values to keep the vessel in navigable water once the ENC + coastline is loaded. Values above are starting approximations. + +BOAT NAVIGATION SIMULATION (Simulator, Thread 4) + + The Simulator maintains a BoatNavigator sub-object that loads the JSON route + at startup and advances the vessel each time TrafficCop polls. Data returned + to TrafficCop alongside the regular target list: + boat_lat_deg — current latitude (degrees WGS84) + boat_lon_deg — current longitude (degrees WGS84) + boat_heading_deg — current true heading (degrees, 0 = north) + boat_speed_kts — current speed from active waypoint segment + boat_zone_str — zone label string for left panel display + + TrafficCop writes these to SharedRenderState under Mutex A. + Thread 1 reads them every frame when BoatPPIScope is active. + + Navigation algorithm (runs in Simulator::poll(), Thread 4): + 1. Compute great-circle bearing from current position to next waypoint. + 2. Rotate boatHeadingDeg toward that bearing at up to + BOAT_HEADING_TURN_RATE_DEG_S per elapsed second (clamped). + 3. Advance position along current heading at the current segment speed_kt. + 4. If distance to next waypoint < BOAT_WAYPOINT_ARRIVAL_M, advance index. + On reverse-loop: flip traversal direction at each end. + 5. Store updated state in BoatNavigator; return to TrafficCop on poll. + +TARGET PROJECTION FOR MOVING RADAR ORIGIN + + For fixed scopes, target positions are projected from a known constant origin. + For the Boat PPI, TrafficCop recalculates each target's polar coordinates + relative to the boat's current position after every poll. + + Flat-earth projection (adequate for 2 nm max range): + dx_m = (target_lon − boat_lon) × cos(boat_lat × π/180) × METERS_PER_DEGREE + dy_m = (target_lat − boat_lat) × METERS_PER_DEGREE + range_m = sqrt(dx_m² + dy_m²) + bearing_deg = atan2(dx_m, dy_m) × 180/π (adjusted to 0–360, CW from north) + + METERS_PER_DEGREE = 111320.0 (constexpr in settings.h). + Result (range_m, bearing_deg, brightness) stored in TargetBuffer under Mutex B. + +HEADING MARKER RENDERING + + BoatPPIScope::renderHeadingMarker() runs inside render() after all phosphor + content, using the graticule shader so the line never decays. + + Geometry: dashed line from scope center to + center + BOAT_HEADING_MARKER_FRACTION × scope_radius + in the direction (boatHeadingDeg + bearingOffsetDeg). + Color: BOAT_HEADING_MARKER_COLOR (default white). + Dash/gap: BOAT_HEADING_MARKER_DASH_PX / BOAT_HEADING_MARKER_GAP_PX. + +DISPLAY MODE LOGIC + + Every frame, BoatPPIScope computes: + float diff = fabs(fmod(bearingOffsetDeg − boatHeadingDeg + 540.0f, 360.0f) − 180.0f) + mode = (diff <= BOAT_HEADUP_TOLERANCE_DEG) ? "Head-up" : "North-up" + Rendered as white text in the left-panel status area. + +TERRAIN CLUTTER AND BREAKWATER SHADOWS + + See SHADOW / LINE-OF-SIGHT MASKING → Boat PPI scope scenario for the + mask-selection algorithm and shadow_boat_NNN.u8 file set. + + The Squalicum Harbor outer breakwater is a significant shadow-caster. + From any open-water patrol position the interior of the marina basin is + shadowed — nothing behind the breakwater is visible. This is realistic + and is visible on the scope as a sharp shadow arc on the far side of the + breakwater return. + + At the start of each 4-second sweep, BoatPPIScope::updateLandClutter() calls: + 1. TerrainMap::selectNearestBoatMask(boatLatDeg, boatLonDeg) + 2. LandClutter::generateForBoat(boatLatDeg, boatLonDeg, maskIndex) + 3. Upload new polar clutter texture to GPU. + If the mask index is unchanged from the previous sweep, steps 2–3 are skipped. + + Terrain clutter shader receives: + u_radarOffsetM = vec2( + (boatLon − MARINE_PLATFORM_LON) × cos(boatLat × π/180) × METERS_PER_DEGREE, + (boatLat − MARINE_PLATFORM_LAT) × METERS_PER_DEGREE) + +V1 GEOMETRY SCOPE (open water only — marina deferred) + + - Outer shoreline of Bellingham Bay + - Squalicum Harbor outer breakwater (solid, strong return, shadow-caster) + - Ferry terminal structure (Bellingham Cruise Terminal area) + - Taylor Dock pier outline (weak return — wood, but pilings visible) + - Boulevard Park shoreline + Internal marina dock fingers, Whatcom Waterway channel walls, and Georgia + Pacific / Waterfront District structures are deferred until the patrol + route is extended into those areas in a future version. + + +IMPORTANT NOTES ON IMPLEMENTATION + +To facilitate debugging: + +1. All p7 persistent stuff should be in one shader; with uniforms that are set by + the settings.h file for time of decay and anything else to facilitate debugging + The p7 persistent shader itself shall be able to be turned off by way of the settings + file. This would allow debugging of the strike (blue) data for each scope. +2. The blue strike activity (prior to persistence) should have a debugging variable + in settings.h so I could do a persistence evaluation by my eyes without the persistence + shader +3. each scope shall have its own shaders for the target activity (what is struck in + the blue color on the screen). This will allow debugging. Note that these shall + have on/off toggles. +4. The three land features (shoreline, terrain, and lidar) should each have their own shaders + to facilitate debugging. Each of these shaders should be turned off and on via settings in + the settings.h file so that I can try each one one at a time. + +C++ files for the CPU should be divided as follows: + +1. Programs that feed the shaders for each scope +2. Receiving program (the traffic cop) for handling targets for each raspberry pi as well as + the simulator. +3. The simulator +4. Initialization of the display +5. Loading the shaders +6. handling controls from keyboard for each scope +7. Each scope's general operation including targets, graticules, max range, +8. The code (now stubs) that handles input from a real control panel with knobs. + + +DO NOT CODE ANYTHING