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:
@@ -77,6 +77,109 @@ def get_planet_positions(jd):
|
||||
return planets
|
||||
|
||||
|
||||
# ── Reverse lookup — sign windows (Set the Game Clock narrowing) ─────────────
|
||||
#
|
||||
# Forward calculation answers "where is the planet at time T"; the Game Clock
|
||||
# ritual needs the REVERSE — "when does planet P reside in sign S" — so each
|
||||
# placement can narrow the real date window the ritual still resolves to.
|
||||
#
|
||||
# Scan strides (days) sit well under each planet's shortest typical sign
|
||||
# residence. Retrograde re-dips briefer than the stride can be missed — that
|
||||
# is CONSERVATIVE: a missed sliver only narrows the reachable window, it never
|
||||
# admits a false moment (every returned window is forward-verified by its
|
||||
# construction: sampled in-sign, edges bisected to the true crossing).
|
||||
|
||||
SIGN_SCAN_STRIDES = {
|
||||
'Moon': 0.5, 'Sun': 5.0, 'Mercury': 2.0, 'Venus': 2.0, 'Mars': 5.0,
|
||||
'Jupiter': 15.0, 'Saturn': 30.0, 'Uranus': 60.0, 'Neptune': 60.0,
|
||||
'Pluto': 60.0,
|
||||
}
|
||||
EDGE_PRECISION_DAYS = 1.0 / 24.0 # bisect sign crossings to the hour
|
||||
|
||||
|
||||
def get_planet_sign(jd, planet):
|
||||
pos, _ = swe.calc_ut(jd, PLANET_CODES[planet], swe.FLG_SWIEPH)
|
||||
return get_sign(pos[0])
|
||||
|
||||
|
||||
def jd_to_iso(jd):
|
||||
y, m, d, h = swe.revjul(jd)
|
||||
hh = int(h)
|
||||
mm = int(round((h - hh) * 60))
|
||||
if mm == 60:
|
||||
hh, mm = hh + 1, 0
|
||||
return f'{y:04d}-{m:02d}-{d:02d}T{hh:02d}:{mm:02d}:00Z'
|
||||
|
||||
|
||||
def _bisect_edge(planet, sign, jd_match, jd_other):
|
||||
"""Refine the sign crossing between a jd where `planet` IS in `sign` and
|
||||
one where it is not, to EDGE_PRECISION_DAYS. Returns the match-side jd."""
|
||||
while abs(jd_other - jd_match) > EDGE_PRECISION_DAYS:
|
||||
mid = (jd_match + jd_other) / 2
|
||||
if get_planet_sign(mid, planet) == sign:
|
||||
jd_match = mid
|
||||
else:
|
||||
jd_other = mid
|
||||
return jd_match
|
||||
|
||||
|
||||
def sign_windows(planet, sign, windows):
|
||||
"""The sub-windows of `windows` — a list of (jd_start, jd_end) — where
|
||||
`planet` resides in `sign`. Stride scan + bisection edge refine."""
|
||||
stride = SIGN_SCAN_STRIDES[planet]
|
||||
out = []
|
||||
for lo, hi in windows:
|
||||
if hi <= lo:
|
||||
continue
|
||||
samples = []
|
||||
jd = lo
|
||||
while jd < hi:
|
||||
samples.append(jd)
|
||||
jd += stride
|
||||
samples.append(hi)
|
||||
|
||||
run_start = None
|
||||
prev = None
|
||||
for s in samples:
|
||||
in_sign = get_planet_sign(s, planet) == sign
|
||||
if in_sign and run_start is None:
|
||||
run_start = lo if prev is None else _bisect_edge(planet, sign, s, prev)
|
||||
elif not in_sign and run_start is not None:
|
||||
out.append((run_start, _bisect_edge(planet, sign, prev, s)))
|
||||
run_start = None
|
||||
prev = s
|
||||
if run_start is not None:
|
||||
out.append((run_start, hi))
|
||||
return out
|
||||
|
||||
|
||||
def narrowed_windows(placements, windows):
|
||||
"""Fold each placement's sign residences into the intersection. Slowest
|
||||
planets first (fewest fragments); each scan is restricted to the windows
|
||||
the prior placements already narrowed to, so the fast bodies only ever
|
||||
scan slivers."""
|
||||
for planet, sign in sorted(
|
||||
placements.items(), key=lambda kv: -SIGN_SCAN_STRIDES[kv[0]]):
|
||||
if not windows:
|
||||
break
|
||||
windows = sign_windows(planet, sign, windows)
|
||||
return windows
|
||||
|
||||
|
||||
def present_signs(planet, windows):
|
||||
"""The set of signs `planet` occupies at any point within `windows` —
|
||||
sampled at the scan stride plus both edges (presence needs no bisection)."""
|
||||
stride = SIGN_SCAN_STRIDES[planet]
|
||||
signs = set()
|
||||
for lo, hi in windows:
|
||||
jd = lo
|
||||
while jd < hi:
|
||||
signs.add(get_planet_sign(jd, planet))
|
||||
jd += stride
|
||||
signs.add(get_planet_sign(hi, planet))
|
||||
return signs
|
||||
|
||||
|
||||
def get_element_counts(planets):
|
||||
sign_counts = {s: 0 for s in SIGNS}
|
||||
sign_planets = {s: [] for s in SIGNS}
|
||||
|
||||
Reference in New Issue
Block a user