Module:MissionaryDailyOpsBVR
From MicrasWiki
Documentation for this module may be created at Module:MissionaryDailyOpsBVR/doc
-- Module:MissionaryDailyOpsBVR
-- Daily (PSSC-calendar keyed) missionary tasking schedule for the Temple Bank of the Reformed Stripping Path.
-- Deterministic per-day output (no math.random / no os.time seeding inside this module).
--
-- Color coding: row background reflects mission urgency (Green/Yellow/Red),
-- matching the palette used in Module:HatchMinistryDailyOps.
--
-- Optional integration:
-- |includeThreat=yes -> imports Module:ReligiousEvents and parses today's table once,
-- using it to bump severity and bias order selection (opt-in only).
--
-- Usage examples:
-- {{#invoke:MissionaryDailyOpsBVR|makeTable}}
-- {{#invoke:MissionaryDailyOpsBVR|makeTable|region=Core}}
-- {{#invoke:MissionaryDailyOpsBVR|makeTable|includeThreat=yes}}
-- {{#invoke:MissionaryDailyOpsBVR|makeCity|city=Vaeringheim}}
-- {{#invoke:MissionaryDailyOpsBVR|makeOrder|order=Order of Aurora Mystica}}
local p = {}
local cal = require('Module:BassaridianCalendar')
local DAYS_IN_YEAR = 183
--------------------------------------------------------------------------------
-- 0) UTIL: calendar parsing (mirrors HatchMinistryDailyOps)
--------------------------------------------------------------------------------
local function parseCalendarString(s)
if type(s) ~= "string" then return nil end
local dayOfYear, month, year = s:match("^(%d+),%s+([%a]+)%s+%b()%s*,%s*(%d+)%s+PSSC")
if dayOfYear and year then
return tonumber(year), tonumber(dayOfYear), month
end
return nil
end
local function extractEventText(s)
if type(s) ~= "string" then return "" end
local ev = s:match("PSSC%s+–%s+(.-)%s+–%s+Proverb:")
return ev or ""
end
local function doyToMonthDom(doy)
if doy <= 61 then
return 1, doy
elseif doy <= 122 then
return 2, doy - 61
else
return 3, doy - 122
end
end
local function getDateInfo()
local raw = cal.getCurrentDate()
local y, doy = parseCalendarString(raw)
if not y or not doy then
y, doy = 0, 1
raw = tostring(raw or "")
end
local m, dom = doyToMonthDom(doy)
return {
year = y,
dayOfYear = doy,
monthNum = m,
dom = dom,
text = string.format("%d/%d/%d PSSC", dom, m, y),
raw = raw,
calendarEvent = extractEventText(raw),
}
end
--------------------------------------------------------------------------------
-- 1) DETERMINISTIC HASH + RNG (NO bit32, NO math.random)
--------------------------------------------------------------------------------
local function hash31(str)
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 = seed % 2147483647
if s <= 0 then s = s + 2147483646 end
return function()
s = (1103515245 * s + 12345) % 2147483647
return s
end
end
local function pickFromList(nextInt, list, avoidValue)
if #list == 0 then return nil end
if #list == 1 then return list[1] end
local tries = 0
while tries < 12 do
local idx = (nextInt() % #list) + 1
local v = list[idx]
if v ~= avoidValue then return v end
tries = tries + 1
end
for _, v in ipairs(list) do
if v ~= avoidValue then return v end
end
return list[1]
end
--------------------------------------------------------------------------------
-- 2) COLOR CODING (same palette logic as HatchMinistryDailyOps)
--------------------------------------------------------------------------------
local function sevStyle(sev)
if sev == "red" then
return ' style="background-color:#f8d7da;"'
elseif sev == "yellow" then
return ' style="background-color:#fff3cd;"'
else
return ' style="background-color:#d4edda;"'
end
end
local function bumpSev(sev)
if sev == "green" then return "yellow" end
if sev == "yellow" then return "red" end
return "red"
end
--------------------------------------------------------------------------------
-- 3) CITY NETWORK (canonical list page)
--------------------------------------------------------------------------------
local function cityLink(name)
return string.format("[[List of cities in Bassaridia Vaeringheim#%s|%s]]", name, name)
end
-- 54 canonical cities, grouped for filtering
local cityOrder = {
-- Core majors
{name="Vaeringheim", region="Core"},
{name="Luminaria", region="Core"},
{name="Serena", region="Core"},
{name="Pyralis", region="Core"},
{name="Symphonara", region="Core"},
{name="Aurelia", region="Core"},
{name="Somniumpolis", region="Core"},
{name="Nexa", region="Core"},
{name="Lunalis Sancta", region="Core"},
{name="Sylvapolis", region="Core"},
-- Core minors
{name="Saluria", region="Core"},
{name="Aetherium", region="Core"},
{name="Ferrum Citadel", region="Core"},
{name="Acheron", region="Core"},
{name="Erythros", region="Core"},
{name="Catonis Atrium", region="Core"},
{name="Delphica", region="Core"},
{name="Koinonía", region="Core"},
{name="Aureum", region="Core"},
-- Alperkin
{name="The Alpazkigz", region="Alperkin"},
{name="Odiferia", region="Alperkin"},
-- New South Jangsong – major
{name="Skýrophos", region="New South Jangsong"},
{name="Bjornopolis", region="New South Jangsong"},
{name="Aegirheim", region="New South Jangsong"},
{name="Norsolyra", region="New South Jangsong"},
{name="Thorsalon", region="New South Jangsong"},
-- New South Jangsong – minor
{name="Pelagia", region="New South Jangsong"},
{name="Myrene", region="New South Jangsong"},
{name="Thyrea", region="New South Jangsong"},
{name="Ephyra", region="New South Jangsong"},
{name="Halicarn", region="New South Jangsong"},
-- Haifan Bassaridia – major
{name="Keybir-Aviv", region="Haifan Bassaridia"},
{name="Tel-Amin", region="Haifan Bassaridia"},
{name="Diamandis", region="Haifan Bassaridia"},
{name="Jogi", region="Haifan Bassaridia"},
{name="Lewisburg", region="Haifan Bassaridia"},
-- Haifan Bassaridia – minor
{name="Thermosalem", region="Haifan Bassaridia"},
{name="Akróstadium", region="Haifan Bassaridia"},
{name="Sufriya", region="Haifan Bassaridia"},
{name="Lykopolis", region="Haifan Bassaridia"},
-- Normark
{name="Ardclach", region="Normark"},
{name="Riddersborg", region="Normark"},
-- Ouriana
{name="Bashkim", region="Ouriana"},
{name="Ourid", region="Ouriana"},
{name="Tonar", region="Ouriana"},
-- Caledonian dependencies
{name="Fanghorn", region="Caledonia"},
{name="Eikbu", region="Caledonia"},
{name="Galvø", region="Caledonia"},
{name="Sårensby", region="Caledonia"},
{name="Slevik", region="Caledonia"},
{name="Sjøsborg", region="Caledonia"},
{name="Storesund", region="Caledonia"},
{name="Notranskja", region="Caledonia"},
{name="Kaledonija", region="Caledonia"},
}
local function regionPass(filter, cityRegion)
if not filter or filter == "" or filter == "All" then return true end
-- Allow a few convenient aliases
if filter == "Core" then return cityRegion == "Core" end
if filter == "NSJ" then return cityRegion == "New South Jangsong" end
if filter == "Haifan" then return cityRegion == "Haifan Bassaridia" end
if filter == "Caledonian" then return cityRegion == "Caledonia" end
return cityRegion == filter
end
--------------------------------------------------------------------------------
-- 4) MISSIONARY ORGANIZATION (sizes align to RSP org table)
--------------------------------------------------------------------------------
local org = {
Lampade = {size=3, code="L"},
Hetairoi = {size=6, code="H"},
Dodekade = {size=12, code="D"},
Kleisthenes = {size=25, code="K"},
Stoa = {size=50, code="S"},
Thiasos = {size=100, code="T"},
}
local function pickOrgUnit(nextInt, sev)
if sev == "green" then
return pickFromList(nextInt, {"Lampade","Hetairoi"}, nil)
elseif sev == "yellow" then
return pickFromList(nextInt, {"Hetairoi","Dodekade","Kleisthenes"}, nil)
else
return pickFromList(nextInt, {"Kleisthenes","Stoa","Thiasos"}, nil)
end
end
--------------------------------------------------------------------------------
-- 5) CULT/ORDER PROFILES + TASK LIBRARIES
--------------------------------------------------------------------------------
local function orderLink(pageTitle, anchor, label)
return string.format("[[%s#%s|%s]]", pageTitle, anchor, label)
end
local function T(text, sev, tag)
return {text=text, sev=sev, tag=tag or ""}
end
local orders = {
{
name="Ordo Solis Invicti",
key="OSI",
link=orderLink("Atos (Reformed Stripping Path)","Ordo Solis Invicti","Ordo Solis Invicti"),
tasks={
T("Dawn rites and public illumination drill to restore civic clarity and discourage eclipse-rumors in the market wards.", "green", "rumor"),
T("Temple-school literacy session paired with cosmology instruction and rooftop shrine maintenance inspections.", "green", "school"),
T("Radiance audit of public lamps, mirrored courts, and shrine sightlines; repair orders issued under Temple Bank stipend.", "yellow", "infrastructure"),
T("Solar Circle consecration for a new neighborhood shrine, with crowd choreography and compliance wardens.", "yellow", "shrine"),
T("Emergency morale procession after night unrest; hymn leadership and calm restoration at canal crossings.", "red", "unrest"),
}
},
{
name="Order of Aurora Mystica",
key="OAM",
link=orderLink("Eos (Reformed Stripping Path)","Order of Aurora Mystica","Order of Aurora Mystica"),
tasks={
T("Sunrise vigil and reconciliation rite for competing guilds; signed harmony pledges archived by Temple Bank clerks.", "green", "mediation"),
T("Dawn-watch observatory check and omen bulletin issued to reduce panic and stabilize labor shifts.", "green", "omen"),
T("Civic blessing for new housing blocks; routing of pilgrimage foot-traffic to prevent crush points.", "yellow", "crowd"),
T("Counter-heresy sermon cycle following illicit broadsides; witness interviews conducted with local elders.", "yellow", "heresy"),
T("Rapid-response pacification after arson rumor; dawn procession under guard with emergency stipend release.", "red", "arson"),
}
},
{
name="Harmony Sanctum",
key="HSA",
link=orderLink("Micras (Reformed Stripping Path)","Harmony Sanctum","Harmony Sanctum"),
tasks={
T("Neighborhood mediation session for family disputes and trade-feud spillover; binding accords notarized.", "green", "mediation"),
T("Harmony clinic for stress and rumor contagion; quiet-room rotations and guided breathing rites.", "green", "health"),
T("Loyalty and morale audit of a civic office after complaint surge; corrective training scheduled.", "yellow", "audit"),
T("Containment of a cult schism meeting; supervised dialogue and sanction warnings delivered.", "yellow", "schism"),
T("Major crowd-stabilization operation after riot signals; staged dispersal with peace-liturgy and escorts.", "red", "riot"),
}
},
{
name="Ignis Aeternum",
key="IGA",
link=orderLink("Pyros (Reformed Stripping Path)","Ignis Aeternum","Ignis Aeternum"),
tasks={
T("Controlled fire blessing for workshops; safety rite that doubles as an inspection of fuel and storage.", "green", "fire"),
T("Purification burn at a compromised shrine-store; ash sealed and catalogued for Temple Bank disposal.", "yellow", "contam"),
T("Investigation of incendiary threats near a gate-temple; firebreak plan issued to local wardens.", "yellow", "arson"),
T("Emergency response to active arson or explosive rumor; sanctified suppression teams deployed with escorts.", "red", "explosive"),
T("Post-incident purification circuit across three oratories, restoring ritual confidence by nightfall.", "red", "incident"),
}
},
{
name="Celestial Harmony Sect",
key="CHS",
link=orderLink("Indigo (Reformed Stripping Path)","Celestial Harmony Sect","Celestial Harmony Sect"),
tasks={
T("Diplomatic courtesy visit to a city council; alignments review and oath renewal with minimal spectacle.", "green", "diplomacy"),
T("Star-chart instruction for apprentices; quiet observatory checks and record sealing.", "green", "astronomy"),
T("Faction-balance mediation between rival shrines; written compromise placed in Temple Bank archive.", "yellow", "balance"),
T("Rumor forensics on ‘forbidden rite’ claims; witness tree compiled and forwarded to Temple authorities.", "yellow", "rumor"),
T("High-risk stabilization after cult confrontation; negotiators embedded with escorts to prevent escalation.", "red", "confrontation"),
}
},
{
name="Guild of Golden Shadows",
key="GGS",
link=orderLink("Chrysos (Reformed Stripping Path)","Guild of Golden Shadows","Guild of Golden Shadows"),
tasks={
T("Discrete audit of offertory ledgers and voucher seals; minor variances reconciled quietly.", "green", "audit"),
T("Merchant vetting interviews for a sensitive consignment; anonymity rites certified.", "yellow", "merchant"),
T("Counter-counterfeit sweep targeting forged stamps and false Temple consignments; evidence boxed and logged.", "yellow", "forgery"),
T("Recovery detail for stolen reliquary property; shadow-following surveillance and lawful seizure.", "red", "theft"),
T("Crisis finance stabilization after panic; emergency stipend routing and rumor suppression coordination.", "red", "panic"),
}
},
{
name="Azure Sentinel Sect",
key="ASS",
link=orderLink("Thalassa (Reformed Stripping Path)","Azure Sentinel Sect","Azure Sentinel Sect"),
tasks={
T("Canal and fountain sanctification circuit; water-quality blessings paired with public reassurance.", "green", "water"),
T("Harbor-side watch for sabotage indicators; shrine-wardens trained on lock and pier checks.", "yellow", "sabotage"),
T("Storm or flood readiness rite; evacuation hymn routes verified with civic stewards.", "yellow", "flood"),
T("Quarantine support for suspected waterborne illness; controlled distribution points blessed and monitored.", "red", "outbreak"),
T("Major incident response: water infrastructure damage; emergency sanctification and ration-order enforcement.", "red", "infrastructure"),
}
},
{
name="Reverie Nebulous",
key="REN",
link=orderLink("Nephele (Reformed Stripping Path)","Reverie Nebulous","Reverie Nebulous"),
tasks={
T("Dream-interpretation clinic to reduce anxiety; rumor narratives redirected into sanctioned symbolism.", "green", "dream"),
T("Nocturne counseling for insomnia cluster; soft-lit rites and chant rotation schedules.", "green", "sleep"),
T("Investigation of forbidden homily broadcasts; witness interviews and calm messaging drafted.", "yellow", "broadcast"),
T("Containment of mass-vision panic; staged public reassurance with tightly controlled procession.", "red", "panic"),
T("Stabilization after hallucination-linked violence; liaison with Harmony Sanctum for long-tail care.", "red", "violence"),
}
},
{
name="Eon Fellowship",
key="EOF",
link=orderLink("Glinos (Reformed Stripping Path)","Eon Fellowship","Eon Fellowship"),
tasks={
T("Cold-weather relief checks and ration fairness review; storage seals verified.", "green", "relief"),
T("Infrastructure endurance audit: roofs, walls, and bridges; frost damage triage list issued.", "yellow", "infrastructure"),
T("Conflict de-escalation for winter resource disputes; binding queue rules posted.", "yellow", "queue"),
T("Emergency shelter activation after storm loss; warm-hall rites and controlled entry.", "red", "storm"),
T("High-risk escort for frontier convoys under threat; endurance cadres embedded with wardens.", "red", "frontier"),
}
},
{
name="Order of the Umbral Oracle",
key="UOR",
link=orderLink("Noctis (Reformed Stripping Path)","Order of the Umbral Oracle","Order of the Umbral Oracle"),
tasks={
T("Night watch and omen-reading in the temple quarter; guidance slips sealed for officials.", "green", "omen"),
T("Discrete consultation for a magistrate; truth-testing rite witnessed by two scribes.", "yellow", "inquiry"),
T("Shadow-route mapping after unexplained disappearances; safe-path markers installed.", "yellow", "disappear"),
T("Emergency intervention after ‘demonic sign’ sightings; controlled crowd handling and ward placement.", "red", "demonic"),
T("High-risk extraction of a threatened cantor; night escort to secure sanctuary.", "red", "abduction"),
}
},
{
name="Mystery of the Verdant Embrace",
key="MVE",
link=orderLink("Opsithe (Reformed Stripping Path)","Mystery of the Verdant Embrace","Mystery of the Verdant Embrace"),
tasks={
T("Harvest blessing and storage rite; spoilage prevention guidance issued to granaries.", "green", "harvest"),
T("Garden-shrine restoration day with community labor; devotional planting aligned to calendar.", "green", "garden"),
T("Food shortage mediation and ration arbitration; Temple Bank stipend adjustments proposed.", "yellow", "shortage"),
T("Pest or blight containment rites; field inspections with farmers’ associations.", "yellow", "blight"),
T("Famine-risk surge response; emergency distribution queues sanctified and monitored.", "red", "famine"),
}
},
{
name="Conclave Illuminara",
key="CIL",
link=orderLink("Tarsica (Reformed Stripping Path)","Conclave Illuminara","Conclave Illuminara"),
tasks={
T("Moonlit instruction circle for apprentices; eclipse-watch protocol rehearsed.", "green", "lunar"),
T("Archive sealing of sensitive liturgies; access logs reviewed for anomalies.", "yellow", "archive"),
T("Public lantern rite to counter rumor cascades; controlled routes and timing.", "yellow", "rumor"),
T("Emergency omen bulletin after night panic; official statement drafted and delivered.", "red", "panic"),
T("High-risk protection of an observatory site under threat; wards reinforced and watchers stationed.", "red", "threat"),
}
},
{
name="Sanctum Vitalis",
key="SCV",
link=orderLink("Plateau (Reformed Stripping Path)","Sanctum Vitalis","Sanctum Vitalis"),
tasks={
T("Clinic day: basic health screenings and hygiene instruction; water points checked.", "green", "health"),
T("Supplement and ration fortification distribution under Temple Bank authorization.", "yellow", "nutrition"),
T("Outbreak protocol rehearsal with civic healers; spacing rules posted at shrines.", "yellow", "outbreak"),
T("Active outbreak response: triage lanes established; quarantine support and calm messaging.", "red", "outbreak"),
T("Mass-casualty contingency drill after violence reports; intake points and transport routes mapped.", "red", "violence"),
}
},
{
name="Temple Alabaster",
key="ALB",
link=orderLink("Ivory (Reformed Stripping Path)","Temple Alabaster","Temple Alabaster"),
tasks={
T("Purity inspection of ritual vessels and incense stock; minor replacements approved.", "green", "purity"),
T("Doctrinal correction session after unsigned broadsides; sanctioned texts distributed.", "yellow", "heresy"),
T("Sanctuary security review for valuables; locking discipline and witness rules enforced.", "yellow", "security"),
T("Emergency purification after poisoned incense discovery; wards placed and offenders sought.", "red", "poison"),
T("High-risk protection of a central temple during unrest; entry control and calm liturgy.", "red", "unrest"),
}
},
{
name="Court of the Ironclad",
key="CIC",
link=orderLink("Fenrir (Reformed Stripping Path)","Court of the Ironclad","Court of the Ironclad"),
tasks={
T("Discipline drill for shrine wardens; non-lethal control and restraint practices.", "green", "training"),
T("Escort for a threatened courier; route cleared and handover documented.", "yellow", "escort"),
T("Guard training in a frontier district; coordination with civic militia.", "yellow", "militia"),
T("Immediate reinforcement during riot signals; cordons established and safe egress opened.", "red", "riot"),
T("High-risk interdiction support for a seized oratory; secure perimeter until authorities arrive.", "red", "siege"),
}
},
{
name="Accord Concordia",
key="ACC",
link=orderLink("Lukedu (Reformed Stripping Path)","The Accord Concordia","Accord Concordia"),
tasks={
T("Public harmony dialogue in a garden pavilion; disputants guided to a signed concord.", "green", "mediation"),
T("Catacomb Harmonic Communion support: logistics, spacing, and participant verification.", "yellow", "rite"),
T("Civic cooperation workshop for rival guilds; shared calendar commitments set.", "yellow", "guild"),
T("Emergency reconciliation after faction clash; mediators embedded with escorts.", "red", "clash"),
T("High-risk crowd calming after panic; sound-liturgical dispersal and safe routes enforced.", "red", "panic"),
}
},
{
name="Mystery of the Stygian Veil",
key="MSV",
link=orderLink("Styx (Reformed Stripping Path)","Mystery of the Stygian Veil","Mystery of the Stygian Veil"),
tasks={
T("Funerary liaison and cemetery vigil support; grief rites standardized.", "green", "funeral"),
T("Transition counseling for displaced families; resettlement blessings recorded.", "yellow", "displacement"),
T("Investigation of graveyard desecration rumor; ward placement and witness interviews.", "yellow", "desecration"),
T("Emergency containment after death-cult provocation; controlled rites to prevent escalation.", "red", "provocation"),
T("High-risk escort of relics through hostile streets; night route and guards coordinated.", "red", "escort"),
}
},
{
name="Sylvan Fellowship",
key="SYF",
link=orderLink("Faun (Reformed Stripping Path)","Sylvan Fellowship","Sylvan Fellowship"),
tasks={
T("Forest-edge shrine maintenance and trail safety check; minor repairs logged.", "green", "trail"),
T("Ecology rite with farmers: pest balance and water stewardship guidance.", "yellow", "ecology"),
T("Containment of illegal logging or poaching rumor; community patrol coordination.", "yellow", "poaching"),
T("Emergency response to wildfire risk; firebreak rites and evacuation path marking.", "red", "wildfire"),
T("High-risk protection for a threatened grove shrine; cordon and ward reinforcement.", "red", "threat"),
}
},
{
name="Mystery of Red Mirth",
key="MRM",
link=orderLink("Silenus (Reformed Stripping Path)","Myster of Red Mirth","Mystery of Red Mirth"),
tasks={
T("Morale rite and sanctioned celebration oversight; prevent disorder while restoring joy.", "green", "morale"),
T("Festival logistics support with crowd timing; safe routes and water points verified.", "yellow", "festival"),
T("Containment of illicit revel causing unrest; correction by sanctioned rite and fines.", "yellow", "illicit"),
T("Emergency morale stabilization after violence; controlled gatherings and escorts.", "red", "violence"),
T("High-risk de-escalation during riot-prone festival; staged dispersal and peace hymns.", "red", "riot"),
}
},
{
name="Rex Catonis",
key="RCA",
link=orderLink("Cato (Reformed Stripping Path)","Rex Catonis","Rex Catonis"),
tasks={
T("Civic order inspection: queue rules, market discipline, and shrine traffic flow.", "green", "order"),
T("Administrative compliance review after complaints; corrective edicts posted.", "yellow", "compliance"),
T("Guard liaison for a threatened civic hall; protocol rehearsal and watch rotation.", "yellow", "security"),
T("Emergency restoration of order after mob action; curfew-litany and escorted dispersal.", "red", "mob"),
T("High-risk support to authorities during barricade incident; perimeter discipline enforced.", "red", "barricade"),
}
},
{
name="Sanctum Delphica",
key="SDL",
link=orderLink("Sisera (Reformed Stripping Path)","Sanctum Delphica","Sanctum Delphica"),
tasks={
T("Oracle consultation clinic for officials; questions logged and sealed.", "green", "oracle"),
T("Archive and library integrity check; catalog drift corrected and access verified.", "yellow", "archive"),
T("Investigation of forged prophecy pamphlets; counterfeit sources traced.", "yellow", "forgery"),
T("Emergency counter-homily after loudspeaker seizure; sanctioned address delivered.", "red", "broadcast"),
T("High-risk protection for a key temple scholar; escort and secure lodging arranged.", "red", "threat"),
}
},
{
name="Ordo Amicitia",
key="OAI",
link=orderLink("Erasmus (Reformed Stripping Path)","Ordo Amicitia","Ordo Amicitia"),
tasks={
T("Charity distribution and household check-ins; kindness register updated for stipend support.", "green", "charity"),
T("Offering logistics for a major rite; livestock and treasure accounted and sealed.", "yellow", "offering"),
T("Mediation for neighborhood poverty disputes; aid allocations made transparent.", "yellow", "aid"),
T("Emergency relief surge after incident; soup lines sanctified and guarded.", "red", "relief"),
T("High-risk protection for a vulnerable procession route; escorts and safe shelters staged.", "red", "procession"),
}
},
{
name="Temple Illuminata",
key="TIL",
link=orderLink("Nikolaj (Reformed Stripping Path)","Temple Illuminata","Temple Illuminata"),
tasks={
T("Lamp and beacon maintenance blessing; public wayfinding clarity restored.", "green", "beacon"),
T("Infrastructure illumination audit for tunnels and arcologies; repair notices issued.", "yellow", "infrastructure"),
T("Investigation of signal tampering near a transit node; seals replaced and watchers posted.", "yellow", "tamper"),
T("Emergency lighting deployment after sabotage; temporary beacons installed with escorts.", "red", "sabotage"),
T("High-risk stabilization at a transport hub after panic; controlled flow and sanctuary lanes.", "red", "panic"),
}
},
}
-- Build index maps
local orderByName = {}
local orderNames = {}
for i, o in ipairs(orders) do
orderByName[o.name] = o
orderNames[i] = o.name
end
--------------------------------------------------------------------------------
-- 6) OPTIONAL: pull today's ReligiousEvents table and map per-city urgency
--------------------------------------------------------------------------------
local function parseReligiousEventsTable(wikitext)
local out = {}
if type(wikitext) ~= "string" then return out end
-- Row pattern: | date || City || Event || Urgency
for line in wikitext:gmatch("[^\n]+") do
if line:match("^|%s*%d+/%d+/%d+%s+PSSC") then
local date, city, event, urg = line:match("^|%s*(.-)%s*||%s*(.-)%s*||%s*(.-)%s*||%s*(.-)%s*$")
if city and urg then
out[city] = {urg=urg, event=event or ""}
end
end
end
return out
end
local function threatSev(th)
if not th or not th.urg then return nil end
local u = th.urg
if u == "N/A" or u == "LOW" then return "green" end
if u == "MEDIUM" then return "yellow" end
if u == "HIGH" or u == "CRITICAL" then return "red" end
return "yellow"
end
local function biasOrderByThreat(nextInt, th)
if not th or type(th.event) ~= "string" then
return pickFromList(nextInt, orderNames, nil)
end
local e = th.event:lower()
-- A few simple keyword biases
if e:match("incendiary") or e:match("arson") or e:match("explosive") then return "Ignis Aeternum" end
if e:match("poison") or e:match("outbreak") or e:match("treated") or e:match("hygiene") then return "Sanctum Vitalis" end
if e:match("rumor") or e:match("panic") or e:match("dream") then return "Reverie Nebulous" end
if e:match("demonic") or e:match("exorc") or e:match("symbols") then return "Order of the Umbral Oracle" end
if e:match("offertory") or e:match("ledger") or e:match("counterfeit") or e:match("forged") then return "Guild of Golden Shadows" end
if e:match("barricade") or e:match("armed") or e:match("zealots") then return "Court of the Ironclad" end
if e:match("funeral") or e:match("cortège") or e:match("reliquary") then return "Mystery of the Stygian Veil" end
return pickFromList(nextInt, orderNames, nil)
end
--------------------------------------------------------------------------------
-- 7) TEMPLE BANK NOTES (deterministic snippets)
--------------------------------------------------------------------------------
local bankNotes = {
"Temple Bank stipend verified; local shrine receipts reconciled; next audit window scheduled.",
"Voucher authorizations issued for ritual materials; chain-of-custody notes logged for any seized items.",
"Investor liaison note filed; payroll routing confirmed for the deployed unit; compliance clerk assigned.",
"Donation flow stabilized; offertory seals inspected; variance thresholds set for the next three days.",
"Emergency stipend line opened for crowd-control and relief logistics; disbursement witnessed by two scribes.",
}
--------------------------------------------------------------------------------
-- 8) BUILD ONE CITY ROW (deterministic per city/day)
--------------------------------------------------------------------------------
local function buildCityRow(di, cityName, cityRegion, threatMap, includeThreat)
local seedStr = string.format("%d:%03d:%s", di.year, di.dayOfYear, cityName)
local nextInt = lcg(hash31(seedStr))
local th = includeThreat and threatMap and threatMap[cityName] or nil
local chosenOrderName
if includeThreat and th then
chosenOrderName = biasOrderByThreat(nextInt, th)
else
-- Gentle geographic bias: some regions pick slightly different mixes
local bias = cityRegion .. ":" .. cityName .. ":" .. tostring(di.dayOfYear)
local idx = (hash31(bias) % #orderNames) + 1
chosenOrderName = orderNames[idx]
end
local o = orderByName[chosenOrderName] or orders[1]
-- Pick task; avoid repeating the same tag if possible (simple)
local task = pickFromList(nextInt, o.tasks, nil)
local sev = task.sev or "green"
-- If threat is worse than task, bump severity
if includeThreat and th then
local ts = threatSev(th)
if ts == "yellow" and sev == "green" then sev = "yellow" end
if ts == "red" and sev ~= "red" then sev = "red" end
end
local unitName = pickOrgUnit(nextInt, sev)
local unitMeta = org[unitName]
local unitIndex = (nextInt() % 12) + 1
local unitCode = string.format("%s-%s%d", o.key, unitMeta.code, unitIndex)
local headcount = unitMeta.size
local bank = pickFromList(nextInt, bankNotes, nil)
local threatText = ""
local threatUrg = ""
if includeThreat and th then
threatText = th.event or ""
threatUrg = th.urg or ""
end
return {
style = sevStyle(sev),
date = di.text,
region = cityRegion,
city = cityLink(cityName),
order = o.link,
unit = string.format("%s (%d) [%s]", unitName, headcount, unitCode),
task = task.text,
bank = bank,
threat = threatText,
threatUrg = threatUrg,
sev = sev,
}
end
--------------------------------------------------------------------------------
-- 9) MAIN TABLE
--------------------------------------------------------------------------------
function p.makeTable(frame)
local args = (frame and frame.args) or {}
local regionFilter = tostring(args.region or "All")
local showLegend = tostring(args.showlegend or "yes"):lower() ~= "no"
local includeThreat = tostring(args.includeThreat or "no"):lower() == "yes"
local limit = tonumber(args.limit or "") -- optional max rows
local di = getDateInfo()
-- Optional: build threatMap once
local threatMap = {}
if includeThreat then
-- NOTE: ReligiousEvents.makeTable uses math.randomseed internally; opt-in only.
local ok, rel = pcall(require, "Module:ReligiousEvents")
if ok and rel and type(rel.makeTable) == "function" then
local t = rel.makeTable()
threatMap = parseReligiousEventsTable(t)
end
end
local out = {}
table.insert(out, "{| class='wikitable sortable'")
table.insert(out, string.format("! style='white-space:nowrap;' | Date !! City !! Region !! Cult Order !! Unit !! Tasking !! Temple Bank Notes%s !! Severity",
includeThreat and " !! Threat Watch" or ""
))
local count = 0
for _, c in ipairs(cityOrder) do
if regionPass(regionFilter, c.region) then
local row = buildCityRow(di, c.name, c.region, threatMap, includeThreat)
table.insert(out, "|-" .. row.style)
if includeThreat then
local threatCell = row.threat ~= "" and (row.threat .. " (" .. (row.threatUrg or "") .. ")") or "No reports / n.a."
table.insert(out, string.format("| %s || %s || %s || %s || %s || %s || %s || %s || %s",
row.date, row.city, row.region, row.order, row.unit, row.task, row.bank, threatCell, row.sev
))
else
table.insert(out, string.format("| %s || %s || %s || %s || %s || %s || %s || %s",
row.date, row.city, row.region, row.order, row.unit, row.task, row.bank, row.sev
))
end
count = count + 1
if limit and count >= limit then break end
end
end
table.insert(out, "|}")
if showLegend then
table.insert(out, "\n<div style='margin-top:0.5em; font-size:90%;'>")
table.insert(out, "<b>Legend:</b> ")
table.insert(out, "<span style='background:#d4edda;padding:2px 6px;border:1px solid #c3e6cb;'>Green</span> = Routine rites / civic support ")
table.insert(out, "<span style='background:#fff3cd;padding:2px 6px;border:1px solid #ffeeba;'>Yellow</span> = Heightened oversight / rumor control / targeted intervention ")
table.insert(out, "<span style='background:#f8d7da;padding:2px 6px;border:1px solid #f5c6cb;'>Red</span> = Crisis response / unrest / outbreak / high-risk operations")
table.insert(out, "</div>")
end
-- Optional header note (calendar event)
if di.calendarEvent and di.calendarEvent ~= "" then
table.insert(out, string.format("\n<div style='margin-top:0.5em; font-size:90%%;'><b>Calendar note:</b> %s</div>", di.calendarEvent))
end
return table.concat(out, "\n")
end
--------------------------------------------------------------------------------
-- 10) CITY-ONLY VIEW
--------------------------------------------------------------------------------
function p.makeCity(frame)
local args = (frame and frame.args) or {}
local city = tostring(args.city or "")
if city == "" then
return "''Error: city parameter required.''"
end
args.limit = 1
-- We reuse makeTable logic by filtering manually
local di = getDateInfo()
local includeThreat = tostring(args.includeThreat or "no"):lower() == "yes"
local threatMap = {}
if includeThreat then
local ok, rel = pcall(require, "Module:ReligiousEvents")
if ok and rel and type(rel.makeTable) == "function" then
threatMap = parseReligiousEventsTable(rel.makeTable())
end
end
-- Find region
local reg = "Core"
for _, c in ipairs(cityOrder) do
if c.name == city then reg = c.region break end
end
local row = buildCityRow(di, city, reg, threatMap, includeThreat)
local out = {}
table.insert(out, "{| class='wikitable'")
if includeThreat then
table.insert(out, "! Date !! City !! Region !! Cult Order !! Unit !! Tasking !! Temple Bank Notes !! Threat Watch !! Severity")
table.insert(out, "|-" .. row.style)
local threatCell = row.threat ~= "" and (row.threat .. " (" .. (row.threatUrg or "") .. ")") or "No reports / n.a."
table.insert(out, string.format("| %s || %s || %s || %s || %s || %s || %s || %s || %s",
row.date, row.city, row.region, row.order, row.unit, row.task, row.bank, threatCell, row.sev
))
else
table.insert(out, "! Date !! City !! Region !! Cult Order !! Unit !! Tasking !! Temple Bank Notes !! Severity")
table.insert(out, "|-" .. row.style)
table.insert(out, string.format("| %s || %s || %s || %s || %s || %s || %s || %s",
row.date, row.city, row.region, row.order, row.unit, row.task, row.bank, row.sev
))
end
table.insert(out, "|}")
return table.concat(out, "\n")
end
--------------------------------------------------------------------------------
-- 11) ORDER-ONLY VIEW (shows missions for that order across the city network)
--------------------------------------------------------------------------------
function p.makeOrder(frame)
local args = (frame and frame.args) or {}
local wanted = tostring(args.order or "")
if wanted == "" then
return "''Error: order parameter required (use exact order name, e.g., Order of Aurora Mystica).''"
end
local di = getDateInfo()
local includeThreat = tostring(args.includeThreat or "no"):lower() == "yes"
local threatMap = {}
if includeThreat then
local ok, rel = pcall(require, "Module:ReligiousEvents")
if ok and rel and type(rel.makeTable) == "function" then
threatMap = parseReligiousEventsTable(rel.makeTable())
end
end
local out = {}
table.insert(out, "{| class='wikitable sortable'")
table.insert(out, "! Date !! City !! Region !! Unit !! Tasking !! Temple Bank Notes" .. (includeThreat and " !! Threat Watch" or "") .. " !! Severity")
for _, c in ipairs(cityOrder) do
local row = buildCityRow(di, c.name, c.region, threatMap, includeThreat)
-- Compare by display name (strip wikilink wrapper crudely by checking map key)
local orderObj = orderByName[wanted]
if orderObj and row.order == orderObj.link then
table.insert(out, "|-" .. row.style)
if includeThreat then
local threatCell = row.threat ~= "" and (row.threat .. " (" .. (row.threatUrg or "") .. ")") or "No reports / n.a."
table.insert(out, string.format("| %s || %s || %s || %s || %s || %s || %s || %s",
row.date, row.city, row.region, row.unit, row.task, row.bank, threatCell, row.sev
))
else
table.insert(out, string.format("| %s || %s || %s || %s || %s || %s || %s",
row.date, row.city, row.region, row.unit, row.task, row.bank, row.sev
))
end
end
end
table.insert(out, "|}")
return table.concat(out, "\n")
end
return p