A new map-layout skin — the world's own sensor picture read AS the cosmology,
ported from the GODSIGH view. A minimal equirectangular Earth (a sparse hand-drawn
continent set kept inline — no heavy geo data in the one-file console), a 30°
graticule refit to a 2:1 rect each frame (dynamicStations), and nine geographic
"posts" the sources are seated at by godseyeOf. The overlay seats every feed at its
LITERAL coordinates: earthquakes flash at quake.lat/lon (venue-antipode fallback if
absent), the ISS and recon birds sweep as a moving point + trail from sats.iss_lat/
lon, aircraft are a shimmer band, the markets pulse at NY/London/Tokyo, the solar
wind washes the poles as an aurora, the word sparks at civilisational coordinates,
and your own hands are the ring at the venue. Cold watch-floor palette (near-black,
cyan, warning amber) and a Bb minor-pentatonic retune. Registered in SKINS +
SKIN_HOME beside the bagua; grimoire + manual entry ("the eye that hears").
To give the console the venue's location (it had none), world_sky now emits
/gs/sky/lat,lon; config.json declares sky.lat/lon and quake.lat/lon so the eye can
place "here" and the quakes. (sats.* signals + routes already landed with world_sats.)
Verified: full viz parses/runs with the groove self-check passing and zero console
errors; an isolated harness renders the REAL overlay code into a coherent world-map
with every layer; and an end-to-end run (feeds -> hub -> WS) delivers all the eye's
input signals. Adversarial review found one wrap bug — the ISS trail streaking
across the map at the ±180° antimeridian — now fixed by breaking the polyline at the
seam (verified: a seam-crossing sine trail renders split, no full-width streak).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
91 lines
3.7 KiB
Python
91 lines
3.7 KiB
Python
"""
|
|
world_sky.py — the local sky over the venue, as a slow LFO.
|
|
|
|
Computes the sun's elevation above the horizon at a given latitude/longitude and
|
|
the current instant, using the NOAA solar-position algorithm. No network, no API
|
|
key, never rate-limited — just astronomy. This is the most place-specific signal
|
|
in the whole instrument: playing Brisbane at 2am sounds nothing like London at
|
|
sunset, because the sun is literally somewhere else in the sky.
|
|
|
|
Emits:
|
|
/gs/sky/elev solar elevation in degrees (-90 night .. +90 overhead)
|
|
/gs/sky/day 0..1 smooth day/night (0 deep night, 1 broad daylight)
|
|
/gs/sky/az solar azimuth in degrees (0=N, 90=E, 180=S, 270=W)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
import argparse
|
|
import datetime
|
|
import math
|
|
import time
|
|
|
|
from pythonosc.udp_client import SimpleUDPClient
|
|
|
|
|
|
def solar_position(lat: float, lon: float, when: datetime.datetime):
|
|
"""Return (elevation_deg, azimuth_deg) for lat/lon at UTC time `when`."""
|
|
n = when.timetuple().tm_yday
|
|
hour = when.hour + when.minute / 60 + when.second / 3600
|
|
g = 2 * math.pi / 365 * (n - 1 + (hour - 12) / 24) # fractional year
|
|
|
|
eqtime = 229.18 * (0.000075 + 0.001868 * math.cos(g)
|
|
- 0.032077 * math.sin(g) - 0.014615 * math.cos(2 * g)
|
|
- 0.040849 * math.sin(2 * g)) # minutes
|
|
decl = (0.006918 - 0.399912 * math.cos(g) + 0.070257 * math.sin(g)
|
|
- 0.006758 * math.cos(2 * g) + 0.000907 * math.sin(2 * g)
|
|
- 0.002697 * math.cos(3 * g) + 0.00148 * math.sin(3 * g)) # radians
|
|
|
|
time_offset = eqtime + 4 * lon # minutes (E +)
|
|
tst = hour * 60 + time_offset # true solar time
|
|
ha = math.radians(tst / 4 - 180) # hour angle
|
|
|
|
lat_r = math.radians(lat)
|
|
cos_zen = (math.sin(lat_r) * math.sin(decl)
|
|
+ math.cos(lat_r) * math.cos(decl) * math.cos(ha))
|
|
cos_zen = max(-1.0, min(1.0, cos_zen))
|
|
zen = math.acos(cos_zen)
|
|
elev = 90 - math.degrees(zen)
|
|
|
|
# azimuth
|
|
sin_az = -math.sin(ha) * math.cos(decl)
|
|
cos_az = (math.sin(decl) - math.sin(lat_r) * math.cos(zen)) / \
|
|
(math.cos(lat_r) * math.sin(zen) + 1e-9)
|
|
az = (math.degrees(math.atan2(sin_az, cos_az)) + 360) % 360
|
|
return elev, az
|
|
|
|
|
|
def day_fraction(elev: float) -> float:
|
|
"""Smooth 0..1 across the twilight band (civil dusk .. full day)."""
|
|
return max(0.0, min(1.0, (elev + 6.0) / 18.0))
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--host", default="127.0.0.1")
|
|
ap.add_argument("--port", type=int, default=9000)
|
|
ap.add_argument("--lat", type=float, default=-27.47, help="latitude (default Brisbane)")
|
|
ap.add_argument("--lon", type=float, default=153.02, help="longitude (default Brisbane)")
|
|
ap.add_argument("--interval", type=float, default=10.0)
|
|
args = ap.parse_args()
|
|
|
|
client = SimpleUDPClient(args.host, args.port)
|
|
print(f"[sky] emitting /gs/sky/elev|day|az for ({args.lat},{args.lon}) "
|
|
f"every {args.interval:g}s")
|
|
|
|
while True:
|
|
try:
|
|
elev, az = solar_position(args.lat, args.lon,
|
|
datetime.datetime.now(datetime.timezone.utc))
|
|
client.send_message("/gs/sky/elev", float(elev))
|
|
client.send_message("/gs/sky/day", day_fraction(elev))
|
|
client.send_message("/gs/sky/az", float(az))
|
|
client.send_message("/gs/sky/lat", float(args.lat)) # the venue's own coordinates,
|
|
client.send_message("/gs/sky/lon", float(args.lon)) # so the console can place "here" on a map
|
|
except Exception as e: # noqa
|
|
print(f"[sky] warn: {e}")
|
|
time.sleep(args.interval)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|