🔍 crossover post-merge review — 18 confirmed findings fixed (fable)
A fresh-eyes adversarial pass over the 15 pulled commits (godrum +
keyroll + sats + planes cache + god's eye): 21 raised, 18 confirmed,
all fixed.
The instrument: .mid export no longer smuggles the seeded demo beat
(lanes export only if playing or edited off the seed — same test
zeroHasBuild uses); master ▶/■ remembers per-lane mutes; the beat
panel's bpm field is always editable like the dial (🔒 blocks the
world's hands, not the user's); ⏺ rec with the beat stopped anchors
step 0 at arm instead of the invisible free-running clock; note-length
drags speak pointer events (touch works, window-level so mid-drag
re-renders survive); the resize handle no longer eats 40% of a
sixteenth's click width; the god's eye clears a stale ISS trail after
an rAF nap or resize; the "Agent A" dev-process string is gone; the
grimoire now tells the truth about kit knobs and which voices hold
note length (manual regenerated).
The workers: world_sats — partial Celestrak fetches backfill from the
old cache instead of clobbering it, pass-detection state survives the
6-hourly refresh, the no-TLE exit naps 15 min so the supervisor can't
hammer Celestrak, and the venue default now matches world_sky's
Brisbane instead of Null Island; world_planes bounds its stale-cache
fallback to 1h so a days-old sky can't resurrect as live; world_sky's
docstring admits the lat/lon it emits. run.py retires workers that
exit 0 instead of reviving them every 10s (the sgp4-missing churn).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
d2c7938321
commit
629cbad6fd
5
run.py
5
run.py
@ -198,6 +198,11 @@ def main():
|
||||
now = time.time()
|
||||
for w in workers: # revive any worker that died
|
||||
if w["proc"].poll() is not None and now >= w["next_ok"]:
|
||||
if w["proc"].returncode == 0: # exit 0 = a deliberate, polite goodbye
|
||||
if not w.get("retired"): # (missing dep etc.) — don't churn it
|
||||
w["retired"] = True
|
||||
print(f" ({os.path.basename(w['path'])} exited cleanly; retired)")
|
||||
continue
|
||||
print(f" ({os.path.basename(w['path'])} died; restarting)")
|
||||
np = launch(w["path"], w["extra"])
|
||||
if np:
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -172,14 +172,14 @@ def main():
|
||||
print(f"[{NAME}] HTTP 429 rate limited; backing off to {poll:.0f}s")
|
||||
else:
|
||||
print(f"[{NAME}] HTTP {ex.code}; retrying in {poll:.0f}s")
|
||||
_stale = read_cache(None) # 429 -> a stale cache beats nothing
|
||||
if _stale is not None:
|
||||
_stale = read_cache(3600) # 429 -> a stale cache beats nothing,
|
||||
if _stale is not None: # but a day-old sky beats neither
|
||||
last = reduce_states(filter_states_bbox(_stale[0], args.bbox))
|
||||
print(f"[{NAME}] serving stale cache ({_stale[1]:.0f}s old)")
|
||||
except (urllib.error.URLError, TimeoutError, OSError,
|
||||
json.JSONDecodeError, ValueError) as ex:
|
||||
print(f"[{NAME}] fetch error: {ex}; retrying in {poll:.0f}s")
|
||||
_stale = read_cache(None) # network down -> fall back to a stale cache
|
||||
_stale = read_cache(3600) # network down -> fall back, bounded 1h
|
||||
if _stale is not None:
|
||||
last = reduce_states(filter_states_bbox(_stale[0], args.bbox))
|
||||
print(f"[{NAME}] serving stale cache ({_stale[1]:.0f}s old)")
|
||||
|
||||
@ -142,11 +142,13 @@ def load_grouped_tles(force_fetch=False):
|
||||
print(f"[{NAME}] using cached TLEs (< 24 h old)")
|
||||
return groups
|
||||
merged, recon_catnrs = [], set()
|
||||
any_failed = False
|
||||
for url, is_recon in TLE_SOURCES:
|
||||
try:
|
||||
txt = fetch_url(url)
|
||||
except (urllib.error.URLError, OSError, ValueError) as e:
|
||||
print(f"[{NAME}] TLE fetch failed for {url.split('?')[-1]}: {e}")
|
||||
any_failed = True
|
||||
continue
|
||||
for name, l1, l2 in parse_tles(txt):
|
||||
catnr = l1[2:7].strip()
|
||||
@ -154,6 +156,14 @@ def load_grouped_tles(force_fetch=False):
|
||||
recon_catnrs.add(catnr)
|
||||
merged.append((name, l1, l2, is_recon))
|
||||
if merged:
|
||||
if any_failed: # partial fetch: backfill the failed groups
|
||||
have = {l1[2:7].strip() for _n, l1, _l2, _r in merged} # from the old cache so a
|
||||
old, _old_recon = _read_cache() # half-set never clobbers it
|
||||
for name, l1, l2, is_recon in old:
|
||||
if l1[2:7].strip() not in have:
|
||||
merged.append((name, l1, l2, is_recon))
|
||||
if is_recon:
|
||||
recon_catnrs.add(l1[2:7].strip())
|
||||
_write_cache(merged, recon_catnrs)
|
||||
return merged
|
||||
groups, _ = _read_cache() # network down — serve stale
|
||||
@ -247,8 +257,8 @@ def main():
|
||||
ap.add_argument("--host", default="127.0.0.1")
|
||||
ap.add_argument("--port", type=int, default=9000)
|
||||
ap.add_argument("--interval", type=float, default=2.0)
|
||||
ap.add_argument("--lat", type=float, default=0.0, help="venue latitude (run.py retargets this)")
|
||||
ap.add_argument("--lon", type=float, default=0.0, help="venue longitude")
|
||||
ap.add_argument("--lat", type=float, default=-27.47, help="venue latitude (run.py retargets this; default matches world_sky's Brisbane)")
|
||||
ap.add_argument("--lon", type=float, default=153.02, help="venue longitude")
|
||||
args = ap.parse_args()
|
||||
|
||||
if not HAVE_SGP4:
|
||||
@ -257,8 +267,12 @@ def main():
|
||||
|
||||
sats = build_sats(load_grouped_tles())
|
||||
if not sats:
|
||||
print(f"[{NAME}] no TLEs (no cache and no network) — exiting politely.")
|
||||
sys.exit(0)
|
||||
# nap before exiting nonzero: run.py revives dead workers ~10s later, and
|
||||
# without this pause a downed network would mean a Celestrak attempt every
|
||||
# revive. 15 min keeps us polite and self-healing.
|
||||
print(f"[{NAME}] no TLEs (no cache and no network) — napping 15 min, then retrying via the supervisor.")
|
||||
time.sleep(900)
|
||||
sys.exit(1)
|
||||
|
||||
client = SimpleUDPClient(args.host, args.port)
|
||||
n_recon = sum(1 for s in sats if s.is_recon)
|
||||
@ -272,6 +286,9 @@ def main():
|
||||
last_refresh = time.time()
|
||||
fresh = build_sats(load_grouped_tles()) # refetches only if the cache is stale
|
||||
if fresh:
|
||||
prev = {s.catnr: s.prev_elev for s in sats} # carry pass-detection state so a
|
||||
for s in fresh: # refresh mid-pass can't eat the event
|
||||
s.prev_elev = prev.get(s.catnr)
|
||||
sats = fresh
|
||||
have_recon = any(s.is_recon for s in sats)
|
||||
|
||||
|
||||
@ -11,6 +11,8 @@ 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)
|
||||
/gs/sky/lat the venue's own latitude (so the console can place
|
||||
/gs/sky/lon the venue's own longitude "here" on the god's eye map)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@ -69,7 +71,7 @@ def main():
|
||||
args = ap.parse_args()
|
||||
|
||||
client = SimpleUDPClient(args.host, args.port)
|
||||
print(f"[sky] emitting /gs/sky/elev|day|az for ({args.lat},{args.lon}) "
|
||||
print(f"[sky] emitting /gs/sky/elev|day|az|lat|lon for ({args.lat},{args.lon}) "
|
||||
f"every {args.interval:g}s")
|
||||
|
||||
while True:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user