Set the Game Clock — ephemeris narrowing (hard CSP): placements shrink the REAL date window; narrowed range prints below the prompt; unreachable signs dim + reject — TDD

- PySwiss gains its FIRST reverse lookup: GET /api/windows/?placements=
  Uranus:Aquarius,…&next=Saturn — sign_windows (per-planet stride scan +
  bisection edge refine to the hour) folds thru narrowed_windows
  (slowest planet first, each scan restricted to the prior intersection
  so the fast bodies only ever scan slivers) over the game window
  (settings GAME_WINDOW_START/END, default 1781-03-13 — Uranus's
  discovery — → 2100-12-31, the snapshot span); present_signs reports
  the next planet's reachable signs. Self-validating UTs (every window
  forward-checked at midpoint + edges) + 8 API ITs
- epic clock_windows endpoint (lazy, table_sky-shaped — room views stay
  HTTP-free) proxies the lookup, cached per room+placements (six felts
  polling one ritual state = ONE upstream call; failures cached 60s);
  fails OPEN {available:false} when PySwiss is unreachable
- place_clock_planet enforces the HARD constraint: a sign outside the
  narrowed windows' reach → 409 sign_unreachable; fail-open w.o PySwiss
  (the ritual never bricks on microservice downtime); PlaceClockPlanet
  ITs sever PySwiss in setUp so the turn walk stays deterministic
  against a live local service
- felt: #id_clock_windows readout below the prompt for ALL viewers —
  "1995-04-01 → 1998-04-17 · 2 windows" — fetched at parse + after own
  placement + on every clock_placement broadcast; drawRim opts gain
  allowedSigns → unreachable wedges .nw-sign--blocked (dimmed, inert,
  no handlers); SkyWheelSpec R10/R11
- SeedMapClockNarrowingTest FT stubs PySwiss in-process (real proxy,
  real gating): readout renders, blocked Aries won't place, allowed
  Pisces lands Saturn, readout re-narrows

[[project-voronoi-spec]]

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-06-10 12:30:16 -04:00
parent b2ddd98956
commit 32db203543
15 changed files with 888 additions and 7 deletions

View File

@@ -5,12 +5,19 @@ from timezonefinder import TimezoneFinder
import swisseph as swe
from django.conf import settings as django_settings
from .calc import (
DEFAULT_HOUSE_SYSTEM,
PLANET_CODES,
SIGNS,
calculate_aspects,
get_element_counts,
get_julian_day,
get_planet_positions,
jd_to_iso,
narrowed_windows,
present_signs,
set_ephe_path,
)
from .models import EphemerisSnapshot
@@ -68,6 +75,53 @@ def chart(request):
})
def windows(request):
"""GET /api/windows/ — REVERSE ephemeris lookup (Set the Game Clock).
Query params:
placements — comma list of Planet:Sign pairs (may be absent/empty)
next — planet name; report which signs it can reach in the windows
Folds each placement's sign residences into the intersected date windows
where ALL placements hold simultaneously, bounded by the game window
(settings GAME_WINDOW_START/END — defaults span the precomputed snapshot
range: 1781-03-13, Uranus's discovery year, → 2100-12-31).
Returns {windows: [{start, end}…], days, next?: {planet, signs: {sign:
bool ×12}}} 200 · 400 on an unknown planet/sign or malformed pair.
"""
placements = {}
for pair in [p for p in request.GET.get('placements', '').split(',') if p]:
planet, sep, sign = pair.partition(':')
if not sep or planet not in PLANET_CODES or sign not in SIGNS:
return HttpResponse(status=400)
placements[planet] = sign
next_planet = request.GET.get('next') or None
if next_planet is not None and next_planet not in PLANET_CODES:
return HttpResponse(status=400)
set_ephe_path()
start = getattr(django_settings, 'GAME_WINDOW_START', '1781-03-13')
end = getattr(django_settings, 'GAME_WINDOW_END', '2100-12-31')
base = [(
get_julian_day(datetime.fromisoformat(start).replace(tzinfo=timezone.utc)),
get_julian_day(datetime.fromisoformat(end).replace(tzinfo=timezone.utc)),
)]
wins = narrowed_windows(placements, base)
payload = {
'windows': [{'start': jd_to_iso(a), 'end': jd_to_iso(b)} for a, b in wins],
'days': round(sum(b - a for a, b in wins), 2),
}
if next_planet:
reachable = present_signs(next_planet, wins)
payload['next'] = {
'planet': next_planet,
'signs': {s: s in reachable for s in SIGNS},
}
return JsonResponse(payload)
_tf = TimezoneFinder()