Tell the boxes from the flight paths, and say how far out things are

Three changes to what is on the picture, and both drawings got all three.

The line from an information box to its aircraft was drawn in the
aircraft's own colour, which made it the same colour as that aircraft's
trail: a straight solid line running out of an aeroplane, in the colour of
the path behind the aeroplane, reads as more path, and on a busy picture
that is a heading nobody flew.  It has its own colour now, and is dashed.
Qt measures a dash pattern in multiples of the pen's width, so each pass of
the glow divides the pattern by its own width; without that the halo's
dashes are three times the core's and the line comes out as beads.

The animation had no such line at all, which only came out when the two
were held up against each other: a label pushed into one of the outward
rings by a crowd had nothing tying it to the aeroplane it was about.  It has
one now, walked along the line's own length rather than along whichever axis
is longer, so that a nearly horizontal leader and a nearly vertical one get
dashes of the same length and neither runs past the aircraft it points at.

A red flag stands where the receiver was told it is standing, from the
coordinates in the settings.  The foot of the pole is the position and the
pennant flies up and to the right, so nothing the flag is made of covers the
place it points at.  It is pure red in every theme: that is the one mark on
the picture whose meaning must not change with the colours, and pure red is
both the brightest red there is and the one furthest from every altitude
colour -- a softer one sat close enough to a low aeroplane on the default
map, and to a mid-altitude one on the red theme, to be taken for one.  It is
drawn only where a position was actually given, since a middle worked out
from whatever flew past is not a place anybody is standing.

And the range rings: faint discs at a quarter, a half and three quarters of
the radius, concentric on the receiver and labelled with the distance.  They
are translucent and they stack, so the ground inside the innermost is lifted
three times and the outer once, which gives a sense of how far away a thing
is without measuring anything.  An indexed picture cannot blend, so
translucent there means moving the ground under the disc a step or two up
its own ramp of shades, which keeps the coastline and the roads visible
through it.  Separate settings for the two, because a picture is studied and
a window is glanced at.

Two bugs found by the tests rather than by looking.  The rings were built
across the whole canvas instead of the part of it the map is drawn on, so
they came out stretched by the height of the title bar -- four per cent,
which is invisible and wrong.  And a radius of zero killed the window: the
projection collapses, every pixel maps to one point, and the graticule asks
for lines across a span of nothing until Qt aborts.  The program never
passes a zero, but a caller could, and a crash is not an answer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PsWPTweCT6pwxKngvVxcg
This commit is contained in:
The Dust Council 2026-09-06 11:49:48 -07:00
parent 2e20c48971
commit 2da1d1f245
12 changed files with 1099 additions and 16 deletions

View file

@ -259,3 +259,61 @@ def test_the_brightness_setting_still_does_something_on_a_vector_theme():
fm.set_theme("phosphor")
assert int(fm.dim_ground(levels, 1.0).max()) > \
int(fm.dim_ground(levels, 0.3).max())
# ---------------------------------------------------------------------------
# The leader line, and the flag on the receiver
# ---------------------------------------------------------------------------
def test_no_theme_lets_a_leader_line_be_mistaken_for_a_flight_path():
"""The whole reason it stopped being drawn in the aircraft's colour: a
straight line running out of an aeroplane, in the colour of the path
behind that aeroplane, reads as more path."""
for name in themes.THEMES:
fm.set_theme(name)
leader = _lab(fm.PALETTE[fm.LEADER])
apart = min(float(np.linalg.norm(leader - _lab(fm.PALETTE[fm.RAMP + i])))
for i in range(fm.RAMP_STEPS))
assert apart > 25, f"{name}: only {apart:.0f} units from a trail"
def test_the_receiver_flag_is_red_whatever_the_theme_is():
""""You are here" is the one mark on the picture whose meaning must not
change with the colours, so it does not take the theme's."""
for name in themes.THEMES:
fm.set_theme(name)
assert tuple(fm.PALETTE[fm.HOME]) == fm.HOME_RED
def test_the_flag_stands_clear_of_the_aircraft_on_every_theme():
"""Including the red one, which is the hard case and the reason the
flag is pure red rather than a softer one: a softened red sat close
enough to a low aeroplane on the default map, and to a mid-altitude one
on the red theme, to be taken for one."""
for name in themes.THEMES:
fm.set_theme(name)
red = _lab(fm.PALETTE[fm.HOME])
apart = min(float(np.linalg.norm(red - _lab(fm.PALETTE[fm.RAMP + i])))
for i in range(fm.RAMP_STEPS))
assert apart > 20, f"{name}: only {apart:.0f} units from an aircraft"
def test_the_flag_stands_on_the_spot_rather_than_covering_it():
"""The foot of the pole is the position. A blob would put the position
somewhere inside itself."""
img = np.full((40, 40), fm.BG, dtype=np.uint8)
fm.draw_home(img, 20, 34)
assert img[34, 20] == fm.HOME, "the pole does not stand on the spot"
assert img[35, 20] == fm.BG, "something is drawn below the position"
assert (img[34, :20] == fm.BG).all(), "the flag reaches left of the pole"
# The pennant flies up and to the right, and nothing else does.
top = 34 - fm.HOME_POLE
assert (img[top, 21:21 + fm.HOME_FLY] == fm.HOME).all()
assert (img[34 - 1, 21:] == fm.BG).all(), "the pennant hangs to the foot"
def test_the_flag_off_the_edge_of_the_picture_paints_nothing_absurd():
for x, y in ((-50, 20), (20, -50), (200, 20), (20, 200)):
img = np.full((40, 40), fm.BG, dtype=np.uint8)
fm.draw_home(img, x, y) # must not raise or wrap
assert img.shape == (40, 40)