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

@@ -1033,3 +1033,116 @@ describe("SkyWheel — angle (ASC/MC) click tooltips", () => {
expect(marsGroup.classList.contains("nw-planet--active")).toBe(true);
});
});
// ── SkyWheel.drawRim — the SEED MAP's shared wheel rim ──────────────────────
//
// The STRIPPED wheel ringing the Voronoi tessellation (roadmap step 21,
// Step 2): the ROOM's own sky — planets only, since a virtual table has a
// convened TIME but no birth LOCATION (so houses/ASC/MC never exist on the
// shared rim) — around the canonical signs ring (asc=0 frame, identical for
// all six gamers). drawRim is a PURE renderer: no tooltips, no cycle, no
// transitions, and CRUCIALLY no singleton writes — the interactive saved
// wheel on the same page (_svg/_currentData/#id_sky_tooltip) must survive a
// rim draw untouched.
describe("SkyWheel — drawRim (SEED MAP shared rim)", () => {
const 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: [],
};
let rimSvg, skySvg, tooltipEl;
beforeEach(() => {
rimSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
rimSvg.setAttribute("id", "id_seed_wheel_svg");
rimSvg.setAttribute("width", "400");
rimSvg.setAttribute("height", "400");
rimSvg.style.width = "400px";
rimSvg.style.height = "400px";
document.body.appendChild(rimSvg);
});
afterEach(() => {
rimSvg.remove();
if (skySvg) { SkyWheel.clear(); skySvg.remove(); skySvg = null; }
if (tooltipEl) { tooltipEl.remove(); tooltipEl = null; }
});
it("R1: renders the canonical sign ring + the room sky's planet glyphs", () => {
SkyWheel.drawRim(rimSvg, ROOM_SKY);
expect(rimSvg.querySelectorAll(".nw-sign-group").length).toBe(12);
expect(rimSvg.querySelectorAll(".nw-planet-group").length).toBe(3);
expect(rimSvg.querySelector(".nw-outer-ring")).not.toBeNull();
// Mercury's retrograde badge carries over to the rim.
expect(rimSvg.querySelector("[data-planet='Mercury'] .nw-rx")).not.toBeNull();
});
it("R2: strips the element ring, centre disc, aspect web, houses and axes", () => {
SkyWheel.drawRim(rimSvg, ROOM_SKY);
["nw-elements", "nw-inner-disc", "nw-aspects", "nw-houses", "nw-axes"]
.forEach((cls) => {
expect(rimSvg.querySelectorAll("." + cls).length)
.toBe(0, `expected no .${cls} on the shared rim`);
});
});
it("R3: returns the hub geometry the tessellation slots into", () => {
const geo = SkyWheel.drawRim(rimSvg, ROOM_SKY);
expect(geo.size).toBe(400);
expect(geo.cx).toBe(200);
expect(geo.cy).toBe(200);
expect(geo.r).toBeCloseTo(400 * 0.46, 6);
// The freed hub: just inside the planet band (planetR 0.59 glyph 0.05).
expect(geo.hubR).toBeCloseTo(400 * 0.46 * 0.52, 6);
});
it("R4: places planets statically at their canonical (asc=0) angles — no entry transition", () => {
SkyWheel.drawRim(rimSvg, ROOM_SKY);
const sun = rimSvg.querySelector("[data-planet='Sun'] circle");
const a = (-(338.4) - 180) * Math.PI / 180; // _toAngle(338.4, asc=0)
const r = 400 * 0.46;
expect(parseFloat(sun.getAttribute("cx"))).toBeCloseTo(200 + r * 0.59 * Math.cos(a), 1);
expect(parseFloat(sun.getAttribute("cy"))).toBeCloseTo(200 + r * 0.59 * Math.sin(a), 1);
});
it("R5: leaves the interactive singleton untouched — the sky felt's wheel keeps its svg + tooltip", () => {
skySvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
skySvg.setAttribute("id", "id_sky_svg");
skySvg.style.width = "400px";
skySvg.style.height = "400px";
document.body.appendChild(skySvg);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_sky_tooltip";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
SkyWheel.draw(skySvg, CONJUNCTION_CHART);
SkyWheel.drawRim(rimSvg, ROOM_SKY);
// The tooltip controls draw() injected survive the rim draw.
expect(tooltipEl.querySelector(".nw-tt-prv")).not.toBeNull();
// The singleton still points at the INTERACTIVE svg: clear() empties
// it, not the rim.
SkyWheel.clear();
expect(skySvg.children.length).toBe(0);
expect(rimSvg.querySelectorAll(".nw-sign-group").length).toBe(12);
});
it("R6: renders the signs-only canonical frame when the room sky is absent", () => {
const geo = SkyWheel.drawRim(rimSvg, null);
expect(rimSvg.querySelectorAll(".nw-sign-group").length).toBe(12);
expect(rimSvg.querySelectorAll(".nw-planet-group").length).toBe(0);
expect(geo.hubR).toBeCloseTo(400 * 0.46 * 0.52, 6);
});
});