SEED MAP shared wheel rim: the table's OWN sky (planets-only, canonical signs) rings the tessellation — one frame for all six gamers — TDD
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful

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:
Disco DeDisco
2026-06-10 00:33:23 -04:00
parent cde556b178
commit 9ed877168e
12 changed files with 764 additions and 21 deletions

View File

@@ -1,4 +1,4 @@
"""Functional test for the SEED MAP felt — Voronoi map, roadmap step 21, Step 1.
"""Functional test for the SEED MAP felt — Voronoi map, roadmap step 21.
The SEED MAP felt is the inline --duoUser sibling of the CAST SKY / DRAW SEA
felts: once the seat's Celtic-Cross hand is complete (hand_complete), SEED MAP
@@ -7,6 +7,13 @@ d3-delaunay DUAL GRAPH — a Voronoi cell layer (territory) + a Delaunay edge
layer (adjacency). STEP 1 is placeholder-seeded (card-driven seeding is Step 2),
so we assert only that the dual graph renders both layers.
Step 2's first piece — the SHARED WHEEL RIM: the stripped sky wheel (signs ring
at canonical orientation + planet glyphs; NO element ring, centre disc, houses
or axes — a virtual table has a convened TIME but no birth LOCATION) rings the
tessellation, drawn from the ROOM'S OWN sky (Room.sky_chart, identical for all
six gamers). The map svg shrinks into the wheel's freed hub. The fixture
pre-stores Room.sky_chart so the lazy PySwiss compute path never fires HTTP.
Plain FunctionalTest (NOT @tag("channels")): the felt-open is pure client-side.
We seed a CONFIRMED Character with a complete hand directly in the DB, so there
is no WebSocket sky-confirm flow to drive — unlike the sky/sea FTs.
@@ -25,6 +32,17 @@ from .test_game_room_select_sea import _make_sky_confirmed_room
_HAND_POSITIONS = ["cover", "cross", "crown", "lay", "leave", "loom"]
# The room's own sky — planets only (location-independent), mirroring the
# Jasmine ROOM_SKY fixture. Mercury retrograde exercises the ℞ badge.
_ROOM_SKY = {
"planets": {
"Sun": {"sign": "Pisces", "degree": 338.4, "retrograde": False},
"Moon": {"sign": "Capricorn", "degree": 295.1, "retrograde": False},
"Mercury": {"sign": "Aquarius", "degree": 312.8, "retrograde": True},
},
"aspects": [],
}
class SeedMapFeltTest(FunctionalTest):
def setUp(self):
@@ -53,6 +71,10 @@ class SeedMapFeltTest(FunctionalTest):
celtic_cross holds `hand_len` placed cards. hand_len>=6 => hand_complete
=> SEED MAP is the live phase btn + the felt renders."""
room, seat = _make_sky_confirmed_room(self.live_server_url, self.gamer, self.earthman)
# Pre-store the table's own sky so the rim renders without the lazy
# PySwiss compute (no live HTTP from FTs).
room.sky_chart = _ROOM_SKY
room.save(update_fields=["sky_chart"])
hand = [
{"position": p, "card_id": self.card.id, "reversed": False, "polarity": "gravity"}
for p in _HAND_POSITIONS[:hand_len]
@@ -67,8 +89,8 @@ class SeedMapFeltTest(FunctionalTest):
def _room_url(self, room):
return self.live_server_url + reverse("epic:room", kwargs={"room_id": room.id})
def test_seed_map_felt_opens_and_paints_dual_graph(self):
room = self._seed_room(hand_len=6)
def _open_seed_felt(self, room):
"""Load the room, wait for SEED MAP to go live, click it open."""
self.create_pre_authenticated_session("founder@test.io")
self.browser.get(self._room_url(room))
@@ -88,6 +110,10 @@ class SeedMapFeltTest(FunctionalTest):
))
self.wait_for(_click_and_assert_open)
def test_seed_map_felt_opens_and_paints_dual_graph(self):
room = self._seed_room(hand_len=6)
self._open_seed_felt(room)
self.assertTrue(
self.browser.find_element(By.ID, "id_seed_map_overlay").is_displayed()
)
@@ -105,6 +131,58 @@ class SeedMapFeltTest(FunctionalTest):
), 1,
)
def test_seed_map_felt_rings_tessellation_with_room_sky_rim(self):
"""Step 2's shared frame: the stripped sky wheel — canonical signs ring +
the ROOM's own planets, NOTHING location-bound (no houses/axes) and none
of the stripped chrome (element ring / centre disc / aspect web) — rings
the tessellation, which shrinks into the wheel's freed hub."""
room = self._seed_room(hand_len=6)
self._open_seed_felt(room)
# The rim paints: 12 canonical sign slices + the room sky's 3 planets.
self.wait_for(lambda: self.assertEqual(
self.browser.execute_script(
"return document.querySelectorAll('#id_seed_wheel_svg .nw-sign-group').length"
), 12,
))
self.assertEqual(
self.browser.execute_script(
"return document.querySelectorAll('#id_seed_wheel_svg .nw-planet-group').length"
), 3,
)
# The strip: no element ring / centre disc / aspect web, and no
# location-bound layers (houses, ASC/MC axes) on a shared rim.
for cls in ["nw-elements", "nw-inner-disc", "nw-aspects", "nw-houses", "nw-axes"]:
self.assertEqual(
self.browser.execute_script(
f"return document.querySelectorAll('#id_seed_wheel_svg .{cls}').length"
), 0, f"expected no .{cls} on the shared rim",
)
# The tessellation sits INSIDE the rim: the map svg is shrunk square
# into the wheel's hub (2 × hubR = 2 × 0.52 × 0.46 × wheel span) and
# carries the rimmed clip class.
map_w, map_h, col_w, col_h = self.browser.execute_script(
"const m = document.getElementById('id_seed_map_svg'),"
" c = m.closest('.seed-map-col');"
"return [m.clientWidth, m.clientHeight, c.clientWidth, c.clientHeight];"
)
self.assertEqual(map_w, map_h) # square
self.assertLess(map_w, min(col_w, col_h)) # inside the wheel
self.assertAlmostEqual( # = the freed hub
map_w, 2 * 0.52 * 0.46 * min(col_w, col_h), delta=2,
)
self.assertIn("voronoi-map--rimmed",
self.browser.find_element(By.ID, "id_seed_map_svg").get_attribute("class"))
# And the dual graph still paints inside the hub.
self.assertGreaterEqual(
self.browser.execute_script(
"return document.querySelectorAll('#id_seed_map_svg .voronoi-cell').length"
), 1,
)
def test_seed_map_felt_absent_until_hand_complete(self):
room = self._seed_room(hand_len=5) # only 5 placed → NOT hand_complete
self.create_pre_authenticated_session("founder@test.io")