Make the aircraft breathe, and send a ring out from each of them
Two things that move on their own, on the window and in the animated pictures alike, and both turned off with a flag. An aircraft swells from bright to dim and back. At the top of the swell it burns: drawn in a set of peak colours and wearing a halo, which is what a phosphor does when the beam sits in one place a little too long. The halo is the aeroplane's own shape spread outward a pixel at a time rather than a circle drawn round it -- the first attempt did draw a circle and it looked exactly like what it was, a ring of dots -- so the glow has the shape of the thing casting it. It goes only where the picture was still empty, over the ground and the grid and the background and never over another aircraft, since a halo is what light does to the dark around a thing. And a ring leaves each aircraft and travels outward, growing and dimming as it goes. What a radar repeater does, and what the eye reads as this thing is transmitting -- which is exactly what an aeroplane on this picture is doing, twice a second, and is how it got on the picture at all. One ring at a time per aircraft: a new one leaves as the last reaches the end of its reach, so the sky has one ring per aeroplane rather than a stack of them to read through. Five settings, in a group of their own because the others were already at eight: whether to pulse, how long a swell takes, whether to echo, how long between rings, and how far a ring gets. Both times are seconds of watching rather than of flying, so the rhythm looks the same whatever speed an evening is being run through. Two decisions worth naming. Each aircraft is offset by its own address, so a sky full of them swells and rings separately rather than beating as one, which would read as a display flashing rather than as a lot of separate things transmitting; the offset comes from the address, so an aeroplane keeps its rhythm from one frame to the next and from one drawing of the same log to the next. And only an aircraft still being heard pulses: one that has gone quiet is already fading, and a thing that is fading and beating at once says two contradictory things about itself. The window can blend and its swell is continuous. The animation cannot, a GIF being indexed colour, so there the swell is the steps a palette allows -- which family of colours the aeroplane is drawn from, and how far its halo reaches, which is six between them and reads as a swell at a couple of seconds a cycle. The peak has sixteen colours of its own, sixteen being exactly what was left of the palette once 255 is set aside as the transparent index. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PsWPTweCT6pwxKngvVxcg
This commit is contained in:
parent
8ed01f991f
commit
f01de4117f
10 changed files with 907 additions and 16 deletions
|
|
@ -1919,3 +1919,120 @@ def test_nothing_in_the_window_is_drawn_over_the_flag(app):
|
|||
assert is_flag(x, y), "the foot of the pole was painted over"
|
||||
assert all(is_flag(x, y - up) for up in range(HOME_POLE)), \
|
||||
"part of the pole was painted over"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pulsing aircraft, and the rings that leave them
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_a_sky_draws_neither_effect_unless_it_is_asked_to():
|
||||
plain = a_sky()
|
||||
assert plain.pulse == 0.0 and plain.echo == 0.0
|
||||
lively = Sky(pulse=2.2, echo=3.0, echo_reach=46)
|
||||
assert lively.pulse == 2.2 and lively.echo == 3.0
|
||||
|
||||
|
||||
def test_the_effect_settings_are_kept_inside_their_range():
|
||||
quiet = Sky(pulse=-1.0, echo=-2.0, echo_reach=-5.0)
|
||||
assert quiet.pulse == 0.0 and quiet.echo == 0.0 and quiet.echo_reach == 0.0
|
||||
|
||||
|
||||
def _at_own_peak(sky, icao, want=0.5):
|
||||
"""Wind the clock so this aircraft is `want` of the way round its pulse.
|
||||
|
||||
Each is offset by its own address, so a test cannot assume that half a
|
||||
cycle on the clock is half a cycle for any particular aeroplane.
|
||||
"""
|
||||
from bandsaunter.flightmap import phase_of
|
||||
|
||||
rate = sky.pulse or sky.echo
|
||||
sky.started = time.time() - rate * ((want - phase_of(icao)) % 1.0)
|
||||
|
||||
|
||||
@qt
|
||||
def test_an_aircraft_burns_at_the_top_of_its_pulse(app):
|
||||
"""Brighter, and wearing a halo grown out of its own shape: what a
|
||||
phosphor does when the beam sits in one place a little too long."""
|
||||
from bandsaunter.livemap import SkyView
|
||||
|
||||
def drawn(want):
|
||||
sky = a_sky(a_blip(), pulse=2.2)
|
||||
_at_own_peak(sky, "A76154", want)
|
||||
view = SkyView(sky)
|
||||
view.show_ground = False
|
||||
view.detail = 0 # the symbol alone
|
||||
picture = _rendered(view)
|
||||
return int(picture[:, :, :3].astype(int).sum())
|
||||
|
||||
assert drawn(0.5) > drawn(0.0), "it did not brighten at the top"
|
||||
|
||||
|
||||
@qt
|
||||
def test_a_pulse_turned_off_leaves_the_aircraft_alone(app):
|
||||
"""Asked of the level rather than of the pixels: the header carries a
|
||||
running clock, so two whole frames are never identical anyway."""
|
||||
from bandsaunter.livemap import SkyView
|
||||
|
||||
off = SkyView(a_sky(a_blip()))
|
||||
assert off._pulse_level("A76154") is None
|
||||
|
||||
on = SkyView(a_sky(a_blip(), pulse=2.2))
|
||||
for want in (0.0, 0.25, 0.5, 0.75):
|
||||
_at_own_peak(on.sky, "A76154", want)
|
||||
level = on._pulse_level("A76154")
|
||||
assert level is not None and 0.0 <= level <= 1.0
|
||||
_at_own_peak(on.sky, "A76154", 0.5)
|
||||
assert on._pulse_level("A76154") > 0.98, "it does not reach the top"
|
||||
_at_own_peak(on.sky, "A76154", 0.0)
|
||||
assert on._pulse_level("A76154") < 0.02, "nor the bottom"
|
||||
|
||||
|
||||
@qt
|
||||
def test_a_ring_leaves_an_aircraft_and_grows(app):
|
||||
from bandsaunter.livemap import SkyView
|
||||
|
||||
def spread(want):
|
||||
"""How far the ring has got from its aircraft.
|
||||
|
||||
Measured against the same view with the echo turned off, and only
|
||||
within a box round the aeroplane: the header carries a running
|
||||
clock and the box a running age, and neither is a ring.
|
||||
"""
|
||||
sky = a_sky(a_blip(), echo=3.0, echo_reach=60)
|
||||
_at_own_peak(sky, "A76154", want)
|
||||
view = SkyView(sky)
|
||||
view.show_ground = False
|
||||
view.detail = 0
|
||||
with_it = _rendered(view)
|
||||
sky.echo = 0.0
|
||||
without = _rendered(view)
|
||||
x, y = view.projection().xy(a_blip().latitude, a_blip().longitude)
|
||||
top, left = max(0, y - 90), max(0, x - 90)
|
||||
near = slice(top, y + 90), slice(left, x + 90)
|
||||
differs = (with_it[near] != without[near]).any(axis=2)
|
||||
ys, xs = np.where(differs)
|
||||
if not len(xs):
|
||||
return 0
|
||||
return int(np.hypot(xs + left - x, ys + top - y).max())
|
||||
|
||||
early, late = spread(0.15), spread(0.8)
|
||||
assert early > 0, "no ring at all"
|
||||
assert late > early, f"the ring did not grow: {early} then {late}"
|
||||
|
||||
|
||||
@qt
|
||||
def test_the_window_keeps_asking_for_frames_while_anything_is_alive(app):
|
||||
"""A pulse and an echo are never finished, so the window has to keep
|
||||
repainting the way it does while a box is on its way somewhere."""
|
||||
from bandsaunter.livemap import SkyView
|
||||
|
||||
lively = SkyView(a_sky(a_blip(), pulse=2.2))
|
||||
lively.show_ground = False
|
||||
_rendered(lively)
|
||||
assert lively._glide_timer.isActive(), "it stopped asking for frames"
|
||||
|
||||
still = SkyView(a_sky(a_blip()))
|
||||
still.show_ground = False
|
||||
_rendered(still)
|
||||
assert not still._glide_timer.isActive(), \
|
||||
"it is repainting with nothing to animate"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue