re doing to facilitate debugging

This commit is contained in:
2026-04-24 23:34:21 -07:00
parent 01c1db41db
commit 73e81ea87b
126 changed files with 204 additions and 24043 deletions

543
CLAUDE.md
View File

@@ -67,19 +67,38 @@ 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.
Exceptions:
Graticule text: should be incandescent for the bearing marks.
Graticule text for all a-scope should be incandescent, not white
and not phosphor as they are dependent on glass graticules with etched
lines and text.
PPI Scope Range Ring Markers Text on the PPI scope range rings shall be blue
fade to yellow green as on p7 phosphor. Which is the same for ppi targets.
Please note that direction as stated here are True directions. 000 is True North
Maximum Range is 6 miles for marine type radar
@@ -120,6 +139,9 @@ description window for each scope. When the main exhibit descriptor screen comes
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
@@ -132,12 +154,14 @@ should be articulated in the descriptive text
2. Marine A-Scope - (horizontal axis is range; vertical axis is amplitude of
return pulse; bearing will be set via a bearing control; current implementation
would be two keys on the keyboard; one key to go clockwise on bearing and another
key would be to go counterclockwise. The A scope phosphor is P1, which is green.
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
@@ -167,6 +191,8 @@ should be articulated in the descriptive text
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
@@ -187,6 +213,9 @@ should be articulated in the descriptive text
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
@@ -215,10 +244,7 @@ should be articulated in the descriptive text
Let's assign key . for toggling between 25 and 12.5 PRF. There is no range selection.
Note on description; this is to reduce use of the shift key.
Because of the slow repetition rate, the phosphor used was P2 (long-persistence green)
so that the targets will still glow between the sweeps and not cause flickering. P2 is
a single-layer green phosphor with longer decay than P1, appropriate for an A-scope at
low PRF. Unlike P7, it does not produce a blue flash or a seconds-long smearing tail.
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
@@ -229,20 +255,9 @@ should be articulated in the descriptive text
Let's assign key n for shrink and m for stretch. (may be ambiguous, but I am running
out of keys. Note in the descriptor.
4. Marine PPI Scope -
marine scopes have the following items in common:
Targets, range rings, and range ring text levels shall be treated the same for
presentation. All are P7 phosphor. Immediate strike by the electron beam is blue.
persistence is green/yellow. Targets, range rings, and range ring labels shall all
persist and fade out together. They should be faded out by the time the sweep
returns to that location.
IMPORTANT — range rings are beam-painted, not a static overlay:
Range rings and their labels are written by the rotating sweep beam on
each pass, exactly like target echoes. They are NOT rendered as a fixed
overlay on top of the scope. This means they are fully subject to P7
persistence and decay just as targets are.
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
@@ -254,20 +269,7 @@ should be articulated in the descriptive text
holds whatever the beam last wrote. No special transition animation is
needed; the behavior emerges naturally from the phosphor model.
The maximum range settings are 6 miles for the marine radar scope
Rings should be 2,4, and 6 miles for marine.
The max range settings for marine ppi will be u for up and d for
down. If you are in the marine ppi, you change only the max range for the marine
ppi. The possible max range values for
the marine radar are 2,4,6
miles.
Marine:
Max is 2; one interim range at 1, final ring at 2
Max is 4; one interim range at 2, final ring at 4
Max is 6; one interim range at 4, final ring at 6
Note on range. If cursor range is beyone max, clamp it to the max.
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.
@@ -278,8 +280,6 @@ should be articulated in the descriptive text
an outer ring. Both inner and outer rings, along with ticks, and the bearing
labels are to be incandescent color.
The sweep time shall be 4 seconds for the marine scope
The sweep direction is clockwise, which means that the antenna
dish rotates clockwise.
@@ -292,9 +292,27 @@ should be articulated in the descriptive text
the scope. In the real day, it was a machanical readout. The key sequence would be
r for bearing to the right and l for bearing for the left; and t for higher range
and y for smaller range. These controls should have slow movement for single stroke; but
gradual for for holding key down.
gradual for holding key down.
5. Police Patrol Boat PPI
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
@@ -349,6 +367,9 @@ should be articulated in the descriptive text
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
@@ -368,12 +389,7 @@ should be articulated in the descriptive text
Cursor brg: XXX°T
Max range: X.X mi
6. Air Traffic PPI Scope -
Targets, range rings, and range ring text levels
All are P7 phosphor. Immediate strike by the electron beam is blue.
persistence is green/yellow. Targets, range rings, and range ring labels shall all
persist and fade out together. They should be faded out by the time the sweep
to that location.
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
@@ -388,7 +404,7 @@ should be articulated in the descriptive text
Max is 15; three interim ranges four total; 4,8,12; final ring at 15
Max is 20, three interim ranges four total; 5,10,15; final ring at 20
Note on range. If cursor range is beyone max, clamp it to the max.
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.
@@ -407,17 +423,16 @@ should be articulated in the descriptive text
The scope has cursor for range and bearing. The cursor consists of a
section of a ring ( 10 degrees) and a cross line for bearing.
The cursor should be yellow (it
a plastic overlay in the period time. Two controls control the cursor; range and
Two controls control the cursor; range and
bearing. Both were physical crank controls. For now, both we need to use key pairs
on the keyboard. A white text indication of range and bearing should be put under
the scope. In the real day, it was a machanical readout. The key sequence would be
r for bearing to the right and l for bearing for the left; and t for higher range
and y for smaller range.
These controls should have slow movement for single stroke; but
gradual for for holding key down.
gradual increase movement for holding key down.
7. Precision approach (PAR for short)
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.
@@ -431,7 +446,7 @@ should be articulated in the descriptive text
at Bellingham Airport (BLI). Two vertically stacked scopes share the
right panel. Top scope: azimuth (lateral deviation vs. range from
touchdown). Bottom scope: elevation (vertical deviation vs. range).
Have the azimuth scope to about 1/3 larger than the elevation scope
Both 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.
@@ -580,13 +595,8 @@ Scope and left window arrangement.
For each scope, put the scope itself on the right hand of the window.
On the left hand of the window will be a text description of that scope.
Underneath each scope's description will be cursor range and bearing from the radar
location; and the setting of maximum range; and the bearing offset; for 0 would
be to have 0 degrees pointing to true north (this is needed if I decide to
implement a radar on a boat. If implemented, use k for bearing to right; and
j for bearing to left. Make note in description that this is only used if this
is a radar on a boat. (perhaps later on, I could add a PPI on a boat scenario)
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.
@@ -600,8 +610,6 @@ different radars. Range and bearing for the precision approach radar will be dif
than any other radar as that radar is located at the end of the runway and scan both
horizontal and vertical.
Please analyze and comment. Please do not generate any code file nor shader files.
==================================================================
CLASS DESIGN AND FILE LAYOUT
@@ -636,7 +644,6 @@ AScope : public Scope (abstract) — shared A-scope behavior:
- Noise floor rendering (rain/wave clutter)
- Incandescent graticule (three horizontal amplitude lines + vertical range lines)
- Bearing control with key-hold acceleration
- Phosphor type as parameter (P1, P2, or P7)
MarineAScope : public AScope
- P1 phosphor (green)
@@ -647,7 +654,7 @@ MarineAScope : public AScope
u and d ignored during graticule swap animation
ChainHomeAScope : public AScope
- P2 phosphor (long-persistence green)
- 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
@@ -657,16 +664,15 @@ ChainHomeAScope : public AScope
PPIScope : public Scope (abstract) — shared PPI behavior:
- Clockwise sweep with P7 phosphor persistence (blue strike, green/yellow decay)
- Incandescent bearing graticule (1-degree ticks, 15-degree labels, inner/outer rings)
- Yellow cursor: 10-degree arc + bearing crossline
- 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;
however they are stored in the GAIN-INDEPENDENT G channel of the phosphor FBO
(see PHOSPHOR FBO ARCHITECTURE below) so operator gain does not dim the rings
- renderRingLabels() — virtual method (default no-op); concrete PPI scopes that
have labelled range rings override this to render mile-distance text labels in
P7 fresh-blue colour at a fixed bearing (RING_LABEL_BRG_DEG = 045°)
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
@@ -674,38 +680,7 @@ PPIScope : public Scope (abstract) — shared PPI behavior:
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. Override renderRingLabels() using the same pattern as
MarinePPIScope::renderRingLabels() but with the scope's own
ring-mile table. The base-class no-op produces no labels.
The p7Color() fix, two-channel FBO gain-separation, and target
position scaling are all automatic via the shared PhosphorRenderer
and shaders — no per-scope action required for those.
==================================================================
PHOSPHOR FBO ARCHITECTURE
==================================================================
The phosphor FBO is GL_RG32F (two independent float channels):
R channel — signal energy
Written by: target echoes in the sweep shader
Multiplied by: u_gain in the display shader
Effect: operator gain knob dims/brightens received echoes without
affecting the sweep beam or range rings
G channel — timing/geometry energy
Written by: range rings + sweep background glow in the sweep shader
NOT multiplied by gain in the display shader
Effect: rings always appear at a fixed brightness; the rotating
sweep-line glow is always visible even at minimum gain
Both channels decay at the same P7 rate (P7_DECAY_RATE in settings.h).
The display shader combines them: totalEnergy = max(R * gain, G).
This produces the correct visual priority: a strong target echo always
shows above the ring but a dim echo below gain threshold fades away
while the ring stays steady.
2. No labeling of range rings.That era did not have them
RANGE POSITION NORMALISATION
@@ -718,28 +693,6 @@ 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.
Mapping max-range → GRAT_INNER_RING_FRAC keeps all rings and targets
within the clean active display area inside the bearing scale overlay.
Scale is applied in two places:
1. PhosphorRenderer::update() — target range: × GRAT_INNER_RING_FRAC
2. computeRingRadii() in each concrete PPI scope — ring radii: × GRAT_INNER_RING_FRAC
P7 COLOUR FUNCTION
p7Color() in phosphor.frag is a piecewise linear ramp over [0, 1]:
e ≥ T_BLUE (0.82) → pure C_BLUE
[T_GREEN, T_BLUE) → mix(C_GREEN, C_BLUE, normalised within range)
[T_YGREE, T_GREEN) → mix(C_YGREE, C_GREEN, normalised within range)
[T_DARK, T_YGREE) → mix(C_YELLW, C_YGREE, normalised within range)
[0, T_DARK) → mix(C_BLACK, C_YELLW, normalised within range)
Each mix() factor is in [0, 1] and the function is continuous at every
threshold boundary. An earlier version had each branch using the formula
of the branch below it (off-by-one), which caused SWEEP_BACKGROUND_ENERGY
= 0.10 to render as saturated yellow (factor 3.33) instead of dim
yellow-green, producing an unwanted solid-yellow band behind the sweep.
==================================================================
@@ -769,7 +722,7 @@ ATCPPIScope : public PPIScope
- Keys: u (range up), d (range down) — this scope only
PARScope : public Scope
- Two stacked sub-scopes: azimuth (top, ~1/3 larger) and elevation (bottom)
- 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
@@ -777,7 +730,7 @@ PARScope : public Scope
Supporting classes:
ScopeManager Thread 1 — owns scope list, s/S switching, 120s auto-advance timer
PhosphorRenderer Thread 1 — P1 and P7 decay/persistence; shared dependency
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
@@ -789,104 +742,96 @@ Supporting classes:
File layout:
src/
main.cpp
scope_manager.h / scope_manager.cpp
scope.h / scope.cpp — abstract Scope base
scope_intro.h / scope_intro.cpp
scope_ascope.h / scope_ascope.cpp — abstract AScope
scope_marine_a.h / scope_marine_a.cpp
scope_chain_home.h / scope_chain_home.cpp
scope_ppi.h / scope_ppi.cpp — abstract PPIScope
scope_marine_ppi.h / scope_marine_ppi.cpp
scope_boat_ppi.h / scope_boat_ppi.cpp — BoatPPIScope; police patrol boat;
moving radar origin; variable speed;
heading marker; display-mode tracking;
nearest-mask selection from TerrainMap
scope_atc_ppi.h / scope_atc_ppi.cpp
scope_par.h / scope_par.cpp
phosphor.h / phosphor.cpp
graticule.h / graticule.cpp
left_panel.h / left_panel.cpp
shared_render_state.h / shared_render_state.cpp
target_buffer.h / target_buffer.cpp
traffic_cop.h / traffic_cop.cpp
simulator.h / simulator.cpp
knob_panel.h / knob_panel.cpp
rpi_receiver.h / rpi_receiver.cpp
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
settings.h — all tunable constants; no .cpp needed
Shader pairs (include .vert and .frag
data/
patrol_route.json — boat waypoints with lat/lon and speed per
segment; loaded by Simulator at startup;
not compiled in — edit without rebuild
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
shaders/
phosphor.vert / phosphor.frag — parameterized for P1 and P7 via uniforms
graticule.vert / graticule.frag
text.vert / text.frag
sweep.vert / sweep.frag
==================================================================
settings.h — tunable constants:
All magic numbers live here. Every source file that needs a tunable value
includes settings.h. No values are hardcoded elsewhere.
Categories planned:
- Phosphor P1 color
- Phosphor P2 color and decay time (Chain Home A-Scope)
- Phosphor P7 strike color, persistence color, decay times (PPI scopes and PAR)
- Sweep line width, brightness, fade trail, periods per scope
- PAR scan rate; Chain Home PRF high and low
- Graticule incandescent color, line widths
- PPI bearing ring tick lengths, label interval, font size
- PPI range ring line width, label size, label color
- Cursor color, line width, arc span
- Noise floor amplitude and variation (Marine A-Scope)
- Graticule swap animation durations (slide out, bare CRT, slide in)
- Key-hold acceleration (initial step, rate, max) — separate for goniometer
- Auto-advance timer interval (120 seconds)
- Window size and panel layout fractions
- PAR azimuth/elevation height fractions
- UI text color and size; cursor readout text size
- Graticule label color (incandescent)
- Gain: default (0.5), minimum (0.0), maximum (1.0), keyboard step size
- Rain clutter filter: default (0.0 = off), minimum (0.0), maximum (1.0), keyboard step size
- Wave clutter filter: default (0.0 = off), minimum (0.0), maximum (1.0), keyboard step size
- Key-hold acceleration for gain, rain clutter, and wave clutter keys
- Terrain bounding box (lat/lon min/max) and processed cell size
- Terrain material σ° values (soil, rock, concrete, water calm/rough)
- Terrain material speckle/grain amplitudes (soil, rock, concrete)
- Terrain classification thresholds (rock elevation, rock slope)
- Terrain polar grid dimensions (range bins, bearing bins)
- Terrain clutter brightness scale for marine PPI
- Terrain boat recompute threshold (degrees bearing offset change)
- ATC terrain clutter suppressed flag (bool, default true)
- ATC terrain shadow enabled flag (bool, default true)
- LiDAR structure height threshold for man-made classification
- BOAT_PATROL_ROUTE_JSON: path to patrol route file (default "data/patrol_route.json")
Waypoints and per-segment speeds live in the JSON, not in settings.h, so
the route can be adjusted without recompiling
- BOAT_WAYPOINT_ARRIVAL_M: radius within which a waypoint is considered
reached, advancing to the next (default 50.0 m)
- BOAT_HEADING_TURN_RATE_DEG_S: maximum turn rate deg/s for heading
interpolation — realistic, not instantaneous (default 3.0)
- BOAT_HEADUP_TOLERANCE_DEG: offset within ±this value of boat heading
triggers "Head-up" label in left panel (default 5.0)
- BOAT_HEADING_MARKER_COLOR: RGB color of heading marker line (default white)
- BOAT_HEADING_MARKER_FRACTION: marker length as fraction of scope radius
(default 0.35)
- BOAT_HEADING_MARKER_DASH_PX: dash length in pixels (default 8)
- BOAT_HEADING_MARKER_GAP_PX: gap length in pixels (default 4)
- BOAT_CLUTTER_MASK_COUNT: number of pre-computed boat shadow masks (default 6)
- BOAT_MASK_SWITCH_THRESHOLD_M: minimum boat displacement from last selected
mask waypoint before a new mask is selected (default 500.0 m)
- METERS_PER_DEGREE: flat-earth scale factor for lat/lon → meters conversion
(111320.0, valid for 2 nm max range)
- Patrol boat radar parameters: BOAT_PEAK_POWER_W (6000), BOAT_FREQ_HZ,
BOAT_HORIZ_BEAMWIDTH_DEG (1.9), BOAT_VERT_BEAMWIDTH_DEG (20.0),
BOAT_ANTENNA_HEIGHT_M (3.5)
- Boat PPI range steps: BOAT_RANGE_STEPS[] = {0.5, 1.0, 2.0} miles,
BOAT_RANGE_STEP_COUNT = 3
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.
==================================================================
@@ -1238,7 +1183,7 @@ POSTGRESQL DATABASE
==================================================================
PostgreSQL is installed. Database: radar. User: radar. Password: radar.
User has full privileges on database radar.
User has full privileges on database radar. Table is target_data.
Schema (all dimensions in METERS):
@@ -1400,33 +1345,13 @@ DATA SOURCES
northeast of Bellingham Bay; used for terrain shadowing from the
northeast quadrant.
All three sources are processed offline by terrain_preprocess into
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.
CLASSES
TerrainMap (Thread 1, read-only after init)
Loaded once at startup from map/lidar_processed/. Reads the four
binary grids and the metadata JSON. Provides:
- Elevation query by lat/lon
- Material query by lat/lon
- Pre-computed polar clutter grid (range × bearing bins) per
fixed radar location
- Line-of-sight shadow mask per radar location
Thread 1 only after init; no mutex required.
LandClutter (Thread 1)
Queries TerrainMap to generate clutter returns for each scope.
Called once per full sweep rotation, not once per frame.
- Marine A-Scope: produces amplitude samples along the current
antenna bearing for injection into the range trace.
- Marine PPI / ATC PPI: produces a polar texture (range × bearing)
uploaded to the GPU once per sweep period.
TERRAIN MATERIALS AND RCS
Each terrain cell is classified as one of four materials. Normalized
@@ -1558,7 +1483,7 @@ TERRAIN PREPROCESSING
==================================================================
All three raw terrain sources are processed in a single pass by the
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
@@ -1618,7 +1543,7 @@ PIPELINE (runs in order)
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 three source files,
checksums for all four source files,
processing date,
boat mask waypoint lat/lon list
@@ -1650,7 +1575,7 @@ BOAT SCENARIO
==================================================================
The boat scenario (scope 5 — Police Patrol Boat PPI) simulates a Bellingham
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.
@@ -1707,43 +1632,6 @@ BOAT NAVIGATION SIMULATION (Simulator, Thread 4)
On reverse-loop: flip traversal direction at each end.
5. Store updated state in BoatNavigator; return to TrafficCop on poll.
SIMULATED SMALL TARGETS
The Simulator generates two categories of small targets for the patrol scope:
Scripted paddleboarder:
A single stand-up paddleboarder drifts slowly across the ferry departure
lane on a fixed looping path (~0.5 kt, random drift added). RCS set to
BOAT_SUP_RCS_M2 (default 0.2 m²). This target also appears on the fixed
Marine PPI scope (same Bellingham Bay coverage area, same target pipeline).
Random kayakers:
BOAT_RANDOM_KAYAK_COUNT (default 2) kayaks wander within a defined zone
near the ferry terminal and harbor mouth. RCS set to BOAT_KAYAK_RCS_M2
(default 0.4 m² — slightly larger than SUP due to hull and occupant).
Random targets also appear on the fixed Marine PPI.
These use the same radar equation path as all other targets; the low RCS
values naturally produce faint, intermittent blips in any sea state, which
is the exhibit's intended behavior. No special-casing required.
Settings.h additions for small targets:
BOAT_SUP_RCS_M2 0.2 — stand-up paddleboard + paddler RCS (m²)
BOAT_KAYAK_RCS_M2 0.4 — kayak + occupant RCS (m²)
BOAT_RANDOM_KAYAK_COUNT 2 — number of random kayak targets
BOAT_KAYAK_ZONE_LAT/LON — bounding box for random kayak positions
SHARED STATE ADDITIONS
SharedRenderState new fields (all under Mutex A):
float boatLatDeg = 0.0f (set from JSON WP0 at startup)
float boatLonDeg = 0.0f
float boatHeadingDeg = 0.0f
float boatSpeedKts = 0.0f
char boatZone[64] = "" — zone label, copied from JSON waypoint
bool boatModeActive = false — set TRUE by ScopeManager when BoatPPIScope
active, FALSE for all other scopes
TARGET PROJECTION FOR MOVING RADAR ORIGIN
For fixed scopes, target positions are projected from a known constant origin.
@@ -1801,7 +1689,6 @@ TERRAIN CLUTTER AND BREAKWATER SHADOWS
V1 GEOMETRY SCOPE (open water only — marina deferred)
Vector features needed from NOAA ENC 18424 for the v1 patrol route:
- Outer shoreline of Bellingham Bay
- Squalicum Harbor outer breakwater (solid, strong return, shadow-caster)
- Ferry terminal structure (Bellingham Cruise Terminal area)
@@ -1811,58 +1698,36 @@ V1 GEOMETRY SCOPE (open water only — marina deferred)
Pacific / Waterfront District structures are deferred until the patrol
route is extended into those areas in a future version.
==================================================================
FILE LAYOUT (COMPLETE — including additions)
IMPORTANT NOTES ON IMPLEMENTATION
==================================================================
To facilitate debugging:
src/
main.cpp
scope_manager.h / scope_manager.cpp
scope.h / scope.cpp
scope_intro.h / scope_intro.cpp
scope_ascope.h / scope_ascope.cpp
scope_marine_a.h / scope_marine_a.cpp
scope_chain_home.h / scope_chain_home.cpp
scope_ppi.h / scope_ppi.cpp
scope_marine_ppi.h / scope_marine_ppi.cpp
scope_boat_ppi.h / scope_boat_ppi.cpp
scope_atc_ppi.h / scope_atc_ppi.cpp
scope_par.h / scope_par.cpp
phosphor.h / phosphor.cpp
graticule.h / graticule.cpp
left_panel.h / left_panel.cpp
shared_render_state.h / shared_render_state.cpp
target_buffer.h / target_buffer.cpp
traffic_cop.h / traffic_cop.cpp
simulator.h / simulator.cpp
knob_panel.h / knob_panel.cpp
rpi_receiver.h / rpi_receiver.cpp
db_panel.h / db_panel.cpp — Dear ImGui DB management panel
(--database mode only)
terrain_map.h / terrain_map.cpp — DEM load, shadow mask, polar clutter
grid; read-only after init, Thread 1
land_clutter.h / land_clutter.cpp — per-sweep clutter arrays for A-scope
range trace and PPI clutter texture
terrain_preprocess.cpp — standalone offline preprocessing tool;
separate CMake target; links GDAL only;
NOT part of main radar binary
settings.h — all constexpr constants; no .cpp
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.
imgui/ — Dear ImGui source, compiled in
imgui.h / imgui.cpp
imgui_impl_glfw.h / imgui_impl_glfw.cpp
imgui_impl_opengl3.h / imgui_impl_opengl3.cpp
imgui_draw.cpp / imgui_tables.cpp / imgui_widgets.cpp
C++ files for the CPU should be divided as follows:
shaders/
phosphor.vert / phosphor.frag — P1 and P7 via uniforms
graticule.vert / graticule.frag
text.vert / text.frag
sweep.vert / sweep.frag
bloom.vert / bloom.frag — FBO bloom post-processing
terrain_clutter.vert / terrain_clutter.frag — polar clutter texture overlay
on PPI; P7-compatible decay;
bearing offset rotation uniform;
u_radarOffsetM vec2 for boat origin
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