Module:CultRelationsDailyBVR
From MicrasWiki
Jump to navigationJump to search
Documentation for this module may be created at Module:CultRelationsDailyBVR/doc
-- Module:CultRelationsDailyBVR
-- Dynamic cult-relations index for the Reformed Stripping Path.
--
-- This module:
-- * Uses [[Module:BassaridianCalendar]] for the current PSSC date.
-- * Stores a fixed roster of cults/mysteries from Planetary Devotion.
-- * Starts from baseline alliances and rivalries.
-- * Applies deterministic daily events so relations change automatically by day.
-- * Provides table, pair, matrix, event-log, and raw-data functions.
--
-- Suggested invocations:
-- {{#invoke:CultRelationsDailyBVR|makeTable}}
-- {{#invoke:CultRelationsDailyBVR|makeTable|limit=30}}
-- {{#invoke:CultRelationsDailyBVR|makeTable|year=53|day=95}}
-- {{#invoke:CultRelationsDailyBVR|makePair|a=Harmony Sanctum|b=Ignis Aeternum}}
-- {{#invoke:CultRelationsDailyBVR|eventLog}}
-- {{#invoke:CultRelationsDailyBVR|makeMatrix}}
-- {{#invoke:CultRelationsDailyBVR|legend}}
--
-- Optional bridge from [[Module:MissionaryDailyOpsBVR]]:
-- local cultRel = require("Module:CultRelationsDailyBVR")
-- cultRel.makeTable(frame)
local p = {}
local calOk, cal = pcall(require, "Module:BassaridianCalendar")
if not calOk then
cal = nil
end
local DAYS_IN_YEAR = 183
--------------------------------------------------------------------------------
-- 1. BASIC UTILITIES
--------------------------------------------------------------------------------
local function trim(s)
s = tostring(s or "")
return (s:gsub("^%s+", ""):gsub("%s+$", ""))
end
local function lower(s)
return mw.ustring.lower(trim(s))
end
local function clamp(n, lo, hi)
n = tonumber(n) or 0
if n < lo then return lo end
if n > hi then return hi end
return n
end
local function sortedPair(a, b)
a, b = trim(a), trim(b)
if a < b then
return a .. "||" .. b
end
return b .. "||" .. a
end
local function splitPairKey(k)
local a, b = tostring(k or ""):match("^(.-)%|%|(.*)$")
return a or "", b or ""
end
local function tableContains(t, value)
for _, v in ipairs(t or {}) do
if v == value then
return true
end
end
return false
end
local function shallowCopy(t)
local out = {}
for k, v in pairs(t or {}) do
out[k] = v
end
return out
end
local function hash31(str)
str = tostring(str or "")
local h = 0
for i = 1, #str do
h = (h * 131 + str:byte(i)) % 2147483647
end
return h
end
local function lcg(seed)
local s = tonumber(seed) or 1
s = s % 2147483647
if s <= 0 then
s = s + 2147483646
end
return function()
s = (1103515245 * s + 12345) % 2147483647
return s
end
end
local function pick(nextInt, list)
if not list or #list == 0 then
return nil
end
return list[(nextInt() % #list) + 1]
end
local function pickWeighted(nextInt, list)
local total = 0
for _, item in ipairs(list or {}) do
total = total + (tonumber(item.w) or 0)
end
if total <= 0 then
return list and list[1] or nil
end
local r = nextInt() % total
local acc = 0
for _, item in ipairs(list) do
acc = acc + (tonumber(item.w) or 0)
if r < acc then
return item
end
end
return list[#list]
end
--------------------------------------------------------------------------------
-- 2. DATE HANDLING
--------------------------------------------------------------------------------
local monthNames = {
[1] = "Atosiel",
[2] = "Thalassiel",
[3] = "Opsitheiel"
}
local zodiacByDay = {
[1] = {
{12, "Atosien"},
{24, "Eosena"},
{36, "Micrasha"},
{48, "Pyreska"},
{61, "Indomin"}
},
[2] = {
{12, "Chrysen"},
{24, "Thalassian"},
{36, "Nephelia"},
{48, "Glinaeus"},
{61, "Noctien"}
},
[3] = {
{12, "Opsithia"},
{24, "Stygian"},
{36, "Faunian"},
{48, "Silenian"},
{61, "Catosien"}
}
}
local function doyToMonthDom(doy)
doy = clamp(doy, 1, DAYS_IN_YEAR)
if doy <= 61 then
return 1, doy
elseif doy <= 122 then
return 2, doy - 61
else
return 3, doy - 122
end
end
local function monthDomToDoy(month, dom)
month = tonumber(month) or 1
dom = tonumber(dom) or 1
if month == 1 then
return clamp(dom, 1, 61)
elseif month == 2 then
return 61 + clamp(dom, 1, 61)
else
return 122 + clamp(dom, 1, 61)
end
end
local function zodiacFor(monthNum, dom)
local rows = zodiacByDay[monthNum] or zodiacByDay[1]
for _, row in ipairs(rows) do
if dom <= row[1] then
return row[2]
end
end
return rows[#rows][2]
end
local function parseCalendarString(raw)
raw = tostring(raw or "")
-- Expected from getCurrentDateNoProverb:
-- "95, Thalassiel (Noctien), 53 PSSC"
local doy, month, zodiac, year = raw:match("^(%d+),%s*([%a]+)%s*%(([^%)]+)%),%s*(%d+)%s*PSSC")
if doy and month and year then
local d = tonumber(doy)
local y = tonumber(year)
local m = 1
if month == "Thalassiel" then
m = 2
elseif month == "Opsitheiel" then
m = 3
end
local mn, dom = doyToMonthDom(d)
return {
year = y,
dayOfYear = d,
monthNum = mn,
month = monthNames[mn],
dom = dom,
zodiac = zodiac or zodiacFor(mn, dom),
raw = raw,
text = string.format("%d/%d/%d PSSC", dom, mn, y),
longText = string.format("%d, %s (%s), %d PSSC", d, monthNames[mn], zodiac or zodiacFor(mn, dom), y)
}
end
-- Accept a simpler "day/month/year PSSC" pattern.
local dom2, month2, year2 = raw:match("^(%d+)/(%d+)/(%d+)%s*PSSC")
if dom2 and month2 and year2 then
local mn = clamp(tonumber(month2), 1, 3)
local dm = clamp(tonumber(dom2), 1, 61)
local y = tonumber(year2)
local d = monthDomToDoy(mn, dm)
local z = zodiacFor(mn, dm)
return {
year = y,
dayOfYear = d,
monthNum = mn,
month = monthNames[mn],
dom = dm,
zodiac = z,
raw = raw,
text = string.format("%d/%d/%d PSSC", dm, mn, y),
longText = string.format("%d, %s (%s), %d PSSC", d, monthNames[mn], z, y)
}
end
return nil
end
local function getDateInfo(args)
args = args or {}
local y = tonumber(args.year or args.psscyear)
local doy = tonumber(args.day or args.dayofyear or args.doy)
if y and doy then
doy = clamp(doy, 1, DAYS_IN_YEAR)
local mn, dom = doyToMonthDom(doy)
local z = zodiacFor(mn, dom)
return {
year = y,
dayOfYear = doy,
monthNum = mn,
month = monthNames[mn],
dom = dom,
zodiac = z,
raw = string.format("%d, %s (%s), %d PSSC", doy, monthNames[mn], z, y),
text = string.format("%d/%d/%d PSSC", dom, mn, y),
longText = string.format("%d, %s (%s), %d PSSC", doy, monthNames[mn], z, y)
}
end
local raw = ""
if cal and type(cal.getCurrentDateNoProverb) == "function" then
raw = cal.getCurrentDateNoProverb()
elseif cal and type(cal.getCurrentDate) == "function" then
raw = cal.getCurrentDate()
end
local parsed = parseCalendarString(raw)
if parsed then
return parsed
end
-- Stable fallback if the calendar module is unavailable.
local fallbackYear = 53
local fallbackDay = 1
local mn, dom = doyToMonthDom(fallbackDay)
local z = zodiacFor(mn, dom)
return {
year = fallbackYear,
dayOfYear = fallbackDay,
monthNum = mn,
month = monthNames[mn],
dom = dom,
zodiac = z,
raw = "Calendar unavailable",
text = string.format("%d/%d/%d PSSC", dom, mn, fallbackYear),
longText = string.format("%d, %s (%s), %d PSSC", fallbackDay, monthNames[mn], z, fallbackYear)
}
end
--------------------------------------------------------------------------------
-- 3. CULT ROSTER
-- Based on Planetary Devotion / Mystery table.
--------------------------------------------------------------------------------
local cults = {
{
key = "OSI",
name = "Ordo Solis Invicti",
divine = "Atos",
city = "Vaeringheim",
page = "Atos (Reformed Stripping Path)",
anchor = "Ordo Solis Invicti",
logo = "AtosSygil.png",
tendency = {"order", "measure", "light", "state"}
},
{
key = "AUR",
name = "Order of Aurora Mystica",
divine = "Eos",
city = "Luminaria",
page = "Eos (Reformed Stripping Path)",
anchor = "Order of Aurora Mystica",
logo = "Auroramystica.png",
tendency = {"dawn", "renewal", "illumination", "mediation"}
},
{
key = "HAR",
name = "Harmony Sanctum",
divine = "Micras",
city = "Serena",
page = "Micras (Reformed Stripping Path)",
anchor = "Harmony Sanctum",
logo = "Harmonysanctum.png",
tendency = {"balance", "wisdom", "mediation", "order"}
},
{
key = "IGN",
name = "Ignis Aeternum",
divine = "Pyros",
city = "Pyralis",
page = "Pyros (Reformed Stripping Path)",
anchor = "Ignis Aeternum",
logo = "Ignis Aeternum.png",
tendency = {"fire", "renewal", "passion", "destruction"}
},
{
key = "CHS",
name = "Celestial Harmony Sect",
divine = "Indigo and Momiji",
city = "Symphonara",
page = "Indigo (Reformed Stripping Path)",
anchor = "Celestial Harmony Sect",
logo = "HarmonySect.png",
tendency = {"duality", "change", "mediation", "ambiguity"}
},
{
key = "GGS",
name = "Guild of Golden Shadows",
divine = "Chrysos",
city = "Aurelia",
page = "Chrysos (Reformed Stripping Path)",
anchor = "Guild of Golden Shadows",
logo = "GoldenShadows.png",
tendency = {"wealth", "trade", "secrecy", "espionage"}
},
{
key = "AZU",
name = "Azure Sentinel Sect",
divine = "Thalassa",
city = "Vaeringheim",
page = "Thalassa (Reformed Stripping Path)",
anchor = "Azure Sentinel Sect",
logo = "AzureSentinel.png",
tendency = {"sea", "war", "depth", "mystery"}
},
{
key = "RNB",
name = "Reverie Nebulous",
divine = "Nephele",
city = "Somniumpolis",
page = "Nephele (Reformed Stripping Path)",
anchor = "Reverie Nebulous",
logo = "RevNeb.png",
tendency = {"dreams", "clouds", "imagination", "omens"}
},
{
key = "EOF",
name = "Eon Fellowship",
divine = "Glinos",
city = "Nexa",
page = "Glinos (Reformed Stripping Path)",
anchor = "Eon Fellowship",
logo = "EonFel.png",
tendency = {"winter", "death", "cycle", "endurance"}
},
{
key = "UMB",
name = "Order of the Umbral Oracle",
divine = "Noctis",
city = "Lunalis Sancta",
page = "Noctis (Reformed Stripping Path)",
anchor = "Order of the Umbral Oracle",
logo = "Umbral.png",
tendency = {"night", "secrecy", "truth", "introspection"}
},
{
key = "VDE",
name = "Mystery of the Verdant Embrace",
divine = "Opsithe",
city = "Sylvapolis",
page = "Opsithe (Reformed Stripping Path)",
anchor = "Mystery of the Verdant Embrace",
logo = "Verdant.png",
tendency = {"harvest", "fertility", "nature", "stability"}
},
{
key = "CON",
name = "Conclave Illuminara",
divine = "Tarsica",
city = "Luminaria",
page = "Tarsica (Reformed Stripping Path)",
anchor = "Conclave Illuminara",
logo = "Conclave.png",
tendency = {"illumination", "wisdom", "moon", "eclipse"}
},
{
key = "SVI",
name = "Sanctum Vitalis",
divine = "Plateau",
city = "Saluria",
page = "Plateau (Reformed Stripping Path)",
anchor = "Sanctum Vitalis",
logo = "Vitalis.png",
tendency = {"health", "medicine", "relief", "stability"}
},
{
key = "ALB",
name = "Temple Alabaster",
divine = "Ivory",
city = "Aetherium",
page = "Ivory (Reformed Stripping Path)",
anchor = "Temple Alabaster",
logo = "Alabaster.png",
tendency = {"purity", "serenity", "neutrality", "healing"}
},
{
key = "CICL",
name = "Court of the Ironclad",
divine = "Fenrir",
city = "Ferrum Citadel",
page = "Fenrir (Reformed Stripping Path)",
anchor = "Court of the Ironclad",
logo = "Ironclad.png",
tendency = {"strength", "tenacity", "security", "militancy"}
},
{
key = "ACC",
name = "Accord Concordia",
divine = "Lukedu",
city = "Symphonara",
page = "Lukedu (Reformed Stripping Path)",
anchor = "Accord Concordia",
logo = "Concordia.png",
tendency = {"unity", "peace", "mediation", "diplomacy"}
},
{
key = "STY",
name = "Mystery of the Stygian Veil",
divine = "Styx",
city = "Acheron",
page = "Styx (Reformed Stripping Path)",
anchor = "Mystery of the Stygian Veil",
logo = "StygianVe.png",
tendency = {"transformation", "rebirth", "death", "threshold"}
},
{
key = "SYL",
name = "Sylvan Fellowship",
divine = "Faun",
city = "Sylvapolis",
page = "Faun (Reformed Stripping Path)",
anchor = "Sylvan Fellowship",
logo = "Sylvan.png",
tendency = {"nature", "fertility", "wilds", "balance"}
},
{
key = "MIR",
name = "Mystery of Red Mirth",
divine = "Silenus",
city = "Erythros",
page = "Silenus (Reformed Stripping Path)",
anchor = "Mystery of Red Mirth",
logo = "MirthFlag.png",
tendency = {"merriment", "festival", "wine", "release"}
},
{
key = "CAT",
name = "Rex Catonis",
divine = "Cato",
city = "Catonis Atrium",
page = "Cato (Reformed Stripping Path)",
anchor = "Rex Catonis",
logo = "Catonis.png",
tendency = {"discipline", "order", "law", "judgment"}
},
{
key = "SDE",
name = "Sanctum Delphica",
divine = "Sisera",
city = "Delphica",
page = "Sisera (Reformed Stripping Path)",
anchor = "Sanctum Delphica",
logo = "SanctumDel.png",
tendency = {"reflection", "introspection", "records", "truth"}
},
{
key = "AMI",
name = "Ordo Amicitia",
divine = "Erasmus",
city = "Koinonía",
page = "Erasmus (Reformed Stripping Path)",
anchor = "Ordo Amicitia",
logo = "OrdoFl.png",
tendency = {"charity", "wisdom", "relief", "mercy"}
},
{
key = "ILL",
name = "Temple Illuminata",
divine = "Nikolaj",
city = "Aureum",
page = "Nikolaj (Reformed Stripping Path)",
anchor = "Temple Illuminata",
logo = "AureumFlag.png",
tendency = {"epiphany", "mystery", "cosmic", "insight"}
}
}
local cultByName = {}
local cultByKey = {}
local cultAliases = {}
for _, c in ipairs(cults) do
cultByName[c.name] = c
cultByKey[c.key] = c
cultAliases[lower(c.name)] = c.name
cultAliases[lower(c.key)] = c.name
cultAliases[lower(c.divine)] = c.name
end
-- Common shorthand aliases.
cultAliases["aurora"] = "Order of Aurora Mystica"
cultAliases["aurora mystica"] = "Order of Aurora Mystica"
cultAliases["harmony"] = "Harmony Sanctum"
cultAliases["ignis"] = "Ignis Aeternum"
cultAliases["golden shadows"] = "Guild of Golden Shadows"
cultAliases["azure"] = "Azure Sentinel Sect"
cultAliases["reverie"] = "Reverie Nebulous"
cultAliases["eon"] = "Eon Fellowship"
cultAliases["umbral"] = "Order of the Umbral Oracle"
cultAliases["verdant"] = "Mystery of the Verdant Embrace"
cultAliases["illuminara"] = "Conclave Illuminara"
cultAliases["vitalis"] = "Sanctum Vitalis"
cultAliases["alabaster"] = "Temple Alabaster"
cultAliases["ironclad"] = "Court of the Ironclad"
cultAliases["concordia"] = "Accord Concordia"
cultAliases["stygian veil"] = "Mystery of the Stygian Veil"
cultAliases["sylvan"] = "Sylvan Fellowship"
cultAliases["red mirth"] = "Mystery of Red Mirth"
cultAliases["catonis"] = "Rex Catonis"
cultAliases["delphica"] = "Sanctum Delphica"
cultAliases["amicitia"] = "Ordo Amicitia"
cultAliases["illuminata"] = "Temple Illuminata"
local function normalizeCultName(name)
name = trim(name)
if cultByName[name] then
return name
end
return cultAliases[lower(name)] or name
end
local function cultLink(name)
name = normalizeCultName(name)
local c = cultByName[name]
if not c then
return "[[" .. name .. "]]"
end
return string.format("[[%s#%s|%s]]", c.page, c.anchor, c.name)
end
local function cultMark(name)
name = normalizeCultName(name)
local c = cultByName[name]
if not c then
return cultLink(name)
end
local logo = ""
if c.logo and c.logo ~= "" then
logo = string.format("[[File:%s|18px]] ", c.logo)
end
return logo .. cultLink(name)
end
local function cityLink(city)
city = trim(city)
return string.format("[[List of cities in Bassaridia Vaeringheim#%s|%s]]", city, city)
end
local function eventCityLink(event)
if not event then
return "—"
end
local cultName = normalizeCultName(event.a)
local cult = cultByName[cultName]
if not cult or not cult.city or cult.city == "" then
return "—"
end
return cityLink(cult.city)
end
local function eventCityLink(event)
if not event then
return "—"
end
local cultName = normalizeCultName(event.a)
local cult = cultByName[cultName]
if not cult or not cult.city or cult.city == "" then
return "—"
end
return cityLink(cult.city)
end
--------------------------------------------------------------------------------
-- 4. BASELINE RELATIONS
--
-- Scale:
-- +75 covenantal alliance
-- +55 strong alliance
-- +35 standing alliance
-- +15 mild affinity
-- 0 neutral
-- -15 mild tension
-- -35 standing rivalry
-- -55 hostile rivalry
-- -75 sectarian enmity
--------------------------------------------------------------------------------
local baselineRows = {
["Ordo Solis Invicti"] = {
allies = {"Rex Catonis", "Harmony Sanctum", "Order of Aurora Mystica"},
enemies = {},
note = "solar order, measure, and public adjudication"
},
["Order of Aurora Mystica"] = {
allies = {"Eon Fellowship", "Conclave Illuminara", "Harmony Sanctum", "Ordo Solis Invicti"},
enemies = {"Celestial Harmony Sect", "Ignis Aeternum"},
note = "dawn rites, renewal, and anti-destruction cooperation"
},
["Harmony Sanctum"] = {
allies = {"Order of Aurora Mystica", "Mystery of the Verdant Embrace", "Guild of Golden Shadows", "Conclave Illuminara", "Accord Concordia", "Ordo Solis Invicti"},
enemies = {"Ignis Aeternum", "Azure Sentinel Sect", "Court of the Ironclad", "Sylvan Fellowship"},
note = "balance-oriented center of the faith"
},
["Ignis Aeternum"] = {
allies = {"Azure Sentinel Sect", "Sylvan Fellowship"},
enemies = {"Eon Fellowship", "Harmony Sanctum", "Reverie Nebulous", "Mystery of the Verdant Embrace", "Order of Aurora Mystica", "Court of the Ironclad", "Guild of Golden Shadows"},
note = "purifying flame and destructive renewal"
},
["Celestial Harmony Sect"] = {
allies = {"Order of the Umbral Oracle", "Accord Concordia", "Sanctum Delphica"},
enemies = {"Order of Aurora Mystica", "Conclave Illuminara", "Rex Catonis"},
note = "duality, change, and mediating ambiguity"
},
["Guild of Golden Shadows"] = {
allies = {"Harmony Sanctum", "Reverie Nebulous", "Court of the Ironclad", "Sanctum Delphica"},
enemies = {"Ignis Aeternum", "Azure Sentinel Sect"},
note = "trade, intelligence, brokerage, and covert influence"
},
["Azure Sentinel Sect"] = {
allies = {"Ignis Aeternum", "Reverie Nebulous"},
enemies = {"Eon Fellowship", "Harmony Sanctum", "Guild of Golden Shadows"},
note = "sea, war, depth, and temple defense"
},
["Reverie Nebulous"] = {
allies = {"Guild of Golden Shadows", "Azure Sentinel Sect", "Sanctum Delphica"},
enemies = {"Ignis Aeternum", "Eon Fellowship", "Court of the Ironclad"},
note = "dreams, imagination, omen-craft, and social volatility"
},
["Eon Fellowship"] = {
allies = {"Order of Aurora Mystica", "Court of the Ironclad", "Temple Illuminata"},
enemies = {"Ignis Aeternum", "Azure Sentinel Sect", "Sylvan Fellowship", "Reverie Nebulous"},
note = "winter, endurance, death, and cyclical renewal"
},
["Order of the Umbral Oracle"] = {
allies = {"Harmony Sanctum", "Ordo Amicitia", "Celestial Harmony Sect", "Sanctum Delphica"},
enemies = {},
note = "night, hidden truth, introspection, and oracle-work"
},
["Mystery of the Verdant Embrace"] = {
allies = {"Harmony Sanctum", "Ordo Amicitia", "Sanctum Vitalis", "Sylvan Fellowship"},
enemies = {"Ignis Aeternum", "Court of the Ironclad"},
note = "harvest, fertility, cultivated nature, and anti-destruction practice"
},
["Conclave Illuminara"] = {
allies = {"Order of Aurora Mystica", "Harmony Sanctum", "Ordo Solis Invicti"},
enemies = {"Celestial Harmony Sect"},
note = "Tarsican illumination, eclipse interpretation, and wisdom rites"
},
["Sanctum Vitalis"] = {
allies = {"Temple Alabaster", "Ordo Amicitia", "Mystery of the Verdant Embrace", "Harmony Sanctum"},
enemies = {},
note = "health, medicine, relief, and recovery"
},
["Temple Alabaster"] = {
allies = {"Court of the Ironclad", "Ordo Amicitia", "Sanctum Vitalis", "Rex Catonis"},
enemies = {},
note = "purity, serenity, neutrality, and protected precincts"
},
["Court of the Ironclad"] = {
allies = {"Eon Fellowship", "Guild of Golden Shadows", "Temple Alabaster", "Rex Catonis"},
enemies = {"Ignis Aeternum", "Harmony Sanctum", "Reverie Nebulous", "Mystery of the Verdant Embrace"},
note = "strength, tenacity, armed discipline, and crisis response"
},
["Accord Concordia"] = {
allies = {"Sanctum Delphica", "Harmony Sanctum", "Celestial Harmony Sect", "Ordo Amicitia"},
enemies = {},
note = "unity, diplomacy, and inter-cult mediation"
},
["Mystery of the Stygian Veil"] = {
allies = {"Temple Illuminata", "Order of the Umbral Oracle", "Sanctum Vitalis"},
enemies = {"Ordo Amicitia"},
note = "transformation, rebirth, death, and threshold rites"
},
["Sylvan Fellowship"] = {
allies = {"Ignis Aeternum", "Mystery of the Verdant Embrace"},
enemies = {"Eon Fellowship", "Harmony Sanctum"},
note = "wild fertility, natural balance, and resistance to over-ordering"
},
["Mystery of Red Mirth"] = {
allies = {"Rex Catonis", "Accord Concordia", "Ordo Amicitia"},
enemies = {},
note = "celebration, release, and ritual merriment"
},
["Rex Catonis"] = {
allies = {"Mystery of Red Mirth", "Court of the Ironclad", "Temple Alabaster", "Ordo Solis Invicti"},
enemies = {"Celestial Harmony Sect"},
note = "discipline, order, law, and moral clarity"
},
["Sanctum Delphica"] = {
allies = {"Accord Concordia", "Reverie Nebulous", "Order of the Umbral Oracle", "Guild of Golden Shadows", "Celestial Harmony Sect"},
enemies = {},
note = "reflection, records, introspection, and arbitration"
},
["Ordo Amicitia"] = {
allies = {"Order of the Umbral Oracle", "Mystery of the Verdant Embrace", "Temple Alabaster", "Sanctum Vitalis", "Accord Concordia", "Mystery of Red Mirth"},
enemies = {"Mystery of the Stygian Veil"},
note = "charity, wisdom, aid distribution, and mercy"
},
["Temple Illuminata"] = {
allies = {"Mystery of the Stygian Veil", "Eon Fellowship", "Conclave Illuminara"},
enemies = {},
note = "epiphanies, cosmic insight, and mystery"
}
}
local mythicAffinities = {
{"Order of Aurora Mystica", "Eon Fellowship", 15, "Eos–Glinos mythic affinity"},
{"Order of Aurora Mystica", "Conclave Illuminara", 15, "Eos–Tarsica mythic affinity"},
{"Harmony Sanctum", "Mystery of the Verdant Embrace", 15, "Micras–Opsithe mythic affinity"},
{"Guild of Golden Shadows", "Reverie Nebulous", 15, "Chrysos–Nephele mythic affinity"},
{"Guild of Golden Shadows", "Accord Concordia", 8, "Chrysos–Lukedu lineage affinity"},
{"Azure Sentinel Sect", "Ignis Aeternum", 15, "Thalassa–Pyros mythic affinity"},
{"Order of the Umbral Oracle", "Ordo Amicitia", 12, "Noctis–Erasmus affinity"},
{"Accord Concordia", "Sanctum Delphica", 15, "Lukedu–Sisera mythic affinity"},
{"Mystery of Red Mirth", "Rex Catonis", 15, "Silenus–Cato mythic affinity"},
{"Mystery of the Stygian Veil", "Temple Illuminata", 15, "Styx–Nikolaj mythic affinity"},
{"Court of the Ironclad", "Temple Alabaster", 12, "Fenrir–Ivory mythic affinity"},
{"Sanctum Vitalis", "Mystery of the Verdant Embrace", 10, "Plateau–Opsithe lineage affinity"},
{"Sylvan Fellowship", "Mystery of the Verdant Embrace", 10, "Faun–Opsithe natural affinity"},
{"Ignis Aeternum", "Harmony Sanctum", -15, "Pyros–Micras mythic tension"},
{"Ignis Aeternum", "Mystery of the Verdant Embrace", -15, "Pyros–Opsithe mythic tension"},
{"Ignis Aeternum", "Reverie Nebulous", -12, "Pyros–Nephele mythic tension"},
{"Azure Sentinel Sect", "Harmony Sanctum", -10, "Thalassa–Micras mythic tension"},
{"Order of Aurora Mystica", "Celestial Harmony Sect", -10, "Eos–Indigo/Momiji mythic tension"},
{"Conclave Illuminara", "Celestial Harmony Sect", -10, "Tarsica–Indigo/Momiji mythic tension"},
{"Eon Fellowship", "Azure Sentinel Sect", -10, "Glinos–Thalassa mythic tension"},
{"Eon Fellowship", "Sylvan Fellowship", -8, "winter–wild fertility tension"},
{"Mystery of the Stygian Veil", "Ordo Amicitia", -8, "Styx–Nyssa/Erasmus secrecy tension"}
}
local function buildBaseScores()
local scores = {}
local notes = {}
local function add(a, b, delta, note)
a, b = normalizeCultName(a), normalizeCultName(b)
if a == "" or b == "" or a == b then
return
end
local k = sortedPair(a, b)
scores[k] = clamp((scores[k] or 0) + delta, -150, 150)
notes[k] = notes[k] or {}
table.insert(notes[k], note)
end
for name, row in pairs(baselineRows) do
for _, ally in ipairs(row.allies or {}) do
add(name, ally, 12, "standing affinity")
end
for _, enemy in ipairs(row.enemies or {}) do
add(name, enemy, -12, "standing grievance")
end
end
for _, rel in ipairs(mythicAffinities) do
local scaled = math.floor((tonumber(rel[3]) or 0) * 0.45)
if scaled == 0 and tonumber(rel[3]) and tonumber(rel[3]) ~= 0 then
scaled = rel[3] > 0 and 1 or -1
end
add(rel[1], rel[2], scaled, rel[4])
end
return scores, notes
end
--------------------------------------------------------------------------------
-- 5. DAILY EVENT POOL
--------------------------------------------------------------------------------
local eventPool = {
{
w = 9,
type = "relief",
severity = "green",
delta = 8,
title = "Joint relief distribution",
text = "{A} and {B} jointly distribute food, medicine, or shelter stipends through Temple Bank channels.",
cults = {"Ordo Amicitia", "Sanctum Vitalis", "Temple Alabaster", "Harmony Sanctum", "Mystery of the Verdant Embrace"}
},
{
w = 8,
type = "mediation",
severity = "green",
delta = 7,
title = "Mediation circuit",
text = "{A} and {B} conduct a mediation circuit in a tense district, improving working confidence.",
cults = {"Harmony Sanctum", "Accord Concordia", "Order of Aurora Mystica", "Celestial Harmony Sect", "Sanctum Delphica"}
},
{
w = 7,
type = "audit",
severity = "green",
delta = 6,
title = "Temple Bank audit clears both sides",
text = "A Temple Bank audit clears {A} and {B} of misconduct allegations, reducing public suspicion.",
cults = {"Rex Catonis", "Sanctum Delphica", "Temple Alabaster", "Guild of Golden Shadows", "Ordo Solis Invicti"}
},
{
w = 6,
type = "festival",
severity = "green",
delta = 9,
title = "Shared festival stewardship",
text = "{A} and {B} jointly steward a festival route without disorder.",
cults = {"Mystery of Red Mirth", "Rex Catonis", "Order of Aurora Mystica", "Conclave Illuminara", "Accord Concordia", "Ordo Amicitia"}
},
{
w = 6,
type = "calendar",
severity = "green",
delta = 6,
title = "Calendar synchronization",
text = "{A} and {B} agree on calendar timing for public rites and registry deadlines.",
cults = {"Ordo Solis Invicti", "Conclave Illuminara", "Order of Aurora Mystica", "Rex Catonis", "Sanctum Delphica"}
},
{
w = 6,
type = "omen",
severity = "yellow",
delta = -7,
title = "Disputed omen",
text = "{A} and {B} issue conflicting interpretations of a public omen.",
cults = {"Conclave Illuminara", "Order of the Umbral Oracle", "Temple Illuminata", "Reverie Nebulous", "Celestial Harmony Sect"}
},
{
w = 7,
type = "doctrine",
severity = "yellow",
delta = -8,
title = "Doctrinal protest",
text = "{A} denounces the ritual language used by {B}.",
cults = {"Ignis Aeternum", "Harmony Sanctum", "Eon Fellowship", "Azure Sentinel Sect", "Mystery of the Verdant Embrace", "Order of Aurora Mystica"}
},
{
w = 6,
type = "trade",
severity = "yellow",
delta = -6,
title = "Trade-route accusation",
text = "{A} accuses {B} of shielding merchants, brokers, or shrine vendors from proper review.",
cults = {"Guild of Golden Shadows", "Azure Sentinel Sect", "Rex Catonis", "Temple Alabaster", "Sanctum Delphica", "Ordo Solis Invicti"}
},
{
w = 5,
type = "security",
severity = "yellow",
delta = -7,
title = "Shrine security dispute",
text = "{A} objects to the security posture adopted by {B} near a shrine precinct.",
cults = {"Court of the Ironclad", "Azure Sentinel Sect", "Rex Catonis", "Harmony Sanctum", "Order of Aurora Mystica", "Temple Alabaster"}
},
{
w = 5,
type = "dream",
severity = "yellow",
delta = -6,
title = "Dream-prophecy disagreement",
text = "{A} and {B} dispute whether a recurring dream report should be treated as prophecy, warning, or rumor.",
cults = {"Reverie Nebulous", "Order of the Umbral Oracle", "Sanctum Delphica", "Conclave Illuminara", "Temple Illuminata"}
},
{
w = 4,
type = "panic",
severity = "red",
delta = -12,
title = "Rumor panic",
text = "Panic narratives spread between communities aligned with {A} and {B}.",
cults = {"Reverie Nebulous", "Order of the Umbral Oracle", "Temple Illuminata", "Sanctum Delphica", "Eon Fellowship"}
},
{
w = 4,
type = "violence",
severity = "red",
delta = -16,
title = "Sectarian violence",
text = "Street violence attributed to extremists worsens relations between {A} and {B}.",
cults = {"Ignis Aeternum", "Court of the Ironclad", "Azure Sentinel Sect", "Harmony Sanctum", "Reverie Nebulous", "Mystery of the Verdant Embrace"}
},
{
w = 5,
type = "mourning",
severity = "green",
delta = 7,
title = "Shared mourning rites",
text = "{A} and {B} cooperate during mourning rites after a local death, disaster, or battlefield recovery.",
cults = {"Mystery of the Stygian Veil", "Ordo Amicitia", "Order of the Umbral Oracle", "Sanctum Vitalis", "Temple Alabaster"}
},
{
w = 5,
type = "ecology",
severity = "green",
delta = 6,
title = "Ecological repair compact",
text = "{A} and {B} sign a temporary compact for ecological repair and pilgrimage safety.",
cults = {"Mystery of the Verdant Embrace", "Sylvan Fellowship", "Harmony Sanctum", "Sanctum Vitalis"}
},
{
w = 4,
type = "purification",
severity = "red",
delta = -14,
title = "Purification controversy",
text = "{A} is blamed for encouraging dangerous purification language against {B}.",
cults = {"Ignis Aeternum", "Temple Alabaster", "Harmony Sanctum", "Mystery of the Verdant Embrace", "Order of Aurora Mystica"}
},
{
w = 5,
type = "registry",
severity = "green",
delta = 5,
title = "Registry reconciliation",
text = "{A} and {B} reconcile conflicting cult ledgers through formal registry review.",
cults = {"Rex Catonis", "Sanctum Delphica", "Guild of Golden Shadows", "Ordo Amicitia", "Accord Concordia", "Ordo Solis Invicti"}
},
{
w = 5,
type = "port",
severity = "green",
delta = 6,
title = "General Port membership drive",
text = "{A} and {B} support a General Port membership drive tied to shrine investment and community stabilization.",
cults = {"Guild of Golden Shadows", "Ordo Amicitia", "Harmony Sanctum", "Accord Concordia", "Sanctum Delphica"}
},
{
w = 4,
type = "privateer",
severity = "yellow",
delta = -7,
title = "Privateer blessing dispute",
text = "{A} and {B} disagree over whether a privateer commission should receive ritual blessing.",
cults = {"Azure Sentinel Sect", "Guild of Golden Shadows", "Rex Catonis", "Court of the Ironclad", "Order of the Umbral Oracle"}
},
{
w = 4,
type = "missionary",
severity = "green",
delta = 6,
title = "Missionary escort compact",
text = "{A} and {B} agree to escort missionaries through a contested road or canal route.",
cults = {"Ordo Amicitia", "Court of the Ironclad", "Temple Alabaster", "Harmony Sanctum", "Azure Sentinel Sect"}
},
{
w = 3,
type = "noctic",
severity = "yellow",
delta = -7,
title = "Noctic registration dispute",
text = "{A} challenges {B} over Temple Bank registration standards for households affected by Noctic Vampirism.",
cults = {"Temple Alabaster", "Sanctum Vitalis", "Order of the Umbral Oracle", "Mystery of the Stygian Veil", "Rex Catonis", "Reverie Nebulous"}
},
{
w = 3,
type = "noctic",
severity = "green",
delta = 7,
title = "Noctic triage compact",
text = "{A} and {B} cooperate to stabilize registered Noctic Vampires and separate medical cases from rumor-driven panic.",
cults = {"Temple Alabaster", "Sanctum Vitalis", "Ordo Amicitia", "Order of the Umbral Oracle", "Sanctum Delphica"}
},
{
w = 2,
type = "noctic",
severity = "red",
delta = -13,
title = "Unauthorized Noctic induction",
text = "An unauthorized Noctic induction rite causes {A} and {B} to clash over blame, consent, and ritual discipline.",
cults = {"Mystery of the Stygian Veil", "Order of the Umbral Oracle", "Reverie Nebulous", "Rex Catonis", "Temple Alabaster", "Court of the Ironclad"}
},
{
w = 2,
type = "noctic",
severity = "red",
delta = -14,
title = "Counterfeit nocticine tinctures",
text = "Counterfeit nocticine tinctures force {A} and {B} into open dispute over cargo custody, forged permissions, and shrine-market inspection.",
cults = {"Guild of Golden Shadows", "Temple Alabaster", "Rex Catonis", "Sanctum Vitalis", "Court of the Ironclad", "Azure Sentinel Sect"}
},
{
w = 3,
type = "noctic",
severity = "yellow",
delta = -6,
title = "Raw-leaf custody audit",
text = "{A} accuses {B} of mishandling raw Noctic-Rabrev custody records during a restricted leaf audit.",
cults = {"Guild of Golden Shadows", "Rex Catonis", "Temple Alabaster", "Sanctum Delphica", "Sanctum Vitalis", "Ordo Solis Invicti"}
},
{
w = 2,
type = "noctic",
severity = "green",
delta = 6,
title = "Blood-substitute stabilization",
text = "{A} and {B} coordinate milk, blood-substitute, and sleep-stabilization support for affected households.",
cults = {"Sanctum Vitalis", "Temple Alabaster", "Ordo Amicitia", "Order of the Umbral Oracle", "Mystery of the Stygian Veil"}
},
{
w = 2,
type = "noctic",
severity = "yellow",
delta = -6,
title = "Dream-contagion rumor",
text = "A dream-contagion rumor causes tension between {A} and {B}, as both cults argue over whether visions should be treated as prophecy, panic, or illness.",
cults = {"Reverie Nebulous", "Order of the Umbral Oracle", "Conclave Illuminara", "Sanctum Delphica", "Temple Illuminata", "Sanctum Vitalis"}
},
{
w = 2,
type = "noctic",
severity = "green",
delta = 5,
title = "Protected Noctic household escort",
text = "{A} and {B} jointly protect registered Noctic households from retaliation during an outbreak scare.",
cults = {"Temple Alabaster", "Court of the Ironclad", "Ordo Amicitia", "Harmony Sanctum", "Sanctum Vitalis"}
},
{
w = 2,
type = "noctic",
severity = "yellow",
delta = -5,
title = "Noctic stigma hearing",
text = "{A} and {B} dispute whether public warnings against Noctic Vampirism have crossed into unlawful stigma against registered afflicted persons.",
cults = {"Temple Alabaster", "Harmony Sanctum", "Rex Catonis", "Ordo Amicitia", "Court of the Ironclad", "Sanctum Vitalis"}
},
{
w = 2,
type = "noctic",
severity = "red",
delta = -12,
title = "Predatory blood-rite accusation",
text = "{A} accuses {B} of failing to prevent a predatory blood rite linked to uncontrolled Noctic hunger.",
cults = {"Mystery of the Stygian Veil", "Order of the Umbral Oracle", "Rex Catonis", "Court of the Ironclad", "Temple Alabaster", "Reverie Nebulous"}
}
},
{
w = 3,
type = "cult-specific",
severity = "red",
delta = -18,
title = "Privateer schism",
actor = "Guild of Golden Shadows",
targets = {"Azure Sentinel Sect", "Rex Catonis", "Harmony Sanctum", "Court of the Ironclad"},
text = "{A} is accused of protecting rogue privateers, shrine-smugglers, and oath-brokers operating beyond General Port discipline, provoking {B}."
},
{
w = 3,
type = "cult-specific",
severity = "red",
delta = -20,
title = "Azure knife mandate",
actor = "Azure Sentinel Sect",
targets = {"Guild of Golden Shadows", "Harmony Sanctum", "Eon Fellowship", "Rex Catonis"},
text = "{A} is blamed for a covert assassination mandate against a protected intermediary, forcing {B} to demand inquiry and public penance."
},
{
w = 3,
type = "cult-specific",
severity = "red",
delta = -18,
title = "Ironclad reprisal column",
actor = "Court of the Ironclad",
targets = {"Reverie Nebulous", "Harmony Sanctum", "Ignis Aeternum", "Mystery of the Verdant Embrace"},
text = "{A} deploys a reprisal column after shrine unrest, and {B} denounces the action as punishment disguised as protection."
},
{
w = 3,
type = "cult-specific",
severity = "red",
delta = -19,
title = "Fire-purification riot",
actor = "Ignis Aeternum",
targets = {"Harmony Sanctum", "Mystery of the Verdant Embrace", "Temple Alabaster", "Order of Aurora Mystica"},
text = "{A} is accused of inspiring fire-purification mobs whose rites damage homes, gardens, and shrine archives protected by {B}."
},
{
w = 3,
type = "cult-specific",
severity = "red",
delta = -17,
title = "Dream court panic",
actor = "Reverie Nebulous",
targets = {"Court of the Ironclad", "Rex Catonis", "Sanctum Delphica", "Sanctum Vitalis"},
text = "{A} spreads dream-court testimony that {B} considers unverified, contagious, and dangerous to public order."
},
{
w = 3,
type = "cult-specific",
severity = "yellow",
delta = -12,
title = "Balance injunction",
actor = "Harmony Sanctum",
targets = {"Ignis Aeternum", "Azure Sentinel Sect", "Court of the Ironclad", "Sylvan Fellowship"},
text = "{A} issues a balance injunction limiting militant rites, and {B} answers that restraint has become a weapon."
},
{
w = 3,
type = "cult-specific",
severity = "yellow",
delta = -11,
title = "Dawn silence breach",
actor = "Order of Aurora Mystica",
targets = {"Celestial Harmony Sect", "Ignis Aeternum", "Reverie Nebulous", "Order of the Umbral Oracle"},
text = "{A} accuses {B} of breaking first-light silence during a sensitive rite of renewal."
},
{
w = 3,
type = "cult-specific",
severity = "yellow",
delta = -11,
title = "Duality proclamation",
actor = "Celestial Harmony Sect",
targets = {"Order of Aurora Mystica", "Conclave Illuminara", "Rex Catonis", "Ordo Solis Invicti"},
text = "{A} issues a duality proclamation that {B} interprets as deliberate confusion of law, calendar, and oath."
},
{
w = 3,
type = "cult-specific",
severity = "yellow",
delta = -10,
title = "Winter moratorium",
actor = "Eon Fellowship",
targets = {"Ignis Aeternum", "Azure Sentinel Sect", "Sylvan Fellowship", "Reverie Nebulous"},
text = "{A} announces a winter moratorium on risky rites, while {B} condemns it as an attempt to freeze renewal itself."
},
{
w = 3,
type = "cult-specific",
severity = "yellow",
delta = -10,
title = "Umbral sealed testimony",
actor = "Order of the Umbral Oracle",
targets = {"Rex Catonis", "Sanctum Delphica", "Conclave Illuminara", "Reverie Nebulous"},
text = "{A} withholds sealed testimony from public review, and {B} warns that hidden truth can become hidden guilt."
},
{
w = 3,
type = "cult-specific",
severity = "yellow",
delta = -10,
title = "Verdant road closure",
actor = "Mystery of the Verdant Embrace",
targets = {"Ignis Aeternum", "Court of the Ironclad", "Guild of Golden Shadows", "Rex Catonis"},
text = "{A} closes a sacred grove-road to protect harvest rites, angering {B} over trade, patrol, and emergency access."
},
{
w = 3,
type = "cult-specific",
severity = "yellow",
delta = -10,
title = "Eclipse custody dispute",
actor = "Conclave Illuminara",
targets = {"Celestial Harmony Sect", "Order of the Umbral Oracle", "Temple Illuminata", "Ordo Solis Invicti"},
text = "{A} asserts custody over eclipse readings, while {B} contests the authority to declare what the darkness revealed."
},
{
w = 3,
type = "cult-specific",
severity = "green",
delta = 12,
title = "Vitalis mercy corridor",
actor = "Sanctum Vitalis",
targets = {"Temple Alabaster", "Ordo Amicitia", "Mystery of the Verdant Embrace", "Harmony Sanctum"},
text = "{A} opens a mercy corridor for the sick and displaced, improving cooperation with {B}."
},
{
w = 3,
type = "cult-specific",
severity = "green",
delta = 10,
title = "Alabaster sanctuary ruling",
actor = "Temple Alabaster",
targets = {"Sanctum Vitalis", "Ordo Amicitia", "Court of the Ironclad", "Rex Catonis"},
text = "{A} grants sanctuary under strict purity rules, allowing {B} to accept a difficult compromise without losing face."
},
{
w = 3,
type = "cult-specific",
severity = "green",
delta = 10,
title = "Concordian oath table",
actor = "Accord Concordia",
targets = {"Harmony Sanctum", "Sanctum Delphica", "Ordo Amicitia", "Celestial Harmony Sect"},
text = "{A} convenes an oath table where {B} agrees to temporary restraint and shared public wording."
},
{
w = 3,
type = "cult-specific",
severity = "red",
delta = -16,
title = "Stygian forbidden passage",
actor = "Mystery of the Stygian Veil",
targets = {"Ordo Amicitia", "Temple Alabaster", "Rex Catonis", "Sanctum Vitalis"},
text = "{A} is accused of opening a forbidden passage between mourning, rebirth, and Noctic blood-memory rites, alarming {B}."
},
{
w = 3,
type = "cult-specific",
severity = "yellow",
delta = -11,
title = "Sylvan beast-oath",
actor = "Sylvan Fellowship",
targets = {"Eon Fellowship", "Harmony Sanctum", "Rex Catonis", "Court of the Ironclad"},
text = "{A} recognizes a wild beast-oath that {B} refuses to treat as lawful testimony."
},
{
w = 3,
type = "cult-specific",
severity = "yellow",
delta = -9,
title = "Red Mirth excess",
actor = "Mystery of Red Mirth",
targets = {"Rex Catonis", "Temple Alabaster", "Ordo Solis Invicti", "Sanctum Vitalis"},
text = "{A} is blamed for festival excess that leaves {B} to repair injuries, ledgers, and public discipline."
},
{
w = 3,
type = "cult-specific",
severity = "yellow",
delta = -10,
title = "Catonian tribunal summons",
actor = "Rex Catonis",
targets = {"Celestial Harmony Sect", "Guild of Golden Shadows", "Ignis Aeternum", "Mystery of Red Mirth"},
text = "{A} summons witnesses to a tribunal, and {B} protests that judgment has arrived before truth."
},
{
w = 3,
type = "cult-specific",
severity = "green",
delta = 9,
title = "Delphic archive concord",
actor = "Sanctum Delphica",
targets = {"Accord Concordia", "Order of the Umbral Oracle", "Guild of Golden Shadows", "Reverie Nebulous"},
text = "{A} reconciles contradictory archive entries, allowing {B} to withdraw an accusation without humiliation."
},
{
w = 3,
type = "cult-specific",
severity = "green",
delta = 11,
title = "Amicitian ransom of mercy",
actor = "Ordo Amicitia",
targets = {"Temple Alabaster", "Sanctum Vitalis", "Accord Concordia", "Mystery of Red Mirth"},
text = "{A} pays a ransom of mercy for detained families, and {B} agrees to treat the act as charity rather than interference."
},
{
w = 3,
type = "cult-specific",
severity = "yellow",
delta = -9,
title = "Illuminata impossible omen",
actor = "Temple Illuminata",
targets = {"Conclave Illuminara", "Order of the Umbral Oracle", "Eon Fellowship", "Sanctum Delphica"},
text = "{A} announces an impossible omen, and {B} demands proof before the claim can alter ritual calendars."
},
{
w = 3,
type = "cult-specific",
severity = "green",
delta = 9,
title = "Solar measure correction",
actor = "Ordo Solis Invicti",
targets = {"Rex Catonis", "Conclave Illuminara", "Order of Aurora Mystica", "Harmony Sanctum"},
text = "{A} corrects a public measure, calendar mark, or oath-window, allowing {B} to resolve a dispute without bloodshed."
},
{
w = 1,
type = "mythic",
severity = "red",
delta = -28,
title = "Covenant of the broken harbor",
actor = "Azure Sentinel Sect",
targets = {"Guild of Golden Shadows", "Harmony Sanctum", "Rex Catonis"},
text = "A harbor oath collapses after an assassination, and {A} is accused by {B} of turning sacred protection into a blade pointed inward."
},
{
w = 1,
type = "mythic",
severity = "red",
delta = -50,
title = "Ashen procession of Pyros",
actor = "Ignis Aeternum",
targets = {"Mystery of the Verdant Embrace", "Harmony Sanctum", "Temple Alabaster"},
text = "A procession of ash and flame crosses a protected district; {A} calls it renewal, while {B} calls it the opening act of war."
},
{
w = 1,
type = "mythic",
severity = "green",
delta = 25,
title = "Miracle of the divided lamp",
actor = "Order of Aurora Mystica",
targets = {"Conclave Illuminara", "Harmony Sanctum", "Ordo Solis Invicti"},
text = "A lamp burns in two colors at first light, and {A} accepts {B} as co-witness to a new festival of reconciliation."
},
{
w = 1,
type = "mythic",
severity = "green",
delta = 24,
title = "Festival of the unbroken cup",
actor = "Mystery of Red Mirth",
targets = {"Rex Catonis", "Ordo Amicitia", "Accord Concordia"},
text = "A cup dropped before the crowd does not break, and {A} invites {B} to establish a new festival of restrained joy."
},
{
w = 1,
type = "mythic",
severity = "red",
delta = -26,
title = "Dream of the drowned crown",
actor = "Reverie Nebulous",
targets = {"Court of the Ironclad", "Azure Sentinel Sect", "Sanctum Delphica"},
text = "A shared dream of a drowned crown spreads through the night, and {A} is accused by {B} of weaponizing prophecy."
},
{
w = 1,
type = "mythic",
severity = "green",
delta = 22,
title = "Mercy under the white veil",
actor = "Temple Alabaster",
targets = {"Sanctum Vitalis", "Ordo Amicitia", "Mystery of the Stygian Veil"},
text = "A dying witness survives beneath a white veil, and {A} permits {B} to declare a rare mercy rite."
},
{
w = 1,
type = "mythic",
severity = "red",
delta = -25,
title = "The wolf at the sealed gate",
actor = "Court of the Ironclad",
targets = {"Reverie Nebulous", "Harmony Sanctum", "Mystery of the Verdant Embrace"},
text = "A gate sealed by civic oath is broken in the name of protection, and {A} faces accusations from {B} that defense has become conquest."
},
{
{
w = 1,
type = "mythic",
severity = "green",
delta = 23,
title = "The concord of nine witnesses",
actor = "Accord Concordia",
targets = {"Harmony Sanctum", "Sanctum Delphica", "Ordo Amicitia"},
text = "Nine witnesses survive a failed riot without contradiction, and {A} persuades {B} to recognize a new compact of public testimony."
}
}
local cultEventPacks = {
["Ordo Solis Invicti"] = {
targets = {"Rex Catonis", "Conclave Illuminara", "Order of Aurora Mystica", "Harmony Sanctum", "Celestial Harmony Sect", "Guild of Golden Shadows"},
events = {
-- Positive
{"Solar writ correction", "green", 9, "measure", "{A} corrects a disputed oath-window, allowing {B} to withdraw a challenge without public disgrace."},
{"Nine Lamps audit", "green", 8, "audit", "{A} completes a Nine Lamps audit of shrine instruments and grants {B} renewed standing in public registries."},
{"Daylight amnesty decree", "green", 12, "reconciliation", "{A} proclaims a daylight amnesty for lesser ritual offenses, giving {B} a path to reconciliation."},
{"Meridian oath compact", "green", 10, "law", "{A} and {B} jointly certify vows spoken at true noon, preventing a ledger dispute from becoming a schism."},
{"Sun-seed instrument grant", "green", 9, "craft", "{A} grants calibrated Sun-Seed instruments to artisans supervised by {B}."},
{"Public measure concord", "green", 8, "governance", "{A} helps {B} standardize weights, volumes, bells, and ritual schedules for public use."},
-- Negative
{"Meridian court rebuke", "yellow", -10, "law", "{A} rebukes {B} for filing testimony outside the proper meridian hour."},
{"Sun-seed seizure", "yellow", -12, "custody", "{A} seizes unauthorized Sun-Seeds from workshops protected by {B}."},
{"False noon accusation", "red", -18, "doctrine", "{A} accuses {B} of falsifying a noon-vow, threatening the legitimacy of contracts sealed that day."},
{"Broken measure scandal", "yellow", -11, "audit", "{A} discovers that measures used by {B} do not conform to solar-certified standards."},
{"Unlicensed dawn bell", "yellow", -9, "ritual", "{A} condemns an unlicensed bell rung by {B} before the proper solar mark."},
{"Public shadow charge", "red", -20, "crisis", "{A} declares that {B} has hidden a public matter from daylight adjudication."}
}
},
["Order of Aurora Mystica"] = {
targets = {"Conclave Illuminara", "Harmony Sanctum", "Eon Fellowship", "Celestial Harmony Sect", "Ignis Aeternum", "Reverie Nebulous"},
events = {
-- Positive
{"Dawn mirror concord", "green", 10, "ritual", "{A} shares mirror-library calculations with {B}, easing conflict over first-light rites."},
{"Aurora witness miracle", "green", 16, "miracle", "{A} reports a pale dawn radiance over the altar and names {B} as co-witness."},
{"New beginning compact", "green", 11, "mediation", "{A} opens a compact of new beginnings for families displaced by cult unrest, with {B} providing escort or relief."},
{"First-light reconciliation", "green", 10, "reconciliation", "{A} invites {B} to stand in silence at first light before reopening negotiations."},
{"Mirror-library exchange", "green", 9, "archive", "{A} grants {B} access to dawn records that clarify a disputed rite."},
{"Renewal procession accord", "green", 8, "festival", "{A} and {B} agree to share a renewal procession without competing chants."},
-- Negative
{"First-light silence breach", "yellow", -11, "ritual", "{A} accuses {B} of breaking sacred silence before the Dawn Key is kindled."},
{"Renewal procession dispute", "yellow", -9, "festival", "{A} alters a renewal procession route, angering {B} over precedence and shrine access."},
{"Broken dawn accusation", "red", -20, "doctrine", "{A} declares that {B} has turned renewal into disorder and demands public repentance."},
{"Mirror defacement charge", "red", -18, "sacrilege", "{A} accuses devotees linked to {B} of defacing first-light mirrors."},
{"False renewal warning", "yellow", -10, "doctrine", "{A} warns that {B} is using the language of renewal to conceal ambition."},
{"Dawn gate closure", "yellow", -9, "security", "{A} closes a dawn gate before pilgrims aligned with {B} can enter."}
}
},
["Harmony Sanctum"] = {
targets = {"Ignis Aeternum", "Azure Sentinel Sect", "Court of the Ironclad", "Mystery of the Verdant Embrace", "Accord Concordia", "Guild of Golden Shadows"},
events = {
-- Positive
{"Equilibrium hearing", "green", 10, "law", "{A} convenes an equilibrium hearing where {B} agrees to temporary restraint."},
{"Mutual order pact", "green", 12, "governance", "{A} drafts a mutual order pact that allows {B} to preserve honor while reducing street tension."},
{"Harmony tribunal reversal", "green", 9, "reconciliation", "{A} reverses a harsh local ruling after petitions from {B}, improving relations through mercy."},
{"Serene bell compact", "green", 8, "mediation", "{A} and {B} agree to answer the Serene Bell before mobilizing followers."},
{"Balanced procession route", "green", 9, "festival", "{A} redraws a procession route so {B} can participate without appearing subordinate."},
{"Mutual grievance table", "green", 10, "diplomacy", "{A} opens a grievance table where {B} may submit complaints without public humiliation."},
-- Negative
{"Balance injunction", "yellow", -12, "mediation", "{A} issues a balance injunction against extreme rites, and {B} rejects it as interference."},
{"Serene bell warning", "yellow", -8, "warning", "{A} rings the Serene Bell after rumors of violence by followers of {B}."},
{"Broken scales riot", "red", -22, "violence", "{A} blames {B} for a riot in which balance-symbols are shattered and witnesses refuse to testify."},
{"Equilibrium veto", "yellow", -10, "governance", "{A} vetoes a ritual mobilization proposed by {B}."},
{"Serena mediation collapse", "red", -18, "crisis", "{A}'s mediation with {B} collapses after both sides accuse the other of bad faith."},
{"Balance oath refusal", "yellow", -9, "doctrine", "{A} refuses to recognize an oath sworn by {B} as balanced or complete."}
}
},
["Ignis Aeternum"] = {
targets = {"Harmony Sanctum", "Mystery of the Verdant Embrace", "Temple Alabaster", "Order of Aurora Mystica", "Eon Fellowship", "Court of the Ironclad"},
events = {
-- Positive
{"Ash vow renewal", "green", 8, "ritual", "{A} performs ash-vows of restraint before witnesses from {B}."},
{"Pyric artisan compact", "green", 9, "trade", "{A} and {B} certify fire-tempered ritual tools under shared safety rules."},
{"Controlled flame rite", "green", 10, "ritual", "{A} agrees to conduct flame rites only under safety terms accepted by {B}."},
{"Forge blessing accord", "green", 8, "craft", "{A} blesses forge-work needed by {B} for repairs, tools, or shrine restoration."},
{"Ashes of renewal relief", "green", 9, "relief", "{A} redirects renewal offerings toward families aided by {B}."},
{"Festival brazier compact", "green", 8, "festival", "{A} and {B} jointly supervise public braziers during a volatile festival."},
-- Negative
{"Fire-purification riot", "red", -20, "violence", "{A} is accused of inspiring fire-purification mobs whose rites damage homes, gardens, and shrine archives protected by {B}."},
{"Burned grove charge", "red", -24, "ecology", "{A} is blamed after sacred plantings under {B}'s protection are destroyed by ritual flame."},
{"Coal-bright sermon", "yellow", -10, "doctrine", "{A} preaches that destruction is mercy, prompting {B} to warn of doctrinal danger."},
{"Ashen procession", "red", -28, "mythic", "{A} leads an ash-covered procession through a protected district; {B} calls it the beginning of war."},
{"Unauthorized pyre", "red", -22, "sacrilege", "{A} lights an unauthorized pyre near a precinct protected by {B}."},
{"Flame oath ultimatum", "yellow", -12, "ultimatum", "{A} demands that {B} accept purification or be named an enemy of renewal."}
}
},
["Celestial Harmony Sect"] = {
targets = {"Order of Aurora Mystica", "Conclave Illuminara", "Rex Catonis", "Accord Concordia", "Sanctum Delphica", "Ordo Solis Invicti"},
events = {
-- Positive
{"Twin-song mediation", "green", 10, "mediation", "{A} performs a twin-song mediation that allows {B} to accept ambiguity without surrendering doctrine."},
{"Changing-face delegation", "green", 8, "diplomacy", "{A} sends a changing-face delegation to negotiate with {B} in neutral language."},
{"Autumn mirror compact", "green", 9, "festival", "{A} and {B} jointly recognize a seasonal mirror-rite for peaceful transitions."},
{"Dual witness accord", "green", 8, "law", "{A} provides paired witnesses whose testimony satisfies {B}'s demand for balance and clarity."},
{"Harmony-of-opposites rite", "green", 10, "ritual", "{A} invites {B} to witness a rite reconciling opposing claims without erasing either side."},
{"Symphonara chord settlement", "green", 9, "mediation", "{A} proposes a chord settlement in which {B} accepts layered meanings as a temporary peace."},
-- Negative
{"Duality proclamation", "yellow", -11, "doctrine", "{A} issues a duality proclamation that {B} reads as an attack on clear law and fixed ritual meaning."},
{"Masked calendar objection", "yellow", -10, "calendar", "{A} questions whether a ritual date should be read as one sign or two, angering {B}."},
{"Schism of two lamps", "red", -18, "doctrine", "{A} splits a public rite into paired observances, and {B} accuses it of dividing the faithful."},
{"Ambiguous oath scandal", "yellow", -10, "law", "{A} defends an ambiguous oath that {B} says cannot bind anyone."},
{"Twin-mask sedition charge", "red", -20, "public order", "{A} is accused of hiding sedition beneath ritualized ambiguity, alarming {B}."},
{"Contradictory omen decree", "yellow", -11, "omen", "{A} declares two incompatible omens simultaneously valid, provoking {B}."}
}
},
["Guild of Golden Shadows"] = {
targets = {"Azure Sentinel Sect", "Rex Catonis", "Harmony Sanctum", "Court of the Ironclad", "Sanctum Delphica", "Ordo Amicitia"},
events = {
-- Positive
{"Ransom ledger bargain", "green", 10, "trade", "{A} negotiates the release of detained merchants through a ransom ledger accepted by {B}."},
{"Harbor wind compact", "green", 9, "commerce", "{A} negotiates safe-passage winds, docking priority, and fair-market terms with {B}."},
{"Privateer amnesty register", "green", 11, "privateering", "{A} persuades several privateer crews to enter a lawful registry recognized by {B}."},
{"Golden convoy insurance", "green", 8, "trade", "{A} funds convoy insurance that allows merchants aligned with {B} to resume travel."},
{"Shadow broker exchange", "green", 9, "intelligence", "{A} trades useful harbor intelligence to {B}, preventing an ambush or riot."},
{"General Port oath renewal", "green", 10, "commerce", "{A} renews General Port oaths in front of {B}, stabilizing shrine-market operations."},
-- Negative
{"Privateer schism", "red", -22, "privateering", "{A} is accused of protecting rogue privateers, shrine-smugglers, and oath-brokers beyond General Port discipline, provoking {B}."},
{"Golden letter of marque", "yellow", -12, "privateering", "{A} issues a disputed privateering commission that {B} refuses to recognize as lawful."},
{"False shrine cargo", "red", -20, "smuggling", "{A} is accused of moving counterfeit relics through protected cargo channels watched by {B}."},
{"Atämiosian raider blessing", "red", -24, "piracy", "{A} blesses a raider crew before dawn, and {B} calls the act piracy disguised as devotion."},
{"Oath-broker assassination rumor", "red", -21, "intrigue", "{A} is suspected of using oath-brokers to arrange the disappearance of a witness protected by {B}."},
{"Harbor tithe laundering", "yellow", -11, "corruption", "{A} is accused of laundering shrine tithes through privateer prize shares, angering {B}."}
}
},
["Azure Sentinel Sect"] = {
targets = {"Guild of Golden Shadows", "Harmony Sanctum", "Eon Fellowship", "Rex Catonis", "Court of the Ironclad", "Conclave Illuminara"},
events = {
-- Positive
{"Sentinel convoy escort", "green", 10, "security", "{A} escorts a vulnerable shrine convoy, allowing {B} to resume public rites."},
{"Blue-depth amnesty", "green", 8, "reconciliation", "{A} grants amnesty to lesser harbor offenders after mediation by {B}."},
{"Sea-wall rescue compact", "green", 10, "relief", "{A} rescues pilgrims trapped by floodwater and accepts supply support from {B}."},
{"Harbor oath protection", "green", 9, "security", "{A} places a disputed witness under harbor-oath protection recognized by {B}."},
{"Sentinel anti-piracy sweep", "green", 11, "security", "{A} conducts an anti-piracy sweep that protects vessels associated with {B}."},
{"Blue Host precinct guard", "green", 8, "preservation", "{A} reinforces a protected temple precinct at the request of {B}."},
-- Negative
{"Azure knife mandate", "red", -24, "assassination", "{A} is blamed for a covert assassination mandate against a protected intermediary, forcing {B} to demand inquiry and penance."},
{"Harbor silence killing", "red", -28, "assassination", "{A} is accused after a witness is found dead beneath harbor bells, and {B} alleges temple-sanctioned murder."},
{"Drowned oath interrogation", "yellow", -12, "security", "{A} detains suspected oath-breakers in a maritime precinct, and {B} challenges the legality of the interrogation."},
{"Sea-wall mobilization", "yellow", -10, "war", "{A} mobilizes sea-wall defenders without consulting {B}, raising fears of unilateral military action."},
{"Black anchor reprisal", "red", -22, "reprisal", "{A} is accused of drowning a suspected traitor under anchor-rite authority, provoking {B}."},
{"Temple fleet ultimatum", "red", -25, "war", "{A} issues a temple-fleet ultimatum that {B} treats as the first step toward open conflict."}
}
},
["Reverie Nebulous"] = {
targets = {"Court of the Ironclad", "Rex Catonis", "Sanctum Delphica", "Sanctum Vitalis", "Order of the Umbral Oracle", "Guild of Golden Shadows"},
events = {
-- Positive
{"Cloud-veiled mercy vigil", "green", 9, "relief", "{A} shelters frightened families overnight, and {B} recognizes the vigil as stabilizing rather than subversive."},
{"Morphean art compact", "green", 8, "culture", "{A} and {B} sponsor dream-painted memorial screens to calm unrest after a crisis."},
{"Dream-healer conference", "green", 10, "medicine", "{A} hosts dream interpreters and healers from {B} to separate prophecy from illness."},
{"Nebulous witness easing", "green", 8, "justice", "{A} helps a traumatized witness speak safely before representatives of {B}."},
{"Somniumpolis lullaby rite", "green", 9, "ritual", "{A} performs a lullaby rite that reduces panic in districts watched by {B}."},
{"Cloud archive donation", "green", 8, "archive", "{A} donates dream records to {B} for supervised preservation."},
-- Negative
{"Dream court panic", "red", -18, "prophecy", "{A} spreads dream-court testimony that {B} considers unverified, contagious, and dangerous to public order."},
{"Nebulous confession trance", "yellow", -10, "oracle", "{A} accepts trance-confessions that {B} refuses to enter into formal record."},
{"Somniant rumor spiral", "red", -22, "panic", "{A} is blamed for allowing Eidolan rumors to spread through dream circles, forcing {B} into emergency response."},
{"Drowned crown vision", "red", -26, "mythic", "{A} reports a shared vision of a drowned crown, and {B} accuses it of weaponizing prophecy."},
{"Dream contagion accusation", "yellow", -11, "noctic", "{A} is accused of spreading Noctic dream-contagion stories that {B} calls medically reckless."},
{"False nightmare tribunal", "red", -20, "justice", "{A} holds a nightmare tribunal without evidence accepted by {B}."}
}
},
["Eon Fellowship"] = {
targets = {"Ignis Aeternum", "Azure Sentinel Sect", "Sylvan Fellowship", "Reverie Nebulous", "Order of Aurora Mystica", "Temple Illuminata"},
events = {
-- Positive
{"Cold burial compact", "green", 10, "mourning", "{A} agrees to supervised burial and preservation rites with {B}."},
{"Ice-road pilgrimage escort", "green", 8, "pilgrimage", "{A} opens a guarded winter pilgrimage road with support from {B}."},
{"Cycle-closing reconciliation", "green", 9, "reconciliation", "{A} declares that an old grievance has reached the end of its cycle, allowing {B} to accept peace."},
{"Winter stores release", "green", 10, "relief", "{A} releases winter stores to households assisted by {B}."},
{"Frozen archive preservation", "green", 8, "archive", "{A} preserves fragile records for review by {B}."},
{"Eon endurance compact", "green", 9, "security", "{A} and {B} agree to endure a tense season without mobilizing followers."},
-- Negative
{"Winter moratorium", "yellow", -11, "doctrine", "{A} announces a winter moratorium on dangerous rites, while {B} condemns it as an attempt to freeze renewal itself."},
{"Glacial refusal", "red", -18, "crisis", "{A} refuses emergency ritual release until the proper cycle closes, enraging {B}."},
{"Frozen witness dispute", "yellow", -10, "law", "{A} preserves a witness in winter custody, and {B} challenges whether the testimony remains valid."},
{"Boreal death omen", "red", -20, "omen", "{A} declares a death-omen over a disputed precinct, alarming {B} and halting trade."},
{"Ice-sealed gate", "yellow", -11, "security", "{A} seals a pilgrimage gate until the cycle turns, angering {B}."},
{"Cold heresy charge", "red", -21, "doctrine", "{A} condemns {B}'s rites as impatient rebellion against the cycle of death and return."}
}
},
["Order of the Umbral Oracle"] = {
targets = {"Rex Catonis", "Sanctum Delphica", "Conclave Illuminara", "Reverie Nebulous", "Ordo Amicitia", "Temple Illuminata"},
events = {
-- Positive
{"Night court confession", "green", 9, "justice", "{A} receives a night-court confession that helps {B} close a dangerous dispute."},
{"Moonless witness escort", "green", 8, "security", "{A} escorts a threatened witness through moonless streets under protection from {B}."},
{"Umbral truth compact", "green", 10, "oracle", "{A} agrees to reveal hidden testimony under conditions accepted by {B}."},
{"Shadow-play reconciliation", "green", 8, "culture", "{A} stages a shadow play that allows {B} to acknowledge grief without admitting guilt."},
{"Night ledger opening", "green", 9, "archive", "{A} opens sealed night-ledgers to auditors from {B}."},
{"Noctis vigil protection", "green", 10, "ritual", "{A} invites {B} to guard a Noctis vigil and prevent rumor violence."},
-- Negative
{"Umbral sealed testimony", "yellow", -11, "secrecy", "{A} withholds sealed testimony from public review, and {B} warns that hidden truth can become hidden guilt."},
{"Shadow play sedition", "red", -18, "public order", "{A} is accused of allowing coded sedition to spread through shadow plays, alarming {B}."},
{"Forbidden name utterance", "yellow", -10, "ritual", "{A} permits a forbidden name during oracle work, and {B} challenges the rite's legitimacy."},
{"Noctic veil inquiry", "red", -20, "noctic", "{A} is accused of concealing Noctic blood-memory testimony from medical officers aligned with {B}."},
{"Moonless arrest scandal", "yellow", -11, "security", "{A} assists a moonless arrest that {B} says should have been public."},
{"Oracle silence decree", "red", -19, "secrecy", "{A} orders silence after a vision, and {B} treats the silence as obstruction."}
}
},
["Mystery of the Verdant Embrace"] = {
targets = {"Ignis Aeternum", "Court of the Ironclad", "Guild of Golden Shadows", "Rex Catonis", "Sanctum Vitalis", "Sylvan Fellowship"},
events = {
-- Positive
{"Seed tithe compact", "green", 9, "agriculture", "{A} shares seed-tithes with communities assisted by {B}."},
{"Fertility grove arbitration", "green", 10, "mediation", "{A} invites {B} to witness grove arbitration after a harvest dispute."},
{"Festival of first fruit", "green", 12, "festival", "{A} establishes a first-fruit festival and grants {B} a formal role in the procession."},
{"Green canal restoration", "green", 9, "ecology", "{A} restores irrigation channels used by households protected by {B}."},
{"Harvest sanctuary compact", "green", 10, "relief", "{A} opens harvest sanctuary for displaced families under terms accepted by {B}."},
{"Verdant medicine exchange", "green", 8, "medicine", "{A} provides medicinal herbs to clinics or relief houses managed by {B}."},
-- Negative
{"Verdant road closure", "yellow", -10, "ecology", "{A} closes a sacred grove-road to protect harvest rites, angering {B} over trade, patrol, and emergency access."},
{"Root-bound blockade", "red", -18, "blockade", "{A} is accused of using living root-barriers to block patrols and caravans linked to {B}."},
{"Poisoned orchard accusation", "red", -21, "sabotage", "{A} accuses {B} of permitting chemical or ritual contamination of protected orchard land."},
{"Harvest tithe refusal", "yellow", -9, "trade", "{A} refuses a harvest tithe requested by {B} for public order or relief."},
{"Grove trespass punishment", "yellow", -11, "law", "{A} punishes trespassers in a sacred grove before {B} can review the case."},
{"Blighted field prophecy", "red", -19, "omen", "{A} declares a blight prophecy against lands associated with {B}."}
}
},
["Conclave Illuminara"] = {
targets = {"Celestial Harmony Sect", "Order of the Umbral Oracle", "Temple Illuminata", "Ordo Solis Invicti", "Order of Aurora Mystica", "Harmony Sanctum"},
events = {
-- Positive
{"Moon-bathed anointing compact", "green", 9, "ritual", "{A} shares moon-bathed anointing protocols with {B}, easing fears after an eclipse."},
{"Tarsican archive exchange", "green", 8, "archive", "{A} opens Tarsican eclipse records to archivists from {B}."},
{"Revealed shadow miracle", "green", 16, "miracle", "{A} identifies a shadow-sign that prevents violence, and {B} accepts the interpretation as binding."},
{"Eclipse vigil concord", "green", 10, "festival", "{A} and {B} jointly supervise a silent eclipse vigil."},
{"Illuminated calendar correction", "green", 9, "calendar", "{A} corrects a lunar schedule in a way that protects {B}'s ritual dignity."},
{"Moonlit arbitration", "green", 8, "mediation", "{A} settles a dispute by reading lunar signs in the presence of {B}."},
-- Negative
{"Eclipse custody dispute", "yellow", -10, "calendar", "{A} asserts custody over eclipse readings, while {B} contests the authority to declare what the darkness revealed."},
{"False eclipse charge", "red", -20, "doctrine", "{A} accuses {B} of falsifying eclipse omens to alter public decisions."},
{"Illumination veto", "yellow", -11, "governance", "{A} vetoes a ritual calendar proposed by {B}, citing insufficient celestial proof."},
{"Moon-shadow panic", "red", -18, "panic", "{A} is blamed for declaring a dangerous shadow-sign that panics communities aligned with {B}."},
{"Tarsican seal dispute", "yellow", -10, "archive", "{A} refuses to unseal lunar records requested by {B}."},
{"Eclipse procession ban", "red", -19, "festival", "{A} bans an eclipse procession planned by {B}, calling it spiritually unsafe."}
}
},
["Sanctum Vitalis"] = {
targets = {"Temple Alabaster", "Ordo Amicitia", "Mystery of the Verdant Embrace", "Harmony Sanctum", "Rex Catonis", "Order of the Umbral Oracle"},
events = {
-- Positive
{"Vitalis mercy corridor", "green", 12, "relief", "{A} opens a mercy corridor for the sick and displaced, improving cooperation with {B}."},
{"Noctic stabilization clinic", "green", 10, "noctic", "{A} establishes a Noctic stabilization clinic with supply and witness support from {B}."},
{"Miracle of returning breath", "green", 18, "miracle", "{A} records a patient revived after ritual silence, and {B} recognizes the case as a public blessing."},
{"Healer convoy compact", "green", 9, "relief", "{A} sends healers under escort terms accepted by {B}."},
{"Triage registry concord", "green", 8, "medicine", "{A} and {B} agree to a shared triage registry for crisis districts."},
{"Medicine garden dedication", "green", 9, "ecology", "{A} dedicates a medicine garden for households cared for by {B}."},
-- Negative
{"Triage priority dispute", "yellow", -9, "medicine", "{A} prioritizes the wounded by medical need, and {B} objects on ritual or legal grounds."},
{"Quarantine lantern order", "yellow", -10, "quarantine", "{A} raises quarantine lanterns around a district, forcing {B} to suspend public rites."},
{"Healer abduction alarm", "red", -20, "security", "{A} reports that healers have been abducted while serving households under {B}'s protection."},
{"Contaminated tonic scandal", "red", -18, "medicine", "{A} accuses suppliers linked to {B} of distributing contaminated tonics."},
{"Clinic access denial", "yellow", -10, "relief", "{A} denies clinic access to a group until {B} provides proper records."},
{"Noctic hunger panic", "red", -22, "noctic", "{A} warns of uncontrolled Noctic hunger in households watched by {B}."}
}
},
["Temple Alabaster"] = {
targets = {"Sanctum Vitalis", "Ordo Amicitia", "Court of the Ironclad", "Rex Catonis", "Mystery of the Stygian Veil", "Ignis Aeternum"},
events = {
-- Positive
{"Alabaster sanctuary ruling", "green", 10, "sanctuary", "{A} grants sanctuary under strict purity rules, allowing {B} to accept a difficult compromise without losing face."},
{"White veil reprieve", "green", 12, "mercy", "{A} places a condemned petitioner beneath the white veil, giving {B} a ritual path to clemency."},
{"Alabaster wash compact", "green", 9, "reconciliation", "{A} conducts a public washing rite that allows {B} to re-enter negotiations."},
{"Pure threshold escort", "green", 8, "security", "{A} escorts vulnerable families across a purified threshold with support from {B}."},
{"Sanctuary registry concord", "green", 9, "law", "{A} and {B} reconcile sanctuary registries after a disputed protection claim."},
{"White-candle healing rite", "green", 10, "medicine", "{A} hosts a white-candle healing rite recognized by {B} as lawful mercy."},
-- Negative
{"Purity cordon dispute", "yellow", -10, "purity", "{A} establishes a purity cordon that {B} condemns as excessive."},
{"Defiled threshold accusation", "red", -19, "sacrilege", "{A} accuses {B} of permitting a defiled person or object to cross a protected threshold."},
{"Sanctuary breach alarm", "red", -23, "security", "{A} claims that sanctuary was breached by agents associated with {B}."},
{"White veil exclusion", "yellow", -11, "mercy", "{A} refuses to extend the white veil to a petitioner supported by {B}."},
{"Purification ledger purge", "yellow", -10, "audit", "{A} purges names from a purity ledger, drawing protest from {B}."},
{"Alabaster stigma charge", "red", -18, "social order", "{A} is accused by {B} of turning purity law into public stigma."}
}
},
["Court of the Ironclad"] = {
targets = {"Reverie Nebulous", "Harmony Sanctum", "Ignis Aeternum", "Mystery of the Verdant Embrace", "Temple Alabaster", "Guild of Golden Shadows"},
events = {
-- Positive
{"Shield oath escort", "green", 9, "security", "{A} escorts vulnerable pilgrims under shield oath, earning temporary trust from {B}."},
{"Iron gate relief guard", "green", 10, "relief", "{A} guards relief convoys requested by {B}."},
{"Ferrum restraint compact", "green", 9, "governance", "{A} agrees to keep armed devotees inside designated precincts after mediation by {B}."},
{"Wolf-standard protection", "green", 8, "security", "{A} places a wolf-standard at a threatened shrine and grants {B} safe access."},
{"Ironclad prisoner transfer", "green", 10, "law", "{A} transfers detainees to a supervised process accepted by {B}."},
{"Citadel oath renewal", "green", 9, "reconciliation", "{A} renews a defensive oath in terms that {B} can publicly accept."},
-- Negative
{"Ironclad reprisal column", "red", -20, "reprisal", "{A} deploys a reprisal column after shrine unrest, and {B} denounces the action as punishment disguised as protection."},
{"War-dog intimidation charge", "yellow", -10, "security", "{A} posts war-dogs at a disputed gate, and {B} calls the move intimidation."},
{"Ferrum muster alarm", "red", -22, "war", "{A} musters armed devotees in Ferrum Citadel, convincing {B} that a sectarian campaign may be imminent."},
{"Iron tithe enforcement", "yellow", -11, "tax", "{A} enforces an iron tithe for shrine defense, angering communities aligned with {B}."},
{"Gate of the wolf omen", "red", -26, "mythic", "{A} claims a wolf-omen authorizes emergency rule, while {B} warns that defense has become conquest."},
{"Unauthorized cudgel court", "red", -19, "justice", "{A} holds a rough justice hearing that {B} refuses to recognize as lawful."}
}
},
["Accord Concordia"] = {
targets = {"Harmony Sanctum", "Sanctum Delphica", "Ordo Amicitia", "Celestial Harmony Sect", "Mystery of Red Mirth", "Rex Catonis"},
events = {
-- Positive
{"Concordian oath table", "green", 11, "mediation", "{A} convenes an oath table where {B} agrees to temporary restraint and shared public wording."},
{"Three-bell ceasefire", "green", 12, "ceasefire", "{A} brokers a three-bell ceasefire that prevents retaliatory violence by {B}."},
{"Concordia hostage exchange", "green", 14, "security", "{A} arranges a hostage or detainee exchange recognized by {B}."},
{"Unity banner procession", "green", 9, "festival", "{A} leads a unity banner procession in which {B} accepts a shared place of honor."},
{"Shared wording decree", "green", 8, "diplomacy", "{A} drafts shared wording that allows {B} to step back from a dangerous accusation."},
{"Pax candle compact", "green", 10, "reconciliation", "{A} lights Pax candles with {B}, formally ending a local feud."},
-- Negative
{"Broken-pax accusation", "yellow", -9, "diplomacy", "{A} accuses {B} of violating a peace clause hidden in a prior compact."},
{"Failed unity procession", "red", -18, "festival", "{A}'s unity procession collapses into accusations, and {B} abandons the route."},
{"Pax banner desecration", "red", -20, "sacrilege", "{A} reports the desecration of a Pax banner and blames extremists tolerated by {B}."},
{"Concordian neutrality crisis", "yellow", -10, "diplomacy", "{A} refuses to take a side, and {B} condemns neutrality as cowardice."},
{"Broken hostage pledge", "red", -22, "security", "{A} says a detainee pledge was broken by followers of {B}."},
{"Unity oath walkout", "yellow", -9, "mediation", "{A} watches {B} walk out before the final unity oath is sworn."}
}
},
["Mystery of the Stygian Veil"] = {
targets = {"Ordo Amicitia", "Temple Alabaster", "Rex Catonis", "Sanctum Vitalis", "Temple Illuminata", "Order of the Umbral Oracle"},
events = {
-- Positive
{"Black veil mourning compact", "green", 10, "mourning", "{A} gives {B} a formal role in mourning rites for families caught between death and rebirth customs."},
{"Phoenix ash miracle", "green", 18, "miracle", "{A} reports that ash from a funerary bowl formed a phoenix-sign, and {B} accepts it as a rare blessing."},
{"Threshold witness accord", "green", 9, "law", "{A} permits {B} to witness a threshold rite under strict terms."},
{"Acheron mercy vigil", "green", 10, "mourning", "{A} holds a mercy vigil for disputed dead, allowing {B} to participate without endorsing all rites."},
{"Rebirth registry correction", "green", 8, "archive", "{A} corrects rebirth records requested by {B}."},
{"Stygian hospice compact", "green", 11, "medicine", "{A} opens hospice passage for families under the care of {B}."},
-- Negative
{"Stygian forbidden passage", "red", -18, "threshold", "{A} is accused of opening a forbidden passage between mourning, rebirth, and Noctic blood-memory rites, alarming {B}."},
{"Corpse-road secrecy dispute", "yellow", -11, "secrecy", "{A} moves the dead by a hidden road, and {B} demands public accounting."},
{"Noctic rebirth scandal", "red", -22, "noctic", "{A} is accused of confusing Noctic induction with legitimate rebirth ritual, provoking {B}."},
{"Acheron threshold warning", "yellow", -12, "warning", "{A} warns that {B} has mishandled a threshold rite and endangered the passage of the dead."},
{"Unlicensed funerary descent", "red", -20, "sacrilege", "{A} conducts a descent rite without clearance accepted by {B}."},
{"Black water accusation", "yellow", -10, "ritual", "{A} is accused by {B} of using black water from an unverified funerary source."}
}
},
["Sylvan Fellowship"] = {
targets = {"Eon Fellowship", "Harmony Sanctum", "Rex Catonis", "Court of the Ironclad", "Mystery of the Verdant Embrace", "Ignis Aeternum"},
events = {
-- Positive
{"Antler moot compact", "green", 9, "mediation", "{A} convenes an antler moot where {B} accepts natural witness under strict limits."},
{"Faunian fertility accord", "green", 12, "festival", "{A} grants {B} a place in a fertility accord after a successful birth-season rite."},
{"Wild grove protection", "green", 9, "ecology", "{A} protects a wild grove used by pilgrims associated with {B}."},
{"Beast-herder rescue", "green", 8, "relief", "{A} sends beast-herders to rescue stranded travelers under the care of {B}."},
{"Sylvan seed exchange", "green", 9, "agriculture", "{A} shares wild seed knowledge with growers or healers connected to {B}."},
{"Faunian boundary compact", "green", 10, "law", "{A} marks a wild boundary in terms that {B} agrees to enforce."},
-- Negative
{"Sylvan beast-oath", "yellow", -11, "law", "{A} recognizes a wild beast-oath that {B} refuses to treat as lawful testimony."},
{"Feral shrine raid", "red", -20, "violence", "{A} is blamed after feral devotees raid a shrine storehouse associated with {B}."},
{"Wild seed release", "yellow", -10, "ecology", "{A} releases wild seed across managed farmland, angering {B} over boundaries and crop law."},
{"Claw-mark prophecy", "red", -18, "omen", "{A} treats claw-marks as prophecy, and {B} warns that animal sign has replaced lawful testimony."},
{"Horned revel trespass", "yellow", -9, "festival", "{A}'s horned revelers trespass through a precinct managed by {B}."},
{"Beast-blood accusation", "red", -21, "sacrilege", "{A} is accused of permitting beast-blood offerings that {B} considers unlawful."}
}
},
["Mystery of Red Mirth"] = {
targets = {"Rex Catonis", "Temple Alabaster", "Ordo Solis Invicti", "Sanctum Vitalis", "Accord Concordia", "Ordo Amicitia"},
events = {
-- Positive
{"Unbroken cup miracle", "green", 18, "miracle", "{A} drops a festival cup before the crowd and it does not break, prompting {B} to accept a new restrained joy rite."},
{"Mirthful amnesty feast", "green", 10, "reconciliation", "{A} hosts an amnesty feast where {B} agrees to forgive minor public offenses."},
{"Red garland relief drive", "green", 9, "relief", "{A} turns a revel into a relief drive supervised by {B}."},
{"Festival sobriety compact", "green", 8, "festival", "{A} agrees to keep revels within limits set by {B}."},
{"Joyful witness procession", "green", 9, "culture", "{A} leads a joyful witness procession that calms communities associated with {B}."},
{"Scarlet cup concord", "green", 10, "ritual", "{A} shares the scarlet cup with {B} as a sign that pleasure and order can coexist."},
-- Negative
{"Red Mirth excess", "yellow", -9, "festival", "{A} is blamed for festival excess that leaves {B} to repair injuries, ledgers, and public discipline."},
{"Wine tithe dispute", "yellow", -10, "trade", "{A} withholds a wine tithe claimed by {B} for relief or discipline work."},
{"Masked reveler riot", "red", -19, "violence", "{A} is blamed after masked revelers overturn public benches and mock the authority of {B}."},
{"Scarlet procession ban", "yellow", -11, "public order", "{A} defies a procession limit imposed by {B}, arguing that joy cannot be licensed."},
{"Drunken oracle scandal", "red", -18, "scandal", "{A} permits an intoxicated oracle performance that {B} declares spiritually dangerous."},
{"Mirth tax refusal", "yellow", -9, "tax", "{A} refuses to pay a public-cleaning levy after a major revel, angering {B}."}
}
},
["Rex Catonis"] = {
targets = {"Celestial Harmony Sect", "Guild of Golden Shadows", "Ignis Aeternum", "Mystery of Red Mirth", "Court of the Ironclad", "Temple Alabaster"},
events = {
-- Positive
{"Discipline edict", "green", 8, "order", "{A} issues a discipline edict that {B} accepts as necessary to prevent escalation."},
{"Cato's iron docket", "green", 9, "law", "{A} opens an iron docket for old grievances, allowing {B} to submit claims without retaliation."},
{"Lawful witness escort", "green", 10, "justice", "{A} escorts witnesses safely to a hearing requested by {B}."},
{"Catonian record amnesty", "green", 8, "reconciliation", "{A} clears minor offenses from a public record after petition by {B}."},
{"Orderly festival compact", "green", 9, "festival", "{A} accepts a festival plan from {B} after strict procession rules are adopted."},
{"Custody chain concord", "green", 10, "audit", "{A} confirms a custody chain that allows {B} to avoid scandal."},
-- Negative
{"Catonian tribunal summons", "yellow", -10, "law", "{A} summons witnesses to a tribunal, and {B} protests that judgment has arrived before truth."},
{"Chain-of-custody purge", "red", -20, "audit", "{A} purges corrupted custody records and accuses {B} of tolerating forged seals."},
{"Public flogging dispute", "yellow", -12, "punishment", "{A} orders public punishment after unrest, while {B} argues the sentence will deepen sectarian anger."},
{"Judgment before dawn", "red", -22, "crisis", "{A} delivers judgment before dawn, and {B} declares the procedure spiritually defective."},
{"Iron decree overreach", "yellow", -11, "law", "{A} issues an iron decree that {B} says exceeds lawful authority."},
{"Catonis Atrium execution alarm", "red", -24, "punishment", "{A} approves executions after a tribunal that {B} considers rushed or politically shaped."}
}
},
["Sanctum Delphica"] = {
targets = {"Accord Concordia", "Order of the Umbral Oracle", "Guild of Golden Shadows", "Reverie Nebulous", "Celestial Harmony Sect", "Conclave Illuminara"},
events = {
-- Positive
{"Delphic archive concord", "green", 9, "archive", "{A} reconciles contradictory archive entries, allowing {B} to withdraw an accusation without humiliation."},
{"Introspective fast compact", "green", 8, "ritual", "{A} and {B} agree to an introspective fast before reopening negotiations."},
{"Delphic glass miracle", "green", 16, "miracle", "{A} reports that cracked glass rejoined during testimony, and {B} accepts the restored statement as binding."},
{"Reflection chamber review", "green", 9, "justice", "{A} invites {B} to review testimony in a reflection chamber under neutral rules."},
{"Archive custody settlement", "green", 10, "archive", "{A} settles a custody dispute over records needed by {B}."},
{"Delphic quieting rite", "green", 8, "mediation", "{A} performs a quieting rite that allows {B} to step back from accusation."},
-- Negative
{"Mirror testimony dispute", "yellow", -9, "testimony", "{A} accepts reflected testimony that {B} considers too indirect for judgment."},
{"Archive theft accusation", "red", -20, "theft", "{A} accuses agents linked to {B} of stealing sealed archive leaves."},
{"False reflection scandal", "yellow", -11, "scandal", "{A} reveals that a public confession was copied from an older reflection record, embarrassing {B}."},
{"Delphic seal refusal", "yellow", -10, "archive", "{A} refuses to break a seal requested by {B}."},
{"Reflection heresy inquiry", "red", -18, "doctrine", "{A} opens an inquiry into reflective rites practiced by {B}."},
{"Contradictory witness crisis", "red", -19, "justice", "{A} publishes contradictions in witness testimony, provoking outrage from {B}."}
}
},
["Ordo Amicitia"] = {
targets = {"Temple Alabaster", "Sanctum Vitalis", "Accord Concordia", "Mystery of Red Mirth", "Mystery of the Stygian Veil", "Rex Catonis"},
events = {
-- Positive
{"Amicitian ransom of mercy", "green", 11, "charity", "{A} pays a ransom of mercy for detained families, and {B} agrees to treat the act as charity rather than interference."},
{"Mercy riot shelter", "green", 10, "relief", "{A} shelters riot victims and convinces {B} to pause retaliatory measures."},
{"Bread-and-oath procession", "green", 9, "festival", "{A} leads a bread-and-oath procession accepted by {B} as a sign of restored civic trust."},
{"Charity convoy compact", "green", 10, "relief", "{A} sends charity convoys into a tense district under guarantees from {B}."},
{"Mercy ledger reconciliation", "green", 8, "audit", "{A} reconciles charity ledgers with auditors from {B}."},
{"Forgiveness table", "green", 9, "reconciliation", "{A} opens a forgiveness table where {B} may seek pardon without dishonor."},
-- Negative
{"Charity ledger dispute", "yellow", -9, "ledger", "{A} redirects charity ledgers toward emergency relief, and {B} objects to the lack of prior approval."},
{"Forbidden clemency petition", "red", -18, "law", "{A} petitions for clemency in a case that {B} considers unforgivable."},
{"Martyr of charity rumor", "yellow", -10, "rumor", "{A} spreads a martyr-charity account that {B} believes will inflame public emotion."},
{"Mercy obstruction charge", "yellow", -9, "relief", "{A} accuses {B} of obstructing aid to families marked by scandal."},
{"Unregistered orphan shelter", "red", -18, "custody", "{A} shelters orphans without registry papers demanded by {B}."},
{"Charity-as-subversion warning", "red", -20, "politics", "{A} is accused by {B} of using mercy networks to undermine lawful punishment."}
}
},
["Temple Illuminata"] = {
targets = {"Conclave Illuminara", "Order of the Umbral Oracle", "Eon Fellowship", "Sanctum Delphica", "Mystery of the Stygian Veil", "Celestial Harmony Sect"},
events = {
-- Positive
{"Epiphany witness compact", "green", 10, "oracle", "{A} permits {B} to witness an epiphany rite under strict silence."},
{"Star-flame revelation", "green", 16, "miracle", "{A} records a star-flame revelation that {B} accepts as a warning rather than a threat."},
{"Illuminated dream concord", "green", 9, "oracle", "{A} and {B} compare dream and epiphany records to calm public anxiety."},
{"Aureum insight exchange", "green", 8, "archive", "{A} shares epiphany notes with scholars from {B}."},
{"Nikolaj sleep blessing", "green", 10, "medicine", "{A} conducts a sleep blessing for troubled households under the care of {B}."},
{"Cosmic witness circle", "green", 9, "ritual", "{A} allows {B} into a cosmic witness circle to verify an omen."},
-- Negative
{"Illuminata impossible omen", "yellow", -9, "omen", "{A} announces an impossible omen, and {B} demands proof before the claim can alter ritual calendars."},
{"Cosmic sleep decree", "yellow", -10, "doctrine", "{A} declares a sleep-decree after troubling visions, forcing {B} to suspend routine operations."},
{"Hypnos chamber scandal", "red", -19, "scandal", "{A} is accused of inducing visions in a sealed Hypnos chamber without consent recognized by {B}."},
{"Aureum epiphany schism", "red", -22, "schism", "{A} proclaims a new epiphany lineage, and {B} warns that it may divide the faithful."},
{"Star-sign forgery charge", "yellow", -11, "archive", "{A} accuses {B} of copying or altering star-sign records."},
{"Unwaking petitioner crisis", "red", -21, "medicine", "{A} is blamed after a petitioner fails to wake from a sanctioned vision sleep, provoking {B}."}
}
}
}
local festivalPressures = {
Atosien = {
positive = {"Ordo Solis Invicti", "Conclave Illuminara", "Rex Catonis"},
negative = {}
},
Eosena = {
positive = {"Order of Aurora Mystica", "Conclave Illuminara", "Accord Concordia"},
negative = {"Celestial Harmony Sect"}
},
Micrasha = {
positive = {"Harmony Sanctum", "Mystery of the Verdant Embrace", "Accord Concordia"},
negative = {"Ignis Aeternum"}
},
Pyreska = {
positive = {"Ignis Aeternum", "Azure Sentinel Sect", "Sylvan Fellowship"},
negative = {"Harmony Sanctum", "Mystery of the Verdant Embrace"}
},
Indomin = {
positive = {"Celestial Harmony Sect", "Sanctum Delphica", "Accord Concordia"},
negative = {"Order of Aurora Mystica", "Conclave Illuminara", "Rex Catonis"}
},
Chrysen = {
positive = {"Guild of Golden Shadows", "Reverie Nebulous", "Sanctum Delphica"},
negative = {"Azure Sentinel Sect"}
},
Thalassian = {
positive = {"Azure Sentinel Sect", "Ignis Aeternum", "Reverie Nebulous"},
negative = {"Eon Fellowship", "Harmony Sanctum"}
},
Nephelia = {
positive = {"Reverie Nebulous", "Guild of Golden Shadows", "Sanctum Delphica"},
negative = {"Ignis Aeternum", "Court of the Ironclad"}
},
Glinaeus = {
positive = {"Eon Fellowship", "Court of the Ironclad", "Order of Aurora Mystica"},
negative = {"Ignis Aeternum", "Azure Sentinel Sect"}
},
Noctien = {
positive = {"Order of the Umbral Oracle", "Ordo Amicitia", "Sanctum Delphica"},
negative = {}
},
Opsithia = {
positive = {"Mystery of the Verdant Embrace", "Sanctum Vitalis", "Sylvan Fellowship"},
negative = {"Ignis Aeternum"}
},
Stygian = {
positive = {"Mystery of the Stygian Veil", "Temple Illuminata", "Order of the Umbral Oracle"},
negative = {"Ordo Amicitia"}
},
Faunian = {
positive = {"Sylvan Fellowship", "Mystery of the Verdant Embrace", "Sanctum Vitalis"},
negative = {"Eon Fellowship"}
},
Silenian = {
positive = {"Mystery of Red Mirth", "Rex Catonis", "Accord Concordia"},
negative = {}
},
Catosien = {
positive = {"Rex Catonis", "Ordo Solis Invicti", "Temple Alabaster"},
negative = {"Celestial Harmony Sect"}
}
}
local function pickTwoDistinct(nextInt, list)
if not list or #list < 2 then
return nil, nil
end
local a = pick(nextInt, list)
local b = pick(nextInt, list)
local tries = 0
while a == b and tries < 20 do
b = pick(nextInt, list)
tries = tries + 1
end
if a == b then
return list[1], list[2]
end
return a, b
end
local function makeEventText(template, a, b)
local s = tostring(template or "")
s = s:gsub("{A}", cultLink(a))
s = s:gsub("{B}", cultLink(b))
return s
end
local function generateDailyEvents(dateInfo, args)
args = args or {}
local count = tonumber(args.events or args.eventcount or args.relationEvents) or 10
count = clamp(count, 1, 20)
local seed = hash31("CultRelationsDailyBVR:" .. tostring(dateInfo.year) .. ":" .. tostring(dateInfo.dayOfYear))
local nextInt = lcg(seed)
local events = {}
local used = {}
local tries = 0
while #events < count and tries < 100 do
tries = tries + 1
local e = pickWeighted(nextInt, eventPool)
local a, b
if e.actor then
a = normalizeCultName(e.actor)
local targetList = e.targets or e.cults or {}
b = normalizeCultName(pick(nextInt, targetList))
local tries2 = 0
while (not b or b == "" or b == a) and tries2 < 20 do
b = normalizeCultName(pick(nextInt, targetList))
tries2 = tries2 + 1
end
else
a, b = pickTwoDistinct(nextInt, e.cults)
a, b = normalizeCultName(a), normalizeCultName(b)
end
if a and b and a ~= "" and b ~= "" and a ~= b then
local key = sortedPair(a, b) .. "::" .. tostring(e.title)
if not used[key] then
used[key] = true
table.insert(events, {
title = e.title,
type = e.type,
severity = e.severity,
a = a,
b = b,
delta = tonumber(e.delta) or 0,
text = makeEventText(e.text, a, b)
})
end
end
end
-- Zodiac-season pressure. This makes the current zodiac period matter.
local fp = festivalPressures[dateInfo.zodiac]
if fp then
local pa, pb = pickTwoDistinct(nextInt, fp.positive)
if pa and pb then
table.insert(events, {
title = dateInfo.zodiac .. " ritual alignment",
type = "zodiac",
severity = "green",
a = pa,
b = pb,
delta = 5,
text = cultLink(pa) .. " and " .. cultLink(pb) .. " benefit from shared " .. dateInfo.zodiac .. " ritual emphasis."
})
end
if fp.negative and #fp.negative > 0 and fp.positive and #fp.positive > 0 then
local na = pick(nextInt, fp.negative)
local nb = pick(nextInt, fp.positive)
if na and nb and na ~= nb then
table.insert(events, {
title = dateInfo.zodiac .. " doctrinal strain",
type = "zodiac",
severity = "yellow",
a = na,
b = nb,
delta = -4,
text = cultLink(na) .. " experiences doctrinal strain with " .. cultLink(nb) .. " during the " .. dateInfo.zodiac .. " period."
})
end
end
end
-- Noctic Vampirism pressure.
-- These recurring pressures reflect raw-leaf custody, registered afflicted households,
-- unauthorized induction, dream panic, and medical stabilization.
if dateInfo.dayOfYear % 23 == 0 then
table.insert(events, {
title = "Noctic household registration review",
type = "noctic",
severity = "green",
a = "Temple Alabaster",
b = "Sanctum Vitalis",
delta = 7,
text = cultLink("Temple Alabaster") .. " and " .. cultLink("Sanctum Vitalis") .. " improve relations through a review of registered Noctic households and medical-stabilization records."
})
end
if dateInfo.dayOfYear % 37 == 0 then
table.insert(events, {
title = "Raw Noctic-Rabrev interdiction",
type = "noctic",
severity = "yellow",
a = "Guild of Golden Shadows",
b = "Rex Catonis",
delta = -6,
text = cultLink("Guild of Golden Shadows") .. " and " .. cultLink("Rex Catonis") .. " clash over forged permissions, raw-leaf custody seals, and shrine-market inspection authority."
})
end
if dateInfo.dayOfYear % 47 == 0 then
table.insert(events, {
title = "Noctic dream-panic containment",
type = "noctic",
severity = "green",
a = "Order of the Umbral Oracle",
b = "Reverie Nebulous",
delta = 6,
text = cultLink("Order of the Umbral Oracle") .. " and " .. cultLink("Reverie Nebulous") .. " coordinate dream-panic containment after reports of Noctic hallucinations and ancestral blood-memory visions."
})
end
if dateInfo.dayOfYear % 59 == 0 then
table.insert(events, {
title = "Unauthorized Noctic induction scandal",
type = "noctic",
severity = "red",
a = "Mystery of the Stygian Veil",
b = "Temple Alabaster",
delta = -12,
text = cultLink("Mystery of the Stygian Veil") .. " and " .. cultLink("Temple Alabaster") .. " enter open conflict after accusations of unauthorized Noctic induction without proper medical oversight."
})
end
-- Recurring calendar-cycle pressure.
if dateInfo.dayOfYear % 17 == 0 then
table.insert(events, {
title = "Bacchanalia review",
type = "governance",
severity = "green",
a = "Harmony Sanctum",
b = "Accord Concordia",
delta = 8,
text = cultLink("Harmony Sanctum") .. " and " .. cultLink("Accord Concordia") .. " improve relations during a formal review of mediation protocols."
})
end
if dateInfo.dayOfYear % 29 == 0 then
table.insert(events, {
title = "Fire-renewal dispute",
type = "doctrine",
severity = "yellow",
a = "Ignis Aeternum",
b = "Mystery of the Verdant Embrace",
delta = -10,
text = cultLink("Ignis Aeternum") .. " and " .. cultLink("Mystery of the Verdant Embrace") .. " clash over fire-renewal language near cultivated shrine land."
})
end
if dateInfo.dayOfYear % 41 == 0 then
table.insert(events, {
title = "Lunar interpretation compact",
type = "omen",
severity = "green",
a = "Conclave Illuminara",
b = "Order of Aurora Mystica",
delta = 9,
text = cultLink("Conclave Illuminara") .. " and " .. cultLink("Order of Aurora Mystica") .. " publish a shared lunar interpretation compact."
})
end
if dateInfo.dayOfYear % 53 == 0 then
table.insert(events, {
title = "Order-and-mirth procession",
type = "festival",
severity = "green",
a = "Rex Catonis",
b = "Mystery of Red Mirth",
delta = 8,
text = cultLink("Rex Catonis") .. " and " .. cultLink("Mystery of Red Mirth") .. " coordinate disciplined festival processions without arrests."
})
end
return events
end
--------------------------------------------------------------------------------
-- 6. DAILY RELATION CALCULATION
--------------------------------------------------------------------------------
local function addDelta(scores, notes, a, b, delta, note)
a, b = normalizeCultName(a), normalizeCultName(b)
if a == "" or b == "" or a == b then
return
end
local k = sortedPair(a, b)
scores[k] = clamp((scores[k] or 0) + (tonumber(delta) or 0), -150, 150)
notes[k] = notes[k] or {}
table.insert(notes[k], note)
end
local function applyEvent(scores, notes, e)
addDelta(scores, notes, e.a, e.b, e.delta, e.title)
if e.type == "mythic" then
local rowA = baselineRows[e.a]
local rowB = baselineRows[e.b]
for _, ally in ipairs((rowA and rowA.allies) or {}) do
addDelta(scores, notes, e.a, ally, e.delta > 0 and 4 or -4, "mythic spillover")
end
for _, ally in ipairs((rowB and rowB.allies) or {}) do
addDelta(scores, notes, e.b, ally, e.delta > 0 and 3 or -3, "mythic echo")
end
for _, enemy in ipairs((rowA and rowA.enemies) or {}) do
addDelta(scores, notes, e.a, enemy, e.delta > 0 and -3 or -5, "mythic backlash")
end
end
-- Severe events harden each participant's pre-existing enemies.
if e.severity == "red" then
for _, actor in ipairs({e.a, e.b}) do
local row = baselineRows[actor]
for _, enemy in ipairs((row and row.enemies) or {}) do
addDelta(scores, notes, actor, enemy, -3, "crisis hardening")
end
end
end
-- Cooperative events improve spillover relations with standing allies.
if e.severity == "green" then
for _, actor in ipairs({e.a, e.b}) do
local row = baselineRows[actor]
for _, ally in ipairs((row and row.allies) or {}) do
addDelta(scores, notes, actor, ally, 2, "cooperation spillover")
end
end
end
-- Yellow events add mild distrust to factions already in rivalry.
if e.severity == "yellow" then
for _, actor in ipairs({e.a, e.b}) do
local row = baselineRows[actor]
for _, enemy in ipairs((row and row.enemies) or {}) do
addDelta(scores, notes, actor, enemy, -1, "suspicion spillover")
end
end
end
end
local function buildDailyScores(dateInfo, args)
local scores, notes = buildBaseScores()
local events = generateDailyEvents(dateInfo, args)
for _, e in ipairs(events) do
applyEvent(scores, notes, e)
end
return scores, notes, events
end
function p.getDailyRelations(args)
args = args or {}
local dateInfo = getDateInfo(args)
local scores, notes, events = buildDailyScores(dateInfo, args)
return {
date = dateInfo,
scores = scores,
notes = notes,
events = events,
cults = cults,
baseline = baselineRows
}
end
--------------------------------------------------------------------------------
-- 7. DISPLAY HELPERS
--------------------------------------------------------------------------------
local function relationLabel(score)
score = tonumber(score) or 0
if score >= 120 then return "Sacred union" end
if score >= 100 then return "Miraculous compact" end
if score >= 80 then return "Covenantal alliance" end
if score >= 60 then return "Strong alliance" end
if score >= 40 then return "Standing alliance" end
if score >= 20 then return "Working cooperation" end
if score >= 8 then return "Mild cooperation" end
if score > -8 then return "Neutral / unstable" end
if score > -20 then return "Minor tension" end
if score > -40 then return "Open rivalry" end
if score > -60 then return "Sectarian crisis" end
if score > -80 then return "Near-war rupture" end
if score > -100 then return "Open schism" end
if score > -120 then return "Cult war footing" end
if score > -140 then return "Sacred proscription" end
return "Existential holy rupture"
end
local function scoreStyle(score)
score = tonumber(score) or 0
if score >= 100 then
return "background:#b7e1cd;"
elseif score >= 60 then
return "background:#cfe8cf;"
elseif score >= 20 then
return "background:#e8f5e9;"
elseif score > -20 then
return "background:#f8f9fa;"
elseif score > -60 then
return "background:#fff3cd;"
elseif score > -100 then
return "background:#f8d7da;"
else
return "background:#e6b8b7; font-weight:bold;"
end
end
local function severityBadge(severity)
if severity == "red" then
return '<span style="background:#f8d7da;border:1px solid #c99;padding:1px 5px;border-radius:8px;">Red</span>'
elseif severity == "yellow" then
return '<span style="background:#fff3cd;border:1px solid #cc9;padding:1px 5px;border-radius:8px;">Yellow</span>'
end
return '<span style="background:#d4edda;border:1px solid #9c9;padding:1px 5px;border-radius:8px;">Green</span>'
end
local function signed(n)
n = tonumber(n) or 0
if n > 0 then
return "+" .. tostring(n)
end
return tostring(n)
end
local function getPairScore(data, a, b)
a, b = normalizeCultName(a), normalizeCultName(b)
return data.scores[sortedPair(a, b)] or 0
end
local function topPairs(data, limit, mode)
limit = tonumber(limit) or 25
mode = trim(mode or "important")
local rows = {}
for k, score in pairs(data.scores or {}) do
local a, b = splitPairKey(k)
if cultByName[a] and cultByName[b] then
local item = {
a = a,
b = b,
score = score,
abs = math.abs(score)
}
if mode == "positive" then
if score > 0 then
table.insert(rows, item)
end
elseif mode == "negative" then
if score < 0 then
table.insert(rows, item)
end
else
table.insert(rows, item)
end
end
end
table.sort(rows, function(x, y)
if mode == "positive" then
if x.score == y.score then return x.a .. x.b < y.a .. y.b end
return x.score > y.score
elseif mode == "negative" then
if x.score == y.score then return x.a .. x.b < y.a .. y.b end
return x.score < y.score
else
if x.abs == y.abs then return x.a .. x.b < y.a .. y.b end
return x.abs > y.abs
end
end)
local out = {}
for i = 1, math.min(limit, #rows) do
table.insert(out, rows[i])
end
return out
end
--------------------------------------------------------------------------------
-- CULT DAILY RANKING
-- Ranks cults by the total sum of all their pairwise relation scores.
-- Higher total = broader positive climate across the cult-relations network.
--------------------------------------------------------------------------------
local function cultRelationTotals(data)
local totals = {}
for _, c in ipairs(cults) do
totals[c.name] = {
name = c.name,
total = 0,
count = 0,
positive = 0,
negative = 0,
neutral = 0,
bestCult = nil,
bestScore = nil,
worstCult = nil,
worstScore = nil
}
end
for k, score in pairs(data.scores or {}) do
local a, b = splitPairKey(k)
score = tonumber(score) or 0
if totals[a] and totals[b] then
for _, side in ipairs({
{name = a, other = b},
{name = b, other = a}
}) do
local row = totals[side.name]
row.total = row.total + score
row.count = row.count + 1
if score > 0 then
row.positive = row.positive + 1
elseif score < 0 then
row.negative = row.negative + 1
else
row.neutral = row.neutral + 1
end
if not row.bestScore or score > row.bestScore then
row.bestScore = score
row.bestCult = side.other
end
if not row.worstScore or score < row.worstScore then
row.worstScore = score
row.worstCult = side.other
end
end
end
end
local rows = {}
for _, c in ipairs(cults) do
local row = totals[c.name]
row.average = row.count > 0 and (row.total / row.count) or 0
table.insert(rows, row)
end
table.sort(rows, function(x, y)
if x.total == y.total then
if x.positive == y.positive then
return x.name < y.name
end
return x.positive > y.positive
end
return x.total > y.total
end)
return rows
end
local function makeCultRankingTable(data, limit)
limit = tonumber(limit) or #cults
limit = clamp(limit, 1, #cults)
local rows = cultRelationTotals(data)
local out = {}
table.insert(out, string.format("'''Daily cult-relations ranking — %s'''", data.date.longText))
table.insert(out, '{| class="wikitable sortable" style="width:100%; text-align:left;"')
table.insert(out, '! Rank')
table.insert(out, '! Cult')
table.insert(out, '! Home city')
table.insert(out, '! Total score')
table.insert(out, '! Average relation')
table.insert(out, '! Positive relations')
table.insert(out, '! Negative relations')
table.insert(out, '! Strongest relation')
table.insert(out, '! Weakest relation')
for i = 1, math.min(limit, #rows) do
local r = rows[i]
local cult = cultByName[r.name]
local home = cult and cityLink(cult.city) or "—"
local avgRounded = math.floor((r.average * 10) + 0.5) / 10
local avgStatus = relationLabel(r.average)
local best = r.bestCult and (cultLink(r.bestCult) .. " (" .. signed(r.bestScore) .. ")") or "—"
local worst = r.worstCult and (cultLink(r.worstCult) .. " (" .. signed(r.worstScore) .. ")") or "—"
table.insert(out, '|-')
table.insert(out, '| ' .. i)
table.insert(out, '| ' .. cultMark(r.name))
table.insert(out, '| ' .. home)
table.insert(out, string.format("| style='%s text-align:center; font-weight:bold;' | %s", scoreStyle(r.average), signed(r.total)))
table.insert(out, string.format("| style='text-align:center;' | %s / %s", tostring(avgRounded), avgStatus))
table.insert(out, "| style='text-align:center;' | " .. tostring(r.positive))
table.insert(out, "| style='text-align:center;' | " .. tostring(r.negative))
table.insert(out, '| ' .. best)
table.insert(out, '| ' .. worst)
end
table.insert(out, '|}')
return table.concat(out, "\n")
end
local function noteText(data, a, b)
local k = sortedPair(normalizeCultName(a), normalizeCultName(b))
local list = data.notes[k] or {}
if #list == 0 then
return "neutral baseline"
end
local seen = {}
local out = {}
for _, note in ipairs(list) do
if not seen[note] then
seen[note] = true
table.insert(out, note)
end
end
return table.concat(out, "; ")
end
--------------------------------------------------------------------------------
-- 8. PUBLIC DISPLAY FUNCTIONS
--------------------------------------------------------------------------------
function p.eventLog(frame)
local args = frame and frame.args or {}
local data = p.getDailyRelations(args)
local out = {}
table.insert(out, string.format("'''Daily cult-relations event log — %s'''", data.date.longText))
table.insert(out, '{| class="wikitable sortable" style="width:100%; text-align:left;"')
table.insert(out, '! Event')
table.insert(out, '! Severity')
table.insert(out, '! Location')
table.insert(out, '! Cults')
table.insert(out, '! Effect')
table.insert(out, '! Notes')
for _, e in ipairs(data.events or {}) do
table.insert(out, '|-')
table.insert(out, '| ' .. tostring(e.title or '—'))
table.insert(out, '| ' .. severityBadge(e.severity))
table.insert(out, '| ' .. eventCityLink(e))
table.insert(out, '| ' .. cultMark(e.a) .. ' ↔ ' .. cultMark(e.b))
table.insert(out, "| style='text-align:center;' | " .. signed(e.delta))
table.insert(out, '| ' .. tostring(e.text or '—'))
end
table.insert(out, '|}')
return table.concat(out, "\n")
end
function p.makePair(frame)
local args = frame and frame.args or {}
local a = normalizeCultName(args.a or args[1])
local b = normalizeCultName(args.b or args[2])
if a == "" or b == "" then
return "Specify two cults with <code>|a=</code> and <code>|b=</code>."
end
local data = p.getDailyRelations(args)
local score = getPairScore(data, a, b)
local out = {}
table.insert(out, '{| class="wikitable" style="width:100%; text-align:left;"')
table.insert(out, string.format("|+ Daily cult relation — %s", data.date.longText))
table.insert(out, "! Cult A !! Cult B !! Score !! Status !! Basis")
table.insert(out, "|-")
table.insert(out, string.format(
"| %s || %s || style='%s text-align:center; font-weight:bold;' | %d || %s || %s",
cultMark(a),
cultMark(b),
scoreStyle(score),
score,
relationLabel(score),
noteText(data, a, b)
))
table.insert(out, "|}")
return table.concat(out, "\n")
end
function p.makeTable(frame)
local args = frame and frame.args or {}
local data = p.getDailyRelations(args)
local limit = tonumber(args.limit or args.relationsLimit) or 25
local mode = trim(args.mode or "important")
local showEvents = lower(args.showEvents or "yes") ~= "no"
local showRanking = lower(args.showRanking or args.ranking or "yes") ~= "no"
local rankingLimit = tonumber(args.rankingLimit or args.rankLimit) or #cults
local compact = lower(args.compact or "no") == "yes"
local rows = topPairs(data, limit, mode)
local out = {}
table.insert(out, string.format(
"<div style='border:1px solid #aaa;background:#f8f9fa;padding:8px;margin:6px 0;'>" ..
"'''Reformed Stripping Path daily cult-relations index'''<br/>" ..
"Date: '''%s'''<br/>" ..
"Current zodiac period: '''%s'''<br/>" ..
"Relations are recalculated from standing alliances, standing rivalries, mythic affinities, zodiac pressure, and embedded daily events. " ..
"Positive scores indicate cooperation; negative scores indicate rivalry or hostility." ..
"</div>",
data.date.longText,
data.date.zodiac
))
if showRanking then
table.insert(out, makeCultRankingTable(data, rankingLimit))
table.insert(out, "\n")
end
table.insert(out, '{| class="wikitable sortable" style="width:100%; text-align:left;"')
table.insert(out, "! Cult A !! Cult B !! Score !! Status")
if not compact then
table.insert(out, "!! Current basis")
end
for _, r in ipairs(rows) do
table.insert(out, "|-")
local row = string.format(
"| %s || %s || style='%s text-align:center; font-weight:bold;' | %d || %s",
cultMark(r.a),
cultMark(r.b),
scoreStyle(r.score),
r.score,
relationLabel(r.score)
)
if not compact then
row = row .. " || " .. noteText(data, r.a, r.b)
end
table.insert(out, row)
end
table.insert(out, "|}")
if showEvents then
table.insert(out, "\n" .. p.eventLog({args = args}))
end
return table.concat(out, "\n")
end
function p.cultRanking(frame)
local args = frame and frame.args or {}
local data = p.getDailyRelations(args)
local limit = tonumber(args.rankingLimit or args.rankLimit or args.limit) or #cults
return makeCultRankingTable(data, limit)
end
function p.makeMatrix(frame)
local args = frame and frame.args or {}
local data = p.getDailyRelations(args)
local out = {}
table.insert(out, string.format("'''Cult-relations matrix — %s'''", data.date.longText))
table.insert(out, '{| class="wikitable" style="font-size:85%; text-align:center;"')
local header = {"! Cult"}
for _, c in ipairs(cults) do
table.insert(header, "!" .. c.key)
end
table.insert(out, table.concat(header, "\n"))
for _, a in ipairs(cults) do
local row = {"|-\n! " .. cultMark(a.name)}
for _, b in ipairs(cults) do
if a.name == b.name then
table.insert(row, "| style='background:#eee;' | —")
else
local score = getPairScore(data, a.name, b.name)
table.insert(row, string.format("| style='%s' | %d", scoreStyle(score), score))
end
end
table.insert(out, table.concat(row, "\n"))
end
table.insert(out, "|}")
return table.concat(out, "\n")
end
function p.cultList(frame)
local args = frame and frame.args or {}
local out = {}
table.insert(out, '{| class="wikitable sortable" style="width:100%; text-align:left;"')
table.insert(out, "! Key !! Cult / Mystery !! Divine !! Holy city !! General tendency")
for _, c in ipairs(cults) do
table.insert(out, "|-")
table.insert(out, string.format(
"| %s || %s || [[%s]] || %s || %s",
c.key,
cultMark(c.name),
c.divine,
cityLink(c.city),
table.concat(c.tendency or {}, ", ")
))
end
table.insert(out, "|}")
return table.concat(out, "\n")
end
function p.legend(frame)
return table.concat({
'{| class="wikitable" style="width:70%;"',
'! Score range !! Meaning',
'|-',
'| 120 to 150 || Sacred union',
'|-',
'| 100 to 119 || Miraculous compact',
'|-',
'| 80 to 99 || Covenantal alliance',
'|-',
'| 60 to 79 || Strong alliance',
'|-',
'| 40 to 59 || Standing alliance',
'|-',
'| 20 to 39 || Working cooperation',
'|-',
'| 8 to 19 || Mild cooperation',
'|-',
'| -7 to 7 || Neutral / unstable',
'|-',
'| -19 to -8 || Minor tension',
'|-',
'| -39 to -20 || Open rivalry',
'|-',
'| -59 to -40 || Sectarian crisis',
'|-',
'| -79 to -60 || Near-war rupture',
'|-',
'| -99 to -80 || Open schism',
'|-',
'| -119 to -100 || Cult war footing',
'|-',
'| -139 to -120 || Sacred proscription',
'|-',
'| -150 to -140 || Existential holy rupture',
'|}'
}, "\n")
end
--------------------------------------------------------------------------------
-- 9. RAW / DEBUG FUNCTIONS
--------------------------------------------------------------------------------
function p.rawDate(frame)
local args = frame and frame.args or {}
local d = getDateInfo(args)
return string.format(
"dayOfYear=%s; month=%s; day=%s; zodiac=%s; year=%s; raw=%s",
tostring(d.dayOfYear),
tostring(d.month),
tostring(d.dom),
tostring(d.zodiac),
tostring(d.year),
tostring(d.raw)
)
end
function p.rawScore(frame)
local args = frame and frame.args or {}
local a = normalizeCultName(args.a or args[1])
local b = normalizeCultName(args.b or args[2])
if a == "" or b == "" then
return "0"
end
local data = p.getDailyRelations(args)
return tostring(getPairScore(data, a, b))
end
function p.rawLabel(frame)
local args = frame and frame.args or {}
local a = normalizeCultName(args.a or args[1])
local b = normalizeCultName(args.b or args[2])
if a == "" or b == "" then
return "Neutral / watchful"
end
local data = p.getDailyRelations(args)
return relationLabel(getPairScore(data, a, b))
end
function p.rawEvents(frame)
local args = frame and frame.args or {}
local data = p.getDailyRelations(args)
local out = {}
for _, e in ipairs(data.events or {}) do
table.insert(out, string.format(
"%s: %s ↔ %s (%s)",
e.title,
e.a,
e.b,
signed(e.delta)
))
end
return table.concat(out, "; ")
end
return p