ace76183e5
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m56s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m43s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 30s
Testy / Kontrola składni wszystkich warstw (push) Successful in 19s
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m55s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m43s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 37s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 21s
Warunek wstępny pod kosmogram (PRE-12): planety i znaki na kole rysujemy symbolami. Nowy moduł engine/glyphs.py: - forward: nazwa → glif (planety, punkty, Loty, znaki, aspekty, retro ℞), - reverse: glif → nazwa (znosi obecność/brak selektora wariantu), - glyphify(): tokeny [XX z bazy → glify; przyklad z wymagan „[Sa [Pis 26°08' [conj [PF" → „♄ ♓ 26°08' ☌ ⊗" (stopnie nietkniete). DAN-18 „tylko tekst, nigdy emoji" potraktowane serio, TRZEMA warstwami — bo sam selektor NIE wystarcza (potwierdzone wizualnie w przegladarce): 1. w danych: selektor wariantu tekstowego U+FE0E na znakach zodiaku oraz ♀/♂ (maja wariant emoji); ☉ i reszta go nie dostaja (zbedny), 2. CSS font-variant-emoji:text, 3. CSS font-family celujacy w MONOCHROMATYCZNE fonty symboli PRZED emoji. Bez pkt. 3 macOS/Chromium i tak renderowal znaki ♈–♓ jako kolorowe kafelki Apple Color Emoji mimo VS15 — planety wychodzily tekstem, znaki nie. Stack „Apple Symbols / Segoe UI Symbol / Noto Sans Symbols2" naprawia render. NIGDZIE nie emitujemy U+FE0F (emoji) — pilnuje tego test. Wpiete w build_chart: kazda pozycja dostaje glyph (planeta) + sign_glyph (znak, zalezny od zodiaku, wiec po przesunieciu), osie i Loty tak samo (Fortuna ⊗, reszta Lotow bez standardowego symbolu → None), aspekty dostaja glif, result[„sign_glyphs"] to pierscien 12 znakow pod kolo. UI: kolumna „Sym." w tabeli pozycji (planeta + znak) i symbol przy aspekcie. Testy: 14 (kompletnosc — kazdy obiekt/znak/aspekt ma glif; dwukierunkowosc; DAN-18 brak FE0F, znaki maja VS15, ☉ nie; przyklad glyphify z wymagan). Calosc: logika 265 passed / 1 skipped, prezentacja 25. Render znakow potwierdzony wizualnie (czarno-biale symbole, nie emoji). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
178 lines
7.1 KiB
Python
178 lines
7.1 KiB
Python
"""Konwersja tekst ↔ symbol astrologiczny (LOG-22).
|
||
|
||
Litery i słowa (Sa Pis 26°08' conj Fortune) ↔ glify (♄ ♓ 26°08' ☌ ⊗). Potrzebne
|
||
pod rysowanie kosmogramu (PRE-12) — planety i znaki na kole rysujemy symbolami.
|
||
|
||
DWIE zasady z bazy wymagań, obie krytyczne:
|
||
* DAN-18: TYLKO tekstowy Unicode, NIGDY emoji. Część znaków (zodiak, ♀, ♂) ma
|
||
domyślnie prezentację emoji — kolorowy kwadrat zamiast czarno-białego glifu,
|
||
nieczytelny i niesterowalny przez CSS. Wymuszamy prezentację tekstową
|
||
selektorem wariantu U+FE0E (NIE U+FE0F, który robi odwrotnie).
|
||
* DAN-17: łańcuch musi znosić dowolny Unicode. Reverse (symbol→tekst)
|
||
normalizuje wejście, zdejmując selektory wariantu, żeby glif z FE0E i bez
|
||
dawał ten sam wynik.
|
||
|
||
Glify zapisane przez \\u — jednoznaczne code-pointy, odporne na zniekształcenia
|
||
edytorów i samodokumentujące (widać, który to znak Unicode).
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
# selektory wariantu prezentacji
|
||
_TEXT = "︎" # VS15 — wymusza glif tekstowy (czarno-biały)
|
||
_EMOJI = "️" # VS16 — prezentacja emoji; NIGDY nie emitujemy, zdejmujemy przy reverse
|
||
|
||
|
||
def _t(cp: str) -> str:
|
||
"""Znak z wymuszoną prezentacją tekstową (dokleja VS15)."""
|
||
return cp + _TEXT
|
||
|
||
|
||
# ── planety i światła ────────────────────────────────────────────────────
|
||
# ♀ i ♂ mają wariant emoji → wymuszamy tekst; reszta jest tekstowa domyślnie.
|
||
PLANET = {
|
||
"Sun": "☉", # ☉
|
||
"Moon": "☽", # ☽
|
||
"Mercury": "☿", # ☿
|
||
"Venus": _t("♀"), # ♀︎
|
||
"Mars": _t("♂"), # ♂︎
|
||
"Jupiter": "♃", # ♃
|
||
"Saturn": "♄", # ♄
|
||
"Uranus": "♅", # ♅
|
||
"Neptune": "♆", # ♆
|
||
"Pluto": "♇", # ♇
|
||
}
|
||
|
||
# ── punkty wirtualne ─────────────────────────────────────────────────────
|
||
POINT = {
|
||
"North Node": "☊", # ☊
|
||
"South Node": "☋", # ☋
|
||
"Lilith": "⚸", # ⚸ (Black Moon Lilith)
|
||
"Chiron": "⚷", # ⚷
|
||
}
|
||
|
||
# ── Lots (punkty arabskie) ───────────────────────────────────────────────
|
||
# Standardowy glif ma tylko Fortuna (⊗). Reszta Lotów nie ma powszechnie
|
||
# wspieranego symbolu → zwracamy None i UI pokazuje nazwę.
|
||
LOT = {
|
||
"Fortune": "⊗", # ⊗
|
||
"Part of Fortune": "⊗",
|
||
}
|
||
|
||
# ── znaki zodiaku (wszystkie mają wariant emoji → wszystkie z VS15) ──────
|
||
_SIGN_CP = {
|
||
"Aries": "♈", "Taurus": "♉", "Gemini": "♊", "Cancer": "♋",
|
||
"Leo": "♌", "Virgo": "♍", "Libra": "♎", "Scorpio": "♏",
|
||
"Sagittarius": "♐", "Capricorn": "♑", "Aquarius": "♒", "Pisces": "♓",
|
||
}
|
||
SIGN = {name: _t(cp) for name, cp in _SIGN_CP.items()}
|
||
|
||
# ── aspekty (nazwy jak w aspects.py + minor z abbreviations) ─────────────
|
||
ASPECT = {
|
||
"conjunction": "☌", # ☌
|
||
"opposition": "☍", # ☍
|
||
"trine": "△", # △
|
||
"square": "□", # □
|
||
"sextile": "⚹", # ⚹
|
||
"semisextile": "⚺", # ⚺
|
||
"quincunx": "⚻", # ⚻
|
||
"semisquare": "∠", # ∠
|
||
}
|
||
|
||
# ── ruch ─────────────────────────────────────────────────────────────────
|
||
RETROGRADE = "℞" # ℞
|
||
DIRECT = "D" # zwykłe „D" — brak dedykowanego glifu prostego ruchu
|
||
|
||
|
||
# ── forward: nazwa → glif ────────────────────────────────────────────────
|
||
|
||
def glyph_for(name: str) -> str | None:
|
||
"""Glif obiektu/punktu/Lota po nazwie (jak w silniku). None, gdy brak."""
|
||
return PLANET.get(name) or POINT.get(name) or LOT.get(name)
|
||
|
||
|
||
def sign_glyph(sign: str) -> str | None:
|
||
return SIGN.get(sign)
|
||
|
||
|
||
def aspect_glyph(aspect: str) -> str | None:
|
||
return ASPECT.get(aspect)
|
||
|
||
|
||
# ── reverse: glif → nazwa ────────────────────────────────────────────────
|
||
|
||
def _strip_variants(g: str) -> str:
|
||
"""Zdejmuje selektory wariantu — glif z FE0E i bez daje ten sam klucz."""
|
||
return g.replace(_TEXT, "").replace(_EMOJI, "")
|
||
|
||
|
||
def _reverse(mapping: dict[str, str]) -> dict[str, str]:
|
||
# pierwsze wystąpienie wygrywa (Fortune vs Part of Fortune → 'Fortune')
|
||
out: dict[str, str] = {}
|
||
for name, g in mapping.items():
|
||
out.setdefault(_strip_variants(g), name)
|
||
return out
|
||
|
||
|
||
_PLANET_POINT_REV = _reverse({**PLANET, **POINT, **{"Fortune": LOT["Fortune"]}})
|
||
_SIGN_REV = _reverse(SIGN)
|
||
_ASPECT_REV = _reverse(ASPECT)
|
||
|
||
|
||
def name_for_glyph(g: str) -> str | None:
|
||
"""Obiekt/punkt po glifie (znosi obecność lub brak selektora wariantu)."""
|
||
return _PLANET_POINT_REV.get(_strip_variants(g))
|
||
|
||
|
||
def sign_for_glyph(g: str) -> str | None:
|
||
return _SIGN_REV.get(_strip_variants(g))
|
||
|
||
|
||
def aspect_for_glyph(g: str) -> str | None:
|
||
return _ASPECT_REV.get(_strip_variants(g))
|
||
|
||
|
||
# ── glifikacja tekstu sygnifikatora (składnia [XX z bazy) ────────────────
|
||
# Nasze sygnifikatory zapisane są tokenami z prefiksem [ (np. „[Sa [conj [PF").
|
||
# glyphify zamienia znane tokeny na glify, resztę zostawia. To druga strona
|
||
# abbreviations.expand: tam token → słowo, tu token → symbol.
|
||
import re as _re
|
||
|
||
# token po nawiasie (np. „Su", „Tau", „conj", „PF") → glif
|
||
_TOKEN_GLYPH: dict[str, str] = {}
|
||
_ABBR_TO_NAME = {
|
||
"Su": "Sun", "Mo": "Moon", "Me": "Mercury", "Ve": "Venus", "Ma": "Mars",
|
||
"Ju": "Jupiter", "Sa": "Saturn", "Ur": "Uranus", "Ne": "Neptune", "Pl": "Pluto",
|
||
"NN": "North Node", "SN": "South Node", "Lilith": "Lilith", "Chiron": "Chiron",
|
||
"PF": "Fortune", "Fortune": "Fortune",
|
||
}
|
||
for _abbr, _name in _ABBR_TO_NAME.items():
|
||
_g = glyph_for(_name)
|
||
if _g:
|
||
_TOKEN_GLYPH[_abbr] = _g
|
||
_SIGN_ABBR_NAME = {
|
||
"Ari": "Aries", "Tau": "Taurus", "Gem": "Gemini", "Can": "Cancer", "Leo": "Leo",
|
||
"Vir": "Virgo", "Lib": "Libra", "Sco": "Scorpio", "Sag": "Sagittarius",
|
||
"Cap": "Capricorn", "Aqu": "Aquarius", "Pis": "Pisces",
|
||
}
|
||
for _abbr, _name in _SIGN_ABBR_NAME.items():
|
||
_TOKEN_GLYPH[_abbr] = SIGN[_name]
|
||
_ASP_ABBR_NAME = {
|
||
"conj": "conjunction", "opp": "opposition", "tri": "trine", "sq": "square",
|
||
"sex": "sextile", "semisex": "semisextile", "quincunx": "quincunx", "semisq": "semisquare",
|
||
}
|
||
for _abbr, _name in _ASP_ABBR_NAME.items():
|
||
if _name in ASPECT:
|
||
_TOKEN_GLYPH[_abbr] = ASPECT[_name]
|
||
|
||
_TOKEN_RE = _re.compile(r"\[([A-Za-z]+)")
|
||
|
||
|
||
def glyphify(text: str) -> str:
|
||
"""Zamienia tokeny [XX na glify; nieznane zostawia bez zmiany. Dodatkowo
|
||
»Rx«/»R« → ℞. Nie parsuje stopni — te i tak są czytelne (26°08')."""
|
||
if not text:
|
||
return text
|
||
out = _TOKEN_RE.sub(lambda m: _TOKEN_GLYPH.get(m.group(1), m.group(0)), text)
|
||
out = _re.sub(r"\bR[x]?\b", RETROGRADE, out)
|
||
return out
|