feat(prezentacja): linie aspektów na kosmogramie (PRE-12, etap 3)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m36s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m35s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 28s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 21s
build / build (push) Successful in 48s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m34s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m36s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 31s
Testy / Kontrola składni wszystkich warstw (push) Successful in 17s

Trzeci etap rysowania koła: linie aspektów w środku. Domyka rdzeń PRE-12 —
kosmogram ma teraz znaki, domy, osie, obiekty i aspekty.

Linie łączą PRAWDZIWE pozycje obiektów (nie rozsunięte glify — aspekt dotyczy
tego, gdzie planeta faktycznie stoi) na okręgu piasty, w środku koła.

Kodowanie jak w klasycznych programach:
- KOLOR = charakter aspektu: niebieski harmonijny (trygon, sekstyl), czerwony
  napięty (kwadratura, opozycja), zielony koniunkcja. Dwa kolory na środku od
  razu mówią „gdzie łatwo, gdzie tarcie".
- GRUBOŚĆ i JASNOŚĆ = orb: im ciaśniej, tym mocniej. Poniżej 1° wyraźne
  pogrubienie (wprost z wymagania) — najściślejsze aspekty rzucają się w oczy,
  a szerokie ledwo majaczą, żeby nie robić ze środka plątaniny.

Rysowane jako pierwsze, pod resztą: obiekty siedzą na R_PLANET=150, daleko od
piasty (R_HUB=48), więc glify i linie się nie stykają, a osie i szprychy lądują
na wierzchu.

Motyw druku (PDF, PRE-24) dostaje własne, ciemniejsze kolory aspektów bez
zmiennych CSS — samodzielny konwerter SVG arkusza nie widzi.

Weryfikacja na horoskopie referencyjnym: 24 aspekty, 5 ciasnych (<1°) faktycznie
pogrubionych; kolory zgodne z typem (9 czerwonych napiętych, 10 niebieskich
harmonijnych, 5 zielonych koniunkcji); print bez zmiennych CSS. Sprawdzone
wizualnie — pełny aspektarian, czytelny.

Testy: 8 nowych (linie obecne, kolor wg typu, pogrubienie <1°, prawdziwa pozycja
nie rozsunięta, pominięcie aspektu do obiektu bez pozycji, brak aspektów, motyw
druku). Całość: prezentacja 138 passed. PRE-12 -> Zrobione.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit was merged in pull request #36.
This commit is contained in:
2026-07-25 17:44:25 +02:00
parent 15964dd0df
commit 171deff2d1
4 changed files with 127 additions and 3 deletions
@@ -193,3 +193,79 @@ def test_empty_when_cusps_lack_longitude():
for c in chart["cusps"]:
del c["decimal"]
assert chartwheel.render(chart) == ""
# ───────────────────────────── linie aspektów ────────────────────────────
def _chart_with_aspects():
chart = _chart_with_objects()
chart["positions"].append({"name": "Mars", "decimal": 220.0, "glyph": "", "direction": "D"})
chart["aspects"] = [
{"obj1": "Sun", "obj2": "Mars", "aspect": "trine", "orb": 0.2, "allowed": 8.0},
{"obj1": "Moon", "obj2": "Mars", "aspect": "square", "orb": 5.0, "allowed": 8.0},
{"obj1": "Mercury", "obj2": "Venus", "aspect": "conjunction", "orb": 0.4, "allowed": 10.0},
]
return chart
def test_aspect_lines_are_drawn():
"""Każdy aspekt, którego oba obiekty mają pozycję, dostaje linię — porównujemy
TEN SAM wykres z aspektami i bez, żeby różnica była wyłącznie aspektami."""
chart = _chart_with_aspects()
with_asp = chartwheel.render(chart).count("<line")
chart_no = _chart_with_aspects()
chart_no["aspects"] = []
without = chartwheel.render(chart_no).count("<line")
assert with_asp - without == 3
def test_aspect_color_encodes_type():
"""Niebieski = harmonijny, czerwony = napięty, zielony = koniunkcja."""
svg = chartwheel.render(_chart_with_aspects())
assert "#6aa6c9" in svg # trine — harmonijny (niebieski)
assert "#cf6a6a" in svg # square — napięty (czerwony)
assert "#8fae7a" in svg # conjunction — zielony
def test_tight_aspect_is_bold():
"""Poniżej 1° linia pogrubiona (2.0) — wprost z wymagania. Szeroki aspekt
(square, orb 5°) NIE jest pogrubiony."""
svg = chartwheel.render(_chart_with_aspects())
assert 'stroke-width="2.0"' in svg # SunMars trine 0.2° i MercuryVenus 0.4°
# dwa ciasne aspekty → dwie pogrubione linie
assert svg.count('stroke-width="2.0"') == 2
def test_aspect_uses_true_position_not_spread():
"""Linia aspektu łączy PRAWDZIWE pozycje. Mercury(27.36) i Venus(0.41° od
siebie) są rozsuwane jako glify, ale linia koniunkcji ma iść między ich
faktycznymi punktami na piaście."""
chart = _chart_with_aspects()
asc = chart["angles"]["Asc"]["decimal"]
# punkt Wenus na piaście z prawdziwej długości
vx, vy = chartwheel._pt(27.77, asc, chartwheel.R_HUB)
svg = chartwheel.render(chart)
assert f'x1="{vx:.2f}"' in svg or f'x2="{vx:.2f}"' in svg
def test_aspect_to_missing_object_is_skipped():
"""Aspekt do obiektu bez pozycji nie może wywalić rysunku."""
chart = _chart_with_aspects()
chart["aspects"].append(
{"obj1": "Sun", "obj2": "Chiron", "aspect": "trine", "orb": 1.0, "allowed": 8.0})
svg = chartwheel.render(chart)
assert svg and "" in svg
def test_no_aspect_lines_without_aspects():
chart = _chart_with_objects() # bez klucza 'aspects'
a = chartwheel.render(chart).count("<line")
chart["aspects"] = []
assert chartwheel.render(chart).count("<line") == a
def test_print_theme_aspect_colors_have_no_css_vars():
"""Motyw druku (PDF) — konkretne kolory aspektów, bez zmiennych CSS."""
svg = chartwheel.render(_chart_with_aspects(), theme="print")
assert "var(--" not in svg
assert "#a83232" in svg # square napięty w wariancie druku