Module:TarsicanSaros

From MicrasWiki
Revision as of 23:44, 31 May 2025 by NewZimiaGov (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

Documentation for this module may be created at Module:TarsicanSaros/doc

-- Module:TarsicanSaros
-- Generates a Tarsican Saros Cycle table, factoring perigee/apogee for eclipse subtypes.

local p = {}

----------------------------------------------------------------
-- 1. Define all embedded holidays in a single Lua table "events"
----------------------------------------------------------------
-- Format: events["Month,Day"] = { name = "Holiday Name", link = "URL or nil", city = "CityLink" }

local events = {}

-- Atosiel (Days 1..61)
events["Atosiel,1"]  = { name = "Cricket Rise Festival",       link = "https://micras.org/mwiki/Alperkin#Winter-to-Spring:_The_Cricket_Rise_Festival", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Alperkin|Alperkin]]" }
events["Atosiel,6"]  = { name = "Bayram al-Nur (Festival of Light)", link = "https://micras.org/mwiki/Atos#Bayram_al-Nur:_The_Festival_of_Light", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Vaeringheim|Vaeringheim]]" }
events["Atosiel,18"] = { name = "Chag Or Hadash (Festival of New Light)", link = "https://micras.org/mwiki/Eos#Chag_Or_Hadash_(Festival_of_New_Light)", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Luminaria|Luminaria]]" }
events["Atosiel,30"] = { name = "Symposion Eirinis (Symposium of Harmony)", link = "https://micras.org/mwiki/Micras#Symposion_Eirinis_(Symposium_of_Harmony)", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Serena|Serena]]" }
events["Atosiel,34"] = { name = "Crösakarion",              link = nil,                                                   city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Luminaria|Luminaria]]" }
events["Atosiel,36"] = { name = "Liléigostros",             link = nil,                                                   city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Somniumpolis|Somniumpolis]]" }
events["Atosiel,43"] = { name = "Alev Günü (Day of Flame)",  link = "https://micras.org/mwiki/Pyros#Alev_G%C3%BCn%C3%BC_(Day_of_Flame)", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Pyralis|Pyralis]]" }
events["Atosiel,44"] = { name = "Orebellion",               link = nil,                                                   city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Thermosalem|Thermosalem]]" }
events["Atosiel,48"] = { name = "Day of Abandonment",        link = nil,                                                   city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Vaeringheim|Vaeringheim]]" }
events["Atosiel,54"] = { name = "Sanashalros",               link = nil,                                                   city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Keybir-Aviv|Keybir-Aviv]]" }
events["Atosiel,55"] = { name = "Tikkun Tzel (Repair of Shadows)", link = "https://micras.org/mwiki/Indigo#Tikkun_Tzel_(Repair_of_Shadows)", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Symphonara|Symphonara]]" }
events["Atosiel,58"] = { name = "Gahtarion",                 link = nil,                                                   city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Vaeringheim|Vaeringheim]]" }
events["Atosiel,59"] = { name = "Agavronos",                  link = nil,                                                   city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Aurelia|Aurelia]]" }
events["Atosiel,61"] = { name = "Bebekarion",                 link = nil,                                                   city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Akróstadium|Akróstadium]]" }

-- Thalassiel (Days 62..122)
events["Thalassiel,1"]  = { name = "Cicada Song Festival", link = "https://micras.org/mwiki/Alperkin#Spring-to-Summer:_The_Cicada_Song_Festival", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Alperkin|Alperkin]]" }
events["Thalassiel,2"]  = { name = "Amaäzakar",            link = nil,                                                            city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Delphica|Delphica]]" }
events["Thalassiel,6"]  = { name = "Panegyris Chrysou (Golden Gathering)", link = "https://micras.org/mwiki/Chrysos#Panegyris_Chrysou_(Golden_Gathering)", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Aurelia|Aurelia]]" }
events["Thalassiel,10"] = { name = "Dilëtazorium",        link = nil,                                                            city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Sufriya|Sufriya]]" }
events["Thalassiel,11"] = { name = "Agrypnía Kyrenaïou",  link = nil,                                                            city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Erythros|Erythros]]" }
events["Thalassiel,16"] = { name = "Amazärum",            link = nil,                                                            city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Acheron|Acheron]]" }
events["Thalassiel,17"] = { name = "Tävrosion",           link = nil,                                                            city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Thermosalem|Thermosalem]]" }
events["Thalassiel,18"] = { name = "Bulhanum",            link = nil,                                                            city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Luminaria|Luminaria]]" }
events["Thalassiel,19"] = { name = "Mehtap Dalgası (Moonlit Tide)", link = "https://micras.org/mwiki/Thalassa_(planet)#Mehtap_Dalgas%C4%B1_(Moonlit_Tide)", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Vaeringheim|Vaeringheim]]" }
events["Thalassiel,30"] = { name = "Amáenarion",         link = nil,                                                            city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Vaeringheim|Vaeringheim]]" }
events["Thalassiel,31"] = { name = "Oneiro Foteino (Dream of Illumination)", link = "https://micras.org/mwiki/Nephele#Oneiro_Foteino_(Dream_of_Illumination)", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Somniumpolis|Somniumpolis]]" }
events["Thalassiel,39"] = { name = "Anniversary of victory in the New South Jangsong Campaign", link = nil, city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Nexa|Nexa]]" }
events["Thalassiel,44"] = { name = "Erev Galgal (Eve of Cycles)", link = "https://micras.org/mwiki/Glinos#Erev_Galgal_(Eve_of_Cycles)", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Nexa|Nexa]]" }
events["Thalassiel,45"] = { name = "Bassaridia Festival", link = nil,                                            city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Somniumpolis|Somniumpolis]]" }
events["Thalassiel,46"] = { name = "Osirionos",          link = nil,                                            city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Diamandis|Diamandis]]" }
events["Thalassiel,48"] = { name = "Anniversary of victory in the Southern Lake Morovia Campaign", link = nil, city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Somniumpolis|Somniumpolis]]" }
events["Thalassiel,50"] = { name = "Amaprion",           link = nil,                                            city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Serena|Serena]]" }
events["Thalassiel,52"] = { name = "Atämiorium",         link = nil,                                            city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Catonis_Atrium|Catonis Atrium]]" }
events["Thalassiel,54"] = { name = "Leilat al-Kamar (Night of the Moon)", link = "https://micras.org/mwiki/Noctis#Leilat_al-Kamar_(Night_of_the_Moon)", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Lunalis_Sancta|Lunalis Sancta]]" }
events["Thalassiel,56"] = { name = "Dranamorium",         link = nil,                                            city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Lykopolis|Lykopolis]]" }
events["Thalassiel,58"] = { name = "Aprobellium",         link = nil,                                            city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Thermosalem|Thermosalem]]" }
events["Thalassiel,59"] = { name = "Azorion",             link = nil,                                            city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Pyralis|Pyralis]]" }
events["Thalassiel,60"] = { name = "Ocanarion",           link = nil,                                            city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Tel-Amin|Tel-Amin]]" }
events["Thalassiel,61"] = { name = "Taşrakah (Reverence of the Stone)", link = "https://micras.org/mwiki/Tarsica#Ta%C5%9Frakah_(Reverence_of_the_Stone)", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Luminaria|Luminaria]]" }

-- Opsitheiel (Days 123..183)
events["Opsitheiel,1"]  = { name = "Cricket Fall Festival", link = "https://micras.org/mwiki/Alperkin#Summer-to-Fall:_The_Cricket_Fall_Festival", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Alperkin|Alperkin]]" }
events["Opsitheiel,6"]  = { name = "Chag Tvuah (Festival of Harvest)", link = "https://micras.org/mwiki/Opsithe#Chag_Tvuah_(Festival_of_Harvest)", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Sylvapolis|Sylvapolis]]" }
events["Opsitheiel,7"]  = { name = "Nyarion",             link = nil,                                                                     city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Delphica|Delphica]]" }
events["Opsitheiel,13"] = { name = "Danassion",            link = nil,                                                                     city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Diamandis|Diamandis]]" }
events["Opsitheiel,18"] = { name = "Anagenesis Eirmos (Procession of Rebirth)", link = nil,                              city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Acheron|Acheron]]" }
events["Opsitheiel,23"] = { name = "Hazamorium",           link = nil,                                                                     city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Sufriya|Sufriya]]" }
events["Opsitheiel,28"] = { name = "Panagia Therizis (Holy Day of the Reaper)", link = "https://micras.org/mwiki/Opsithe#Panagia_Therizis_(Holy_Day_of_the_Reaper)", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Sylvapolis|Sylvapolis]]" }
events["Opsitheiel,30"] = { name = "The Silence",          link = "https://micras.org/mwiki/Alperkin#Fall-to-Winter:_The_Silence", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Alperkin|Alperkin]]" }
events["Opsitheiel,35"] = { name = "Häparion",             link = nil,                                                                     city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Vaeringheim|Vaeringheim]]" }
events["Opsitheiel,37"] = { name = "Anniversary of victory in the Morovian Frontier Campaign", link = nil, city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Acheron|Acheron]]" }
events["Opsitheiel,43"] = { name = "Karnavali Thysias (Carnival of Celebration)", link = "https://micras.org/mwiki/Silenus#Karnavali_Thysias_(Carnival_of_Celebration)", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Erythros|Erythros]]" }
events["Opsitheiel,53"] = { name = "Sefar Yashar (Straight Path Celebration)", link = "https://micras.org/mwiki/Cato_(moon)#Sefar_Yashar_(Straight_Path_Celebration)", city = "[[List_of_cities_in_Bassaridia_Vaeringheim#Catonis_Atrium|Catonis Atrium]]" }

----------------------------------------------------------------
-- 2. Orbital parameters and constants
----------------------------------------------------------------
local semiMajorAxis = 384399          -- km
local eccentricity = 0.0549           -- approximate, Earth-Moon analog
local moonRadius = 1737               -- km

-- Assume the star is Sun-like
local sunRadius = 696000             -- km
local sunDistance = 87600000         -- km (Micras–star distance)

local synodicPeriod = 24.8           -- days between successive new/full alignments
local micrasYearLength = 183         -- days per PSSC year

----------------------------------------------------------------
-- 3. Compute moon–planet distance on a given day of its orbit
--    using a simple elliptic approximation
----------------------------------------------------------------
local function getTarsicaDistance(day)
    -- Compute mean anomaly M (radians)
    local phase = ((day % synodicPeriod) / synodicPeriod)
    local M = phase * 2 * math.pi  -- mean anomaly

    -- Approximate eccentric anomaly E via first-order iteration:
    local E = M + eccentricity * math.sin(M)
    -- True anomaly approximation → r = a * (1 - e * cos(E))
    local r = semiMajorAxis * (1 - eccentricity * math.cos(E))
    return r  -- km
end

----------------------------------------------------------------
-- 4. Determine solar eclipse subtype from instantaneous distance
----------------------------------------------------------------
local function getSolarSubtype(day)
    local r = getTarsicaDistance(day)
    -- Compute angular diameters (radians)
    local moonAng = 2 * math.atan(moonRadius / r)
    local sunAng  = 2 * math.atan(sunRadius  / sunDistance)
    -- Compare: if moon covers sun, total; otherwise annular
    if moonAng >= sunAng then
        return "Total Solar"
    else
        return "Annular Solar"
    end
end

----------------------------------------------------------------
-- 5. Determine lunar eclipse subtype (simplified)
--    If Tarsica is near perigee, we assume total; otherwise penumbral
----------------------------------------------------------------
local function getLunarSubtype(day)
    local r = getTarsicaDistance(day)
    -- If within 5% of perigee, categorize as total; else penumbral
    local perigee = semiMajorAxis * (1 - eccentricity)
    if r <= perigee * 1.05 then
        return "Total Lunar"
    else
        return "Penumbral Lunar"
    end
end

----------------------------------------------------------------
-- 6. Zodiac definitions
----------------------------------------------------------------
local zodiacSigns = {
    { name = "Atosien",    start = 1,   stop = 12 },
    { name = "Eosena",     start = 13,  stop = 24 },
    { name = "Micrasha",   start = 25,  stop = 36 },
    { name = "Pyreska",    start = 37,  stop = 48 },
    { name = "Indomin",    start = 49,  stop = 61 },
    { name = "Chrysen",    start = 62,  stop = 73 },
    { name = "Thalassian", start = 74,  stop = 85 },
    { name = "Nephelia",   start = 86,  stop = 97 },
    { name = "Glinaeus",   start = 98,  stop = 109 },
    { name = "Noctien",    start = 110, stop = 122 },
    { name = "Opsithia",   start = 123, stop = 133 },
    { name = "Stygian",    start = 134, stop = 145 },
    { name = "Faunian",    start = 146, stop = 157 },
    { name = "Silenian",   start = 158, stop = 169 },
    { name = "Catosien",   start = 170, stop = 183 }
}

----------------------------------------------------------------
-- 7. Helper: Determine month
----------------------------------------------------------------
local function getMonth(day)
    if day <= 61 then
        return "Atosiel"
    elseif day <= 122 then
        return "Thalassiel"
    else
        return "Opsitheiel"
    end
end

----------------------------------------------------------------
-- 8. Helper: Determine zodiac sign
----------------------------------------------------------------
local function getZodiac(day)
    for _, z in ipairs(zodiacSigns) do
        if day >= z.start and day <= z.stop then
            return z.name
        end
    end
    return "Unknown"
end

----------------------------------------------------------------
-- 9. Helper: Format holiday entry
----------------------------------------------------------------
local function formatEvent(event)
    if event.link and event.name then
        -- Show as: [URL name]  (then city in italics underneath)
        return string.format("[%s %s]<br><small>''%s''</small>", event.link, event.name, event.city or "")
    elseif event.name then
        -- No URL, just bold name
        return string.format("'''%s'''", event.name)
    else
        return "—"
    end
end

----------------------------------------------------------------
-- 10. Main function: Build the Tarsican Saros table
----------------------------------------------------------------
function p.sarosTable(frame)
    local output = {}

    table.insert(output, "== Tarsican Eclipse Schedule ==\n")
    table.insert(output, '{| class="wikitable sortable" style="width:100%; text-align:center;"')
    table.insert(output, '|-\n! # !! Day !! Month !! Zodiac !! Eclipse Type !! Nearby Ritual or Holiday')

    local eclipseDay = 18   -- Starting offset
    local isSolar = true

    for i = 1, 10 do
        if eclipseDay > micrasYearLength then
            break
        end

        local eclipseType, subtype
        if isSolar then
            eclipseType = "Solar"
            subtype = getSolarSubtype(eclipseDay)
        else
            eclipseType = "Lunar"
            subtype = getLunarSubtype(eclipseDay)
        end

        local month = getMonth(eclipseDay)
        local zodiac = getZodiac(eclipseDay)

        -- Find adjacent holiday (±1 day)
        local holidayOutput = "—"
        for offset = -1, 1 do
            local checkDay = eclipseDay + offset
            if checkDay >= 1 and checkDay <= micrasYearLength then
                local checkMonth = getMonth(checkDay)
                local dayInMonth = checkDay
                if checkMonth == "Thalassiel" then
                    dayInMonth = checkDay - 61
                elseif checkMonth == "Opsitheiel" then
                    dayInMonth = checkDay - 122
                end
                local key = checkMonth .. "," .. dayInMonth
                if events[key] then
                    holidayOutput = formatEvent(events[key])
                    break
                end
            end
        end

        table.insert(output, string.format(
            "|-\n| %d || %d || %s || %s || %s || %s",
            i, eclipseDay, month, zodiac, subtype, holidayOutput
        ))

        -- Advance to next eclipse
        local drift = ((-1)^i) * 2.3
        eclipseDay = math.floor(eclipseDay + synodicPeriod + drift)
        isSolar = not isSolar
    end

    table.insert(output, "|}")
    return table.concat(output, "\n")
end

return p