SEED MAP shared wheel rim: the table's OWN sky (planets-only, canonical signs) rings the tessellation — one frame for all six gamers — TDD
The Voronoi felt gains a stripped sky-wheel rim drawn from the ROOM's own sky
(Room.sky_chart) — identical for every gamer, updating toward the shared map —
with the tessellation sized into the wheel's freed hub. Roadmap step 21, Step
2's coordinate frame.
- Room.convened_at + Room.sky_chart (migration 0019); pick_roles stamps
convened_at at gate-close (stamp only — no HTTP on the transition)
- epic.table_sky: lazy planets-only chart via PySwiss at the null location
(geocentric longitudes need only the convened TIME; houses/ASC/MC need a
birth LOCATION a virtual table lacks → omitted), cached on Room.sky_chart;
legacy rooms key off created_at; seated-gamer gated, 502 on PySwiss down
- SkyWheel.drawRim(svg, data): pure static renderer — canonical asc=0 frame,
signs ring + planet glyphs only, NO element ring / centre disc / houses /
axes / aspects / tooltips; never writes the interactive wheel's singleton
state; returns {size, cx, cy, r, hubR} so the felt sizes the map into the hub
- _seed_map_overlay.html: rim draws on open; map svg shrinks to 2×hubR +
.voronoi-map--rimmed clip; lazy table-sky fetch on open; preload-then-repaint
so a cold-cache open doesn't strand the zodiac glyphs; ResizeObserver on the
col (not the self-sized map svg)
- _sky.scss: stacked centred svgs in .seed-map-col; .seed-wheel pointer-events
none; circle clip on the rimmed map
- room_sky_json ctx in _role_select_context; rootvars: --sixUser/--octUser
nudged within the Trs ramp (parallel palette tune)
- drawRim Jasmine suite (R1–R6: signs+planets, strip, hub geometry, static
placement, singleton untouched, signs-only fallback) in both spec copies;
epic TableSkyViewTest + convened_at stamp + seed-overlay rim ITs; FT rim
assertions (12 signs, 3 planets, no stripped/located layers, hub sizing)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -696,6 +696,11 @@ def _role_select_context(room, user, seat_param=None):
|
||||
if (sky_confirmed and confirmed_char.chart_data)
|
||||
else "null"
|
||||
)
|
||||
# The table's OWN sky — the shared SEED MAP rim frame (one canonical
|
||||
# frame for all six gamers, NOT this seat's natal chart). None until
|
||||
# the lazy table_sky endpoint computes + caches it; the felt then
|
||||
# fetches it on open.
|
||||
ctx["room_sky_json"] = json.dumps(room.sky_chart) if room.sky_chart else None
|
||||
if sky_confirmed:
|
||||
# Fall back to seat.significator for Characters created before the sync was added
|
||||
ctx["my_tray_sig"] = confirmed_char.significator or _sky_seat.significator
|
||||
@@ -1282,6 +1287,10 @@ def pick_roles(request, room_id):
|
||||
room = Room.objects.get(id=room_id)
|
||||
if room.gate_status == Room.OPEN and room.table_status is None:
|
||||
room.table_status = Room.ROLE_SELECT
|
||||
# The table's convene moment — the timestamp its OWN sky is cast
|
||||
# from (the shared SEED MAP rim). Stamp only; the chart computes
|
||||
# LAZILY in table_sky so this transition never fires HTTP.
|
||||
room.convened_at = timezone.now()
|
||||
room.save()
|
||||
for slot in room.gate_slots.filter(status=GateSlot.FILLED).order_by("slot_number"):
|
||||
TableSeat.objects.create(
|
||||
@@ -1823,6 +1832,47 @@ def sky_preview(request, room_id):
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
@login_required
|
||||
def table_sky(request, room_id):
|
||||
"""The table's OWN sky — the shared SEED MAP rim frame ([[project-voronoi-spec]]).
|
||||
|
||||
Planets-only: geocentric longitudes need only the convened TIME (gate
|
||||
close, Room.convened_at; legacy rooms key off created_at), while houses/
|
||||
ASC/MC need a birth LOCATION a virtual table doesn't have — so PySwiss is
|
||||
queried at the null location and only the location-independent keys
|
||||
(planets, aspects) are kept. Computed LAZILY here on first request and
|
||||
cached on Room.sky_chart — never at the pick_roles transition, which must
|
||||
stay HTTP-free.
|
||||
|
||||
Returns {planets, aspects} 200 · 403 not seated · 502 PySwiss unreachable.
|
||||
"""
|
||||
room = Room.objects.get(id=room_id)
|
||||
if _canonical_user_seat(room, request.user) is None:
|
||||
return HttpResponse(status=403)
|
||||
if room.sky_chart:
|
||||
return JsonResponse(room.sky_chart)
|
||||
|
||||
convened = room.convened_at or room.created_at
|
||||
dt_iso = convened.astimezone(zoneinfo.ZoneInfo('UTC')).strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
try:
|
||||
resp = http_requests.get(
|
||||
settings.PYSWISS_URL + '/api/chart/',
|
||||
params={'dt': dt_iso, 'lat': '0', 'lon': '0'},
|
||||
timeout=5,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
except Exception:
|
||||
return HttpResponse(status=502)
|
||||
|
||||
data = resp.json()
|
||||
room.sky_chart = {
|
||||
'planets': data.get('planets') or {},
|
||||
'aspects': data.get('aspects') or [],
|
||||
}
|
||||
room.save(update_fields=['sky_chart'])
|
||||
return JsonResponse(room.sky_chart)
|
||||
|
||||
|
||||
@login_required
|
||||
def sky_save(request, room_id):
|
||||
"""Create or update the draft Character for the requesting gamer's seat.
|
||||
|
||||
Reference in New Issue
Block a user