Module:MissionaryDailyOpsBVR: Difference between revisions
From MicrasWiki
Jump to navigationJump to search
NewZimiaGov (talk | contribs) Undo revision 387792 by NewZimiaGov (talk) Tag: Undo |
NewZimiaGov (talk | contribs) No edit summary |
||
| Line 1: | Line 1: | ||
-- Module:MissionaryDailyOpsBVR | -- Module:MissionaryDailyOpsBVR | ||
-- Daily | -- Daily missionary tasking ledger (Temple Bank / RSP). | ||
-- | -- | ||
-- | -- WHAT’S FIXED / UPDATED (per your latest notes): | ||
-- • Correct CITY flags and CULT flags: | |||
-- • | -- - City flag filename logic + Skýrophos override matches Module:GeneralPortOperations | ||
-- | -- - Cult flag/logo filenames are taken directly from Module:GeneralPortOperations addLogo() mappings | ||
-- | -- - No file-existence checks (avoids “too many expensive function calls”) | ||
-- | -- • Ops Alert integration (Module:BassaridiaForecast): | ||
-- • | -- - Adds Ops Alert column (badge + condition) | ||
-- - Adds | -- - Orange/Red bumps severity + appends a short weather-driven overlay (Orange/Red only) | ||
-- - | -- • Includes Temple Aprobelle with dedicated task pool (10+ tasks) | ||
-- • Optional econ scan (EXPENSIVE) ONLY runs if showEcon=yes | |||
-- • Optional Threat Watch (Module:ReligiousEvents) only if includeThreat=yes | |||
-- | -- | ||
-- Usage: | -- Usage: | ||
| Line 30: | Line 31: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function trim(s) | local function trim(s) | ||
s = tostring(s or "") | |||
s = tostring(s) | return (s:gsub("^%s+", ""):gsub("%s+$", "")) | ||
end | end | ||
| Line 42: | Line 41: | ||
local function stripWiki(s) | local function stripWiki(s) | ||
s = tostring(s or "") | s = tostring(s or "") | ||
s = s:gsub("%{%b{}%}", "") | s = s:gsub("%{%b{}%}", "") | ||
s = s:gsub("%[%[[^%]|]+|([^%]]+)%]%]", "%1") | s = s:gsub("%[%[[^%]|]+|([^%]]+)%]%]", "%1") | ||
s = s:gsub("%[%[([^%]]+)%]%]", "%1") | s = s:gsub("%[%[([^%]]+)%]%]", "%1") | ||
s = s:gsub("'''''", ""):gsub("'''", ""):gsub("''", "") | s = s:gsub("'''''", ""):gsub("'''", ""):gsub("''", "") | ||
return trim(s) | return trim(s) | ||
end | end | ||
| Line 164: | Line 146: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- 3) | -- 3) STYLE / SEVERITY | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function sevStyle(sev) | local function sevStyle(sev) | ||
| Line 174: | Line 156: | ||
return ' style="background-color:#d4edda;"' | return ' style="background-color:#d4edda;"' | ||
end | end | ||
end | |||
local function urgencyScore(u) | |||
u = tostring(u or ""):upper() | |||
if u == "CRITICAL" then return 4 end | |||
if u == "HIGH" then return 3 end | |||
if u == "MEDIUM" then return 2 end | |||
if u == "LOW" then return 1 end | |||
return 0 | |||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- 4) CITY NETWORK | -- 4) CITY NETWORK | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function cityLink(name) | local function cityLink(name) | ||
return string.format("[[ | return string.format("[[List_of_cities_in_Bassaridia_Vaeringheim#%s|%s]]", name, name) | ||
end | end | ||
local cityOrder = { | local cityOrder = { | ||
-- Core majors | -- Core majors | ||
| Line 263: | Line 253: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- 4.1) CITY FLAGS (existence | -- 4.1) CITY FLAGS (matches GeneralPortOperations naming; NO existence checks) | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local DEFAULT_CITY_FLAG_FILE = "Imperial Trade Union flag.png" | local DEFAULT_CITY_FLAG_FILE = "Imperial Trade Union flag.png" | ||
local function | local function normCityForFlag(name) | ||
-- Matches Module:GeneralPortOperations | |||
return (name or "") | return (name or "") | ||
:gsub("[%s%-’]", "") | :gsub("[%s%-’]", "") | ||
:gsub("[ | :gsub("[Áá]", "A"):gsub("[Éé]", "E") | ||
:gsub("[Íí]", "I"):gsub("[Óó]", "O") | |||
:gsub("[ | :gsub("[Úú]", "U"):gsub("[Ýý]", "Y") | ||
:gsub("[ | |||
end | end | ||
local cityFlagOverrides = { | local cityFlagOverrides = { | ||
-- Same override used in GeneralPortOperations | |||
["Skýrophos"] = "SkyrophosFlag.png", | ["Skýrophos"] = "SkyrophosFlag.png", | ||
-- Known filename oddity (you noted this explicitly) | |||
["Slevik"] = "SlevikFlag.png.png", | ["Slevik"] = "SlevikFlag.png.png", | ||
} | } | ||
local | local cityFlagCache = {} | ||
local function getCityFlagMarkup(cityName) | local function getCityFlagMarkup(cityName) | ||
if | if cityFlagCache[cityName] then return cityFlagCache[cityName] end | ||
local f = cityFlagOverrides[cityName] or (normCityForFlag(cityName) .. "Flag.png") | |||
-- If a city isn't on the list or is a special case, fallback is still safe: | |||
local | if not cityName or cityName == "" then f = DEFAULT_CITY_FLAG_FILE end | ||
local mk = string.format("[[File:%s|20px]]", f) | |||
cityFlagCache[cityName] = mk | |||
local mk = string.format("[[File:%s|20px]]", | |||
return mk | return mk | ||
end | end | ||
| Line 334: | Line 288: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local org = { | local org = { | ||
Lampade | Lampade = { code="L", size=8 }, | ||
Hetairoi | Hetairoi = { code="H", size=12 }, | ||
Dodekade | Dodekade = { code="D", size=18 }, | ||
Kleisthenes= { code="K", size=25 }, | Kleisthenes = { code="K", size=25 }, | ||
Stoa | Stoa = { code="S", size=40 }, | ||
Thiasos | Thiasos = { code="T", size=60 }, | ||
} | } | ||
| Line 353: | Line 307: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- 6) | -- 6) CULT FLAGS (taken from Module:GeneralPortOperations addLogo mappings) | ||
-- NO existence checks. These filenames are the source of truth. | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local | local DEFAULT_CULT_FLAG_FILE = "Imperial Trade Union flag.png" | ||
[" | |||
[" | local cultLogoFile = { | ||
[" | -- From GeneralPortOperations addLogo() lines | ||
[" | ["Order of Aurora Mystica"] = "Auroramystica.png", | ||
[" | ["Harmony Sanctum"] = "Harmonysanctum.png", | ||
[" | ["Celestial Harmony Sect"] = "HarmonySect.png", | ||
["Mystery of the Verdant Embrace"] = " | ["Guild of Golden Shadows"] = "GoldenShadows.png", | ||
["Court of the Ironclad"] | ["Azure Sentinel Sect"] = "AzureSentinel.png", | ||
[" | ["Reverie Nebulous"] = "RevNeb.png", | ||
[" | ["Mystery of the Verdant Embrace"] = "Verdant.png", | ||
[" | ["Conclave Illuminara"] = "Conclave.png", | ||
["Sanctum Vitalis"] = "Vitalis.png", | |||
["Court of the Ironclad"] = "Ironclad.png", | |||
["Sanctum Delphica"] = "SanctumDel.png", | |||
-- Not in ritualCults list, but GeneralPortOperations also adds a logo for Temple of Aprobelle | |||
["Temple Aprobelle"] = "TempleAprobelleLogo.png", | |||
-- Pharmacon Sect is in serviceCompanies; GeneralPortOperations doesn’t map it in the cult list | |||
-- but you likely have a logo file; set it explicitly here: | |||
["Pharmacon Sect"] = "PharmaconSect.png", | |||
} | } | ||
local function | local function getCultFlagMarkup(cultName) | ||
local f = | local f = cultLogoFile[cultName] or DEFAULT_CULT_FLAG_FILE | ||
return string.format("[[File:%s|18px]] ", f) | |||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- 7) ORDERS + TASKS | -- 7) ORDERS + TASKS | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function orderLink(pageTitle, anchor, label) | local function orderLink(pageTitle, anchor, label) | ||
| Line 384: | Line 344: | ||
end | end | ||
local function T(text, sev, tags) | local function T(text, sev, tags) return { text=text, sev=sev, tags=tags or {} } end | ||
end | |||
-- Orders: chosen to match “cult logo” coverage from GeneralPortOperations. | |||
local orders = { | local orders = { | ||
{key=" | {key="AUM", name="Order of Aurora Mystica", | ||
link=orderLink(" | link=orderLink("Eos (Reformed Stripping Path)","Order_of_Aurora_Mystica","Order of Aurora Mystica"), | ||
tasks={ | tasks={ | ||
T("Dawn | T("Dawn reconciliation rite for competing guilds; written pledges sealed with Temple Bank clerks.","green",{"mediation","guild"}), | ||
T(" | T("Sunrise ‘first-light’ counseling circle for strained households; referrals issued through shrine stewards.","green",{"care","mediation"}), | ||
T(" | T("Dawn-watch bulletin: correct false omen claims and stabilize shift work across canal gates.","green",{"omen","public"}), | ||
T(" | T("Crowd choreography support at morning festivals to prevent stampede behavior and pickpocket chaos.","yellow",{"crowd","festival"}), | ||
T(" | T("Targeted rumor-clearing procession after unsigned pamphlets; witness statements taken and archived.","yellow",{"rumor","investigation"}), | ||
T(" | T("Joint patrol with civic stewards through tunnels; lighting faults logged; intimidation incidents recorded.","yellow",{"security","infrastructure"}), | ||
T(" | T("Emergency reconciliation summit after street violence; truce terms read and posted in public squares.","red",{"unrest","mediation"}), | ||
T(" | T("Crisis dawn vigil after night incident; grief support + ‘no retaliation’ pledge drive.","red",{"crisis","grief","public"}), | ||
T("Escort of a vulnerable witness to hearing; route screened for ambush or intimidation.","red",{"security","crisis"}), | |||
T(" | T("Post-incident narrative repair: speaker coaching and public clarity bulletin to prevent reprisals.","yellow",{"public","analysis"}), | ||
T(" | |||
} | } | ||
}, | }, | ||
{key=" | {key="HSA", name="Harmony Sanctum", | ||
link= | link="[[Harmony_Sanctum|Harmony Sanctum]]", | ||
tasks={ | tasks={ | ||
T(" | T("Neighborhood harmony circle: mediated contract dispute between vendors and dock labor overseers.","green",{"mediation","trade"}), | ||
T(" | T("Public etiquette instruction at shrine square; queue discipline and respectful speech enforcement.","green",{"public","order"}), | ||
T(" | T("Household conflict de-escalation visits after complaint surge; written behavioral plan issued.","green",{"care","mediation"}), | ||
T(" | T("Festival crowd-lane planning with stewards to prevent crush risks and rumor cascades.","yellow",{"festival","crowd"}), | ||
T(" | T("Rumor triage: identify source nodes, issue corrective bulletins, and coordinate speaker messaging.","yellow",{"rumor","public"}), | ||
T("Joint calming patrol at transit choke point; separation corridor opened for commuters.","yellow",{"transit","crowd"}), | |||
T(" | T("Emergency stabilization during riot flare: dispersal corridors + visible care stations deployed.","red",{"unrest","crowd"}), | ||
T(" | T("Night calm-litany enforcement after panic episode; coordinated supervised dispersal.","red",{"panic","public"}), | ||
T(" | T("Public apology ceremony after inter-group violence; pledge collection and restitution ledger filed.","yellow",{"mediation","governance"}), | ||
T(" | T("Oversight coaching for district clerks after audit findings; corrective protocol training delivered.","yellow",{"audit","governance"}), | ||
T(" | |||
} | } | ||
}, | }, | ||
{key=" | {key="CHS", name="Celestial Harmony Sect", | ||
link= | link="[[Celestial_Harmony_Sect|Celestial Harmony Sect]]", | ||
tasks={ | tasks={ | ||
T(" | T("Cosmic-order lecture at shrine school: reduce anxiety about star-omens and market rumors.","green",{"education","omen"}), | ||
T(" | T("Guided meditation session for canal workers; stress reduction paired with safety brief.","green",{"care","public"}), | ||
T(" | T("Omen bulletin update: correct a false eclipse claim before it drives hoarding behavior.","yellow",{"rumor","omen"}), | ||
T(" | T("Quiet-zone enforcement near hospitals; controlled chanting to prevent crowd agitation.","yellow",{"health","order"}), | ||
T(" | T("Investigation of counterfeit star-charts being sold to pilgrims; vendor interviews conducted.","yellow",{"investigation","trade"}), | ||
T(" | T("Crisis counseling during mass scare; supervised calm stations established at shrine gates.","red",{"crisis","public"}), | ||
T(" | T("Emergency messaging team embedded with War League stewards to prevent retaliatory panic.","red",{"crisis","rumor"}), | ||
T(" | T("Nighttime stabilization patrol during fog warnings; guidance posted and speakers briefed.","yellow",{"transit","public"}), | ||
T(" | T("Post-event debrief: update approved narratives; speaker scripts distributed to shrines.","yellow",{"analysis","public"}), | ||
T(" | T("Festival harmony protocol review: ensure rites align with civic order requirements.","green",{"festival","audit"}), | ||
} | } | ||
}, | }, | ||
{key=" | {key="GGS", name="Guild of Golden Shadows", | ||
link= | link="[[Guild_of_Golden_Shadows|Guild of Golden Shadows]]", | ||
tasks={ | tasks={ | ||
T(" | T("Quiet intelligence intake: compile rumor sources and identify forged vouchers without public escalation.","green",{"analysis","investigation"}), | ||
T(" | T("Market observation rotation to spot coercion and counterfeit seals; reports filed with Temple Bank.","green",{"audit","trade"}), | ||
T(" | T("Discreet witness protection arrangement for a vendor threatened by smugglers.","yellow",{"security","investigation"}), | ||
T(" | T("Forgery trace: identify printing stall distributing schismatic pamphlets; evidence packet assembled.","yellow",{"investigation","archive"}), | ||
T(" | T("Undercover purchase of fake shrine seals; seize ledgers and hand off to clerks.","yellow",{"trade","security"}), | ||
T(" | T("Night extraction of a compromised informant; safe route and temporary housing arranged.","red",{"crisis","security"}), | ||
T(" | T("Emergency suppression of panic narrative: remove false broadsheets and replace with approved bulletin.","red",{"panic","rumor"}), | ||
T(" | T("Coordination with Temple Aprobelle on narrative discipline during crowd surge.","yellow",{"analysis","public"}), | ||
T(" | T("Intercepted contraband manifest review; cross-check with corridor registry for evasions.","yellow",{"audit","logistics"}), | ||
T(" | T("Post-incident attribution report delivered to War League liaison; next-day prevention plan drafted.","yellow",{"analysis","governance"}), | ||
} | } | ||
}, | }, | ||
{key=" | {key="ASS", name="Azure Sentinel Sect", | ||
link=orderLink(" | link=orderLink("Aether (Reformed Stripping Path)","Azure_Sentinel_Sect","Azure Sentinel Sect"), | ||
tasks={ | tasks={ | ||
T(" | T("Waterfront perimeter walk and safety rope checks along canal districts.","green",{"infrastructure","safety"}), | ||
T(" | T("Storm-drain inspection with civil works; blockages cleared and logged.","green",{"infrastructure","service"}), | ||
T(" | T("Port-house briefing on embarkation discipline and baggage rules; theft prevention guidance posted.","green",{"public","transit"}), | ||
T(" | T("Escort for medical supplies across canal bridges; accountability signatures obtained.","yellow",{"logistics","security"}), | ||
T(" | T("Harbor rumor suppression: correct false ‘quarantine’ claims to prevent stampede and hoarding.","yellow",{"rumor","public"}), | ||
T(" | T("Search-and-rescue standby after storm warnings; muster roster and equipment check.","yellow",{"safety","crisis"}), | ||
T(" | T("Emergency flood response: evacuation guidance at waterfront shrines; calm channels maintained.","red",{"crisis","relief"}), | ||
T(" | T("High-risk escort of officials through crowd at port gate; separation corridor enforced.","red",{"security","crowd"}), | ||
T(" | T("Debris clearance coordination after gale; hazard markings placed on docks.","yellow",{"infrastructure","safety"}), | ||
T(" | T("Crisis harbor closure messaging: distribute official notices; prevent vigilante enforcement.","red",{"crisis","public"}), | ||
} | } | ||
}, | }, | ||
{key=" | {key="RNB", name="Reverie Nebulous", | ||
link=orderLink(" | link=orderLink("Nephele (Reformed Stripping Path)","Reverie_Nebulous","Reverie Nebulous"), | ||
tasks={ | tasks={ | ||
T(" | T("Dream interpretation circle with strict moderation; anxiety de-escalation emphasized.","green",{"care","public"}), | ||
T(" | T("Quiet art therapy session for children after a scare; parent coaching provided.","green",{"care","education"}), | ||
T(" | T("Public ‘sleep hygiene’ teaching for shift workers; rest scheduling guidance posted.","green",{"health","education"}), | ||
T(" | T("Rumor mapping: identify which dreams are feeding a harmful narrative; corrective story deployed.","yellow",{"rumor","analysis"}), | ||
T(" | T("Witness interviews after trance-gathering complaint; compliance conditions issued.","yellow",{"audit","public"}), | ||
T(" | T("Nighttime crowd calming near a transit node; chants regulated to prevent stampede behavior.","yellow",{"crowd","transit"}), | ||
T(" | T("Containment support for mass panic dream episode; calm stations and supervised rest zones established.","red",{"crisis","crowd"}), | ||
T(" | T("Emergency counseling for families after death; grief rites paired with aid referrals.","red",{"grief","crisis"}), | ||
T(" | T("High-risk ‘dream breach’ report assessed with Aprobelle liaisons; misinformation suppressed.","red",{"crisis","rumor"}), | ||
T("Post- | T("Post-event debrief: public narrative edited to reduce retaliatory impulses; speaker training conducted.","yellow",{"public","analysis"}), | ||
} | } | ||
}, | }, | ||
{key=" | {key="VDE", name="Mystery of the Verdant Embrace", | ||
link=orderLink("Thalassa (Reformed Stripping Path)","Mystery_of_the_Verdant_Embrace","Mystery of the Verdant Embrace"), | link=orderLink("Thalassa (Reformed Stripping Path)","Mystery_of_the_Verdant_Embrace","Mystery of the Verdant Embrace"), | ||
tasks={ | tasks={ | ||
T("Irrigation ritual + practical canal cleanup; silt removed and flow restored | T("Irrigation ritual + practical canal cleanup; silt removed and flow restored.","green",{"infrastructure","agriculture"}), | ||
T("Seed blessing for market gardeners; pest-prevention guidance distributed.","green",{"agriculture","public"}), | T("Seed blessing for market gardeners; pest-prevention guidance distributed.","green",{"agriculture","public"}), | ||
T("Wetland path maintenance; travel warnings posted to prevent drownings.","green",{"safety","infrastructure"}), | T("Wetland path maintenance; travel warnings posted to prevent drownings.","green",{"safety","infrastructure"}), | ||
T("Waterbank audit at an aqueduct; leakage reported and repair crews coordinated.","yellow",{"audit","infrastructure"}), | T("Waterbank audit at an aqueduct; leakage reported and repair crews coordinated.","yellow",{"audit","infrastructure"}), | ||
T("Field mediation over irrigation rights; shared schedule drafted and signed.","yellow",{"mediation","governance"}), | T("Field mediation over irrigation rights; shared schedule drafted and signed.","yellow",{"mediation","governance"}), | ||
T("Brushfire risk patrol in dry districts; controlled burns prohibited and notices issued.","yellow",{"fire","public"}), | T("Brushfire risk patrol in dry districts; controlled burns prohibited and notices issued.","yellow",{"fire","public"}), | ||
T("Disease vector mitigation in swamp edges; standing water treated and guidance posted.","yellow",{"health","infrastructure"}), | |||
T("Post-storm debris clearance with shrine volunteers; hazard markings placed.","yellow",{"safety","infrastructure"}), | T("Post-storm debris clearance with shrine volunteers; hazard markings placed.","yellow",{"safety","infrastructure"}), | ||
T(" | T("Emergency flood relief coordination: sandbag lines, food staging, and shelter support.","red",{"crisis","relief"}), | ||
T("Crisis canal breach response: temporary closure, reroute | T("Crisis canal breach response: temporary closure, water reroute, and crowd calming.","red",{"crisis","infrastructure","crowd"}), | ||
} | |||
}, | |||
{key="CIL", name="Conclave Illuminara", | |||
link="[[Conclave_Illuminara|Conclave Illuminara]]", | |||
tasks={ | |||
T("Night-sky lecture and calendar correction posted to calm omen anxiety.","green",{"education","omen"}), | |||
T("Quiet meditation ward established at a shrine; controlled breathing protocols taught.","green",{"care","public"}), | |||
T("Lantern-route inspection for canal patrols; reflective markers placed to reduce accidents.","green",{"infrastructure","transit"}), | |||
T("Dream-space audit: mirror pools cleaned, offerings catalogued, safety signage posted.","green",{"shrine","audit"}), | |||
T("Investigation of counterfeit moon tokens; vendor interviews and seizure log.","yellow",{"investigation","trade"}), | |||
T("Controlled night gathering after panic rumor; calm-litany enforced; dispersal plan coordinated.","yellow",{"rumor","crowd"}), | |||
T("Nighttime crowd calming near transit node; chants regulated to prevent stampede behavior.","yellow",{"crowd","transit"}), | |||
T("Emergency anti-panic messaging after ‘demonic sign’ claim; script distributed to shrine speakers.","red",{"crisis","rumor"}), | |||
T("Escort for sensitive nocturnal rite during unrest; perimeter observers posted.","red",{"security","unrest"}), | |||
T("Blackout safety drill during storm season (lanterns, guides, canal warning ropes).","yellow",{"infrastructure","safety"}), | |||
} | } | ||
}, | }, | ||
{key=" | {key="SVI", name="Sanctum Vitalis", | ||
link= | link="[[Sanctum_Vitalis|Sanctum Vitalis]]", | ||
tasks={ | tasks={ | ||
T(" | T("Clinic resupply run: inventory reconciled with Temple Bank vouchers.","green",{"health","logistics"}), | ||
T(" | T("Water-quality checks at canal taps; purification guidance posted.","green",{"health","infrastructure"}), | ||
T(" | T("School hygiene lecture paired with modest blessing service.","green",{"health","education"}), | ||
T(" | T("Routine fever screening at market gate; clinic referral slips issued.","green",{"health","public"}), | ||
T(" | T("Temporary clinic tent near shrine square; triage lines marked and staffed.","yellow",{"health","crowd"}), | ||
T(" | T("Contamination rumor assessment: test kits deployed to prevent panic.","yellow",{"health","rumor"}), | ||
T(" | T("Heat illness prevention training and hydration discipline coaching.","yellow",{"health","public"}), | ||
T(" | T("Mass casualty support after accident; triage, referral, and grief counseling coordination.","red",{"crisis","health"}), | ||
T(" | T("Quarantine support in dormitory ward; supplies custody and compliance checks.","red",{"crisis","health","audit"}), | ||
T(" | T("Night clinic staffing for unrest injuries; neutral triage posture announced.","red",{"unrest","health"}), | ||
} | } | ||
}, | }, | ||
{key=" | {key="SDE", name="Sanctum Delphica", | ||
link= | link="[[Sanctum_Delphica|Sanctum Delphica]]", | ||
tasks={ | tasks={ | ||
T(" | T("Archive custody inspection: seals verified; storage conditions checked; ledger updated.","green",{"archive","audit"}), | ||
T(" | T("Scribe training on custody chain; convoy paperwork rehearsed for shrine transfers.","green",{"education","logistics"}), | ||
T(" | T("Pilgrim etiquette workshop at library shrine; damage prevention and quiet-zone enforcement.","green",{"public","education"}), | ||
T(" | T("Index confiscated pamphlets for signature motifs; forward packet for correlation.","yellow",{"investigation","archive"}), | ||
T("Witness interviews after | T("Witness interviews after shrine vandalism; sworn statements taken and sealed.","yellow",{"investigation","security"}), | ||
T(" | T("Doctrinal quarantine assistance: entry controls and rumor suppression around restricted ward.","yellow",{"security","rumor"}), | ||
T(" | T("Court packet assembly for desecration suspects; evidence collated with clerks.","yellow",{"governance","audit"}), | ||
T(" | T("Night watch rotation at archive stacks following arson threat.","red",{"security","unrest"}), | ||
T(" | T("Escort a sensitive artifact transfer; route denial markers and perimeter rites deployed.","red",{"security","logistics"}), | ||
T(" | T("Emergency custody lockdown after attempted breach; rapid relocation plan activated.","red",{"crisis","security","archive"}), | ||
} | } | ||
}, | }, | ||
{key=" | {key="PHA", name="Pharmacon Sect", | ||
link= | link="[[Pharmacon_Sect|Pharmacon Sect]]", | ||
tasks={ | tasks={ | ||
T(" | T("Dispensary audit: dosage seals verified; controlled compounds logged and countersigned.","green",{"health","audit"}), | ||
T(" | T("Field compounding session with herb vendors; purity protocols enforced.","green",{"health","industry"}), | ||
T(" | T("Clinic support for chronic cases; treatment plans documented in Temple Bank ledgers.","green",{"health","service"}), | ||
T(" | T("Water purification consult at canal gate; intake screens inspected for sabotage.","yellow",{"infrastructure","investigation"}), | ||
T(" | T("Extraction team dispatched to secure a medically useful mineral/resin source under escort.","yellow",{"logistics","security"}), | ||
T(" | T("Rumor suppression briefing about ‘cursed medicine’; evidence packet and calm script issued.","yellow",{"rumor","public"}), | ||
T(" | T("Emergency antidote preparation after suspected poisoning; vendor supply chain traced.","red",{"crisis","investigation","health"}), | ||
T(" | T("Outbreak response: isolation guidance and clinic routing control at transit nodes.","red",{"crisis","health","public"}), | ||
T(" | T("Hospital reinforcement for Eidolan exposure aftershock cases; monitoring schedule created.","red",{"crisis","health"}), | ||
T(" | T("Post-incident debrief with Temple Aprobelle on panic dynamics; clinic scripts updated.","yellow",{"analysis","rumor"}), | ||
} | } | ||
}, | }, | ||
{key="TAP", name="Temple Aprobelle", | {key="TAP", name="Temple Aprobelle", | ||
link="[[Temple_Aprobelle|Temple Aprobelle]]", | link="[[Temple_Aprobelle|Temple Aprobelle]]", | ||
tasks={ | tasks={ | ||
T("Festival hygiene audit: vendor protocols verified | T("Festival hygiene audit: vendor protocols verified; seals issued to compliant stalls.","green",{"health","audit","festival","public"}), | ||
T("Rumor-control bulletin cycle: shrine-leader briefing + posted clarifications | T("Rumor-control bulletin cycle: shrine-leader briefing + posted clarifications.","green",{"rumor","public","service"}), | ||
T("Grief-cadre home visits after | T("Grief-cadre home visits after accident; de-escalation coaching for relatives and stewards.","green",{"grief","mediation","service"}), | ||
T("School immunization coordination: clinic schedule posted | T("School immunization coordination: clinic schedule posted and consent ledgers reconciled.","green",{"health","school","coordination"}), | ||
T("Clinic-queue stabilization drill: entry metering | T("Clinic-queue stabilization drill: entry metering and conflict prevention training.","yellow",{"queue","crowd","health","order"}), | ||
T("Loyalty and behavior audit of | T("Loyalty and behavior audit of district office after complaint surge; corrective training issued.","yellow",{"audit","governance","compliance"}), | ||
T("Anomaly-response dispatch | T("Anomaly-response dispatch after ‘demonic contamination’ claims; care paired with narrative discipline.","yellow",{"anomaly","rumor","public","health"}), | ||
T("Pattern-analysis intake: confiscated schism texts | T("Pattern-analysis intake: confiscated schism texts logged and forwarded for signature correlation.","yellow",{"analysis","schism","security","archive"}), | ||
T("Transit node stabilization after crowd surge: calm stations | T("Transit node stabilization after crowd surge: calm stations and signage; steward handoffs established.","yellow",{"transit","crowd","stabilization"}), | ||
T("Silencing-cadre activated after panic flare; controlled messaging + visible care stations deployed | T("Silencing-cadre activated after panic flare; controlled messaging + visible care stations deployed.","red",{"panic","rumor","stabilization"}), | ||
T("Emergency stabilization in evacuation/fever center: calming rites, grief triage, | T("Emergency stabilization in evacuation/fever center: calming rites, grief triage, rumor suppression.","red",{"crisis","health","crowd","stabilization"}), | ||
} | } | ||
}, | }, | ||
| Line 575: | Line 546: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- 8) CITY PROFILES ( | -- 8) CITY PROFILES (bias order selection) | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local cityProfiles = { | local cityProfiles = { | ||
["Vaeringheim"] = { | ["Vaeringheim"] = {weights={AUM=2,HSA=2,CHS=1,GGS=1,ASS=2,RNB=1,VDE=2,CIL=2,SVI=2,SDE=2,PHA=1,TAP=3}}, | ||
["Somniumpolis"] = { | ["Somniumpolis"] = {weights={RNB=3,SVI=2,PHA=2,TAP=3,GGS=2,ASS=1}}, | ||
["Luminaria"] = { | ["Luminaria"] = {weights={AUM=3,CIL=2,HSA=2,TAP=2,SVI=1}}, | ||
["Lunalis Sancta"] = { | ["Lunalis Sancta"] = {weights={SDE=3,CIL=2,TAP=2,HSA=1}}, | ||
["Delphica"] = { | ["Delphica"] = {weights={SDE=3,TAP=2,CIL=1,SVI=1}}, | ||
["Thermosalem"] = {weights={SVI=2,PHA=2,TAP=2,ASS=2,HSA=1}}, | |||
["Thermosalem"] = { | ["Pyralis"] = {weights={SVI=2,TAP=2,ASS=2,AUM=2,HSA=1}}, | ||
["Pyralis"] = { | ["Keybir-Aviv"] = {weights={ASS=3,TAP=2,GGS=2,HSA=1,SVI=1}}, | ||
["Keybir-Aviv"] = { | ["Lewisburg"] = {weights={ASS=2,GGS=2,TAP=2,HSA=1,SVI=1}}, | ||
[" | ["Diamandis"] = {weights={TAP=2,SVI=2,AUM=1,RNB=1,HSA=1}}, | ||
["Diamandis"] = { | ["Tonar"] = {weights={SDE=2,SVI=2,VDE=2,TAP=1,ASS=1}}, | ||
["Bashkim"] = {weights={VDE=3,SVI=2,TAP=1,HSA=1}}, | |||
["Ourid"] = {weights={VDE=3,SVI=2,TAP=1}}, | |||
["Tonar"] = { | ["Ardclach"] = {weights={ASS=2,SVI=2,TAP=1,CIL=1}}, | ||
["Bashkim"] = { | ["Riddersborg"] = {weights={ASS=2,HSA=2,TAP=1,SVI=1}}, | ||
["Ourid"] = { | |||
["Ardclach"] = { | |||
["Riddersborg"] = { | |||
} | } | ||
local function getProfile(cityName) | local function getProfile(cityName) | ||
return cityProfiles[cityName] or {weights={AUM=1,HSA=1,ASS=1,SVI=1,SDE=1,TAP=1}} | |||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- 9) OPTIONAL: ECON SIGNALS ( | -- 9) OPTIONAL: ECON SIGNALS (EXPENSIVE; only run when showEcon=yes) | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function getEconSignals() | local function getEconSignals() | ||
local title = mw.title.new("General Port of Lake Morovia") | local title = mw.title.new("General Port of Lake Morovia") | ||
if not title then return {} end | if not title then return {} end | ||
local content = | local ok, content = pcall(function() return title:getContent() end) | ||
if not content or content == "" then return {} end | if not ok or not content or content == "" then return {} end | ||
local signals = {} | local signals = {} | ||
local text = normalizeSpaces(content) | local text = normalizeSpaces(content) | ||
| Line 621: | Line 585: | ||
local n = c.name | local n = c.name | ||
local hits = 0 | local hits = 0 | ||
local pattern = n:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])","%%%1") | |||
for _ in text:gmatch(pattern) do | |||
hits = hits + 1 | hits = hits + 1 | ||
if hits >= 6 then break end | if hits >= 6 then break end | ||
end | end | ||
if hits >= 3 then | if hits >= 3 then | ||
signals[n] = {summary=string.format("High Port linkage (mentions: %d)", hits) | signals[n] = {summary=string.format("High Port linkage (mentions: %d)", hits)} | ||
end | end | ||
end | end | ||
return signals | return signals | ||
end | end | ||
| Line 647: | Line 606: | ||
if line:match("^|%s") and not line:match("^|%-%s*$") then | if line:match("^|%s") and not line:match("^|%-%s*$") then | ||
local parts = mw.text.split(line:gsub("^|%s*", ""), "%s*||%s*") | local parts = mw.text.split(line:gsub("^|%s*", ""), "%s*||%s*") | ||
if #parts >= 4 then | if #parts >= 4 then | ||
local city = stripWiki(parts[2]) | local city = stripWiki(parts[2]) | ||
| Line 659: | Line 617: | ||
end | end | ||
return map | return map | ||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- 11) WX ( | -- 11) WX OPS ALERT INTEGRATION (Module:BassaridiaForecast) | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local wxOk, wx = pcall(require, "Module:BassaridiaForecast") | local wxOk, wx = pcall(require, "Module:BassaridiaForecast") | ||
| Line 706: | Line 655: | ||
local imp = op.impacts or {} | local imp = op.impacts or {} | ||
if lvl == 2 and sev == "green" then sev = "yellow" end | if lvl == 2 and sev == "green" then sev = "yellow" end | ||
if lvl >= 3 then sev = "red" end | if lvl >= 3 then sev = "red" end | ||
if (imp.ground == "Hazard" or imp.sea == "Closed" or imp.air == "No-Go") and sev == "green" then | if (imp.ground == "Hazard" or imp.sea == "Closed" or imp.air == "No-Go") and sev == "green" then | ||
sev = "yellow" | sev = "yellow" | ||
| Line 720: | Line 667: | ||
if not op or not op.alert then return "" end | if not op or not op.alert then return "" end | ||
local lvl = tonumber(op.alert.level) or 0 | local lvl = tonumber(op.alert.level) or 0 | ||
if lvl < 2 then return "" end -- only Orange/Red | if lvl < 2 then return "" end -- only Orange/Red append | ||
local haz = tostring(op.alert.hazard or "weather disruption") | local haz = tostring(op.alert.hazard or "weather disruption") | ||
| Line 730: | Line 677: | ||
if cond == "Storm" or cond == "Rain" then | if cond == "Storm" or cond == "Rain" then | ||
return base .. " | return base .. " Clinic/relief queues stabilized; rumor bulletins posted; shrine-district lanes marked." | ||
elseif cond == "Blizzard" or cond == "Snow" then | elseif cond == "Blizzard" or cond == "Snow" then | ||
return base .. " Warming-shelter checks; fuel | return base .. " Warming-shelter checks; fuel/medicine custody verified; travel guidance posted." | ||
elseif cond == "Fog" then | elseif cond == "Fog" then | ||
return base .. " Navigation warnings and crowd guidance in canal | return base .. " Navigation warnings and crowd guidance in port/canal corridors; panic narratives suppressed." | ||
elseif cond == "Dust" then | elseif cond == "Dust" then | ||
return base .. " Mask discipline | return base .. " Mask discipline and vendor hygiene enforcement prioritized; indoor refuge advisories posted." | ||
else | else | ||
return base .. " Welfare checks and public messaging prioritized." | return base .. " Welfare checks and public messaging prioritized." | ||
| Line 743: | Line 690: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- 12) | -- 12) ORDER/TASK CHOICE | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function inferSeverity(cityTier, taskSev, threatUrg) | local function inferSeverity(cityTier, taskSev, threatUrg) | ||
local sev = tostring(taskSev or "green") | local sev = tostring(taskSev or "green") | ||
if cityTier == "major" and sev == "green" then sev = "yellow" end | if cityTier == "major" and sev == "green" then sev = "yellow" end | ||
if urgencyScore(threatUrg) >= 3 then | |||
if urgencyScore(threatUrg) >= 3 then sev = "red" | sev = "red" | ||
elseif urgencyScore(threatUrg) == 2 and sev == "green" then sev = "yellow" end | elseif urgencyScore(threatUrg) == 2 and sev == "green" then | ||
sev = "yellow" | |||
end | |||
return sev | return sev | ||
end | end | ||
local function pickOrder(nextInt, | local function pickOrder(nextInt, cityName, threat) | ||
local profile = getProfile(cityName) | |||
local weighted = {} | local weighted = {} | ||
for key, w in pairs(profile.weights or {}) do | for key, w in pairs(profile.weights or {}) do | ||
weighted[#weighted+1] = {key=key, w=tonumber(w) or 0} | weighted[#weighted+1] = {key=key, w=tonumber(w) or 0} | ||
end | end | ||
-- threat bump: prefer Aprobelle / health / security / shadows | |||
-- threat bump: prefer Aprobelle / security / | |||
if threat and urgencyScore(threat.urg) >= 2 then | if threat and urgencyScore(threat.urg) >= 2 then | ||
for _, it in ipairs(weighted) do | for _, it in ipairs(weighted) do | ||
if it.key == "TAP" or it.key == " | if it.key == "TAP" or it.key == "SVI" or it.key == "PHA" or it.key == "ASS" or it.key == "GGS" then | ||
it.w = it.w + 2 | |||
end | |||
end | end | ||
end | end | ||
local picked = pickWeighted(nextInt, weighted) | local picked = pickWeighted(nextInt, weighted).key | ||
for _, o in ipairs(orders) do | for _, o in ipairs(orders) do | ||
if o.key == picked | if o.key == picked then return o end | ||
end | end | ||
return orders[1] | return orders[1] | ||
| Line 785: | Line 727: | ||
local function pickTask(nextInt, o, sev) | local function pickTask(nextInt, o, sev) | ||
local pool = {} | local pool = {} | ||
for _, t in ipairs(o.tasks or {}) do | for _, t in ipairs(o.tasks or {}) do | ||
| Line 794: | Line 735: | ||
pool[#pool+1] = {t=t, w=w} | pool[#pool+1] = {t=t, w=w} | ||
end | end | ||
return pickWeighted(nextInt, pool).t | |||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- 13) | -- 13) BANK NOTES | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local bankNotes = { | local bankNotes = { | ||
| Line 805: | Line 745: | ||
"Voucher reconciliation complete; supplies logged and stored under dual-key protocol.", | "Voucher reconciliation complete; supplies logged and stored under dual-key protocol.", | ||
"Observer note: crowd discipline improved after posted guidance and steward coaching.", | "Observer note: crowd discipline improved after posted guidance and steward coaching.", | ||
"Repair authorization issued | "Repair authorization issued; labor scheduled at off-peak hours.", | ||
"Custody ledger updated; artifacts | "Custody ledger updated; medicines/artifacts sealed and moved to secure storage.", | ||
"Training memo filed; corrective instruction issued to volunteers for queue discipline.", | "Training memo filed; corrective instruction issued to volunteers for queue discipline.", | ||
} | } | ||
| Line 813: | Line 753: | ||
-- 14) BUILD ONE CITY ROW | -- 14) BUILD ONE CITY ROW | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function buildCityRow(di, c | local function buildCityRow(di, c, threatMap, includeThreat, showEcon, econSignals) | ||
local cityName = c.name | local cityName = c.name | ||
local seed = hash31(string.format("mops:%d:%03d:%s", di.year, di.dayOfYear, cityName)) | local seed = hash31(string.format("mops:%d:%03d:%s", di.year, di.dayOfYear, cityName)) | ||
local nextInt = lcg(seed) | local nextInt = lcg(seed) | ||
local th = includeThreat and threatMap and threatMap[cityName] or nil | local th = includeThreat and threatMap and threatMap[cityName] or nil | ||
local o = pickOrder(nextInt, cityName, th) | |||
-- | -- task + severity | ||
local | local baseTask = pickTask(nextInt, o, "yellow") | ||
local threatUrg = th and th.urg or "" | local threatUrg = th and th.urg or "" | ||
local sev = inferSeverity(tier, | local sev = inferSeverity(c.tier, baseTask.sev, threatUrg) | ||
-- | -- wx overlay | ||
local opsBadge, wxData = opsCell(cityName, di.year, di.dayOfYear) | local opsBadge, wxData = opsCell(cityName, di.year, di.dayOfYear) | ||
sev = bumpSevWeather(sev, wxData) | sev = bumpSevWeather(sev, wxData) | ||
local taskText = | local taskText = baseTask.text .. weatherTaskOverlay(wxData) | ||
-- unit | |||
local unitName = pickOrgUnit(nextInt, sev) | local unitName = pickOrgUnit(nextInt, sev) | ||
local unitMeta = org[unitName] | local unitMeta = org[unitName] | ||
| Line 842: | Line 777: | ||
local unitCode = string.format("%s-%s%d", o.key, unitMeta.code, unitIndex) | local unitCode = string.format("%s-%s%d", o.key, unitMeta.code, unitIndex) | ||
-- bank note | |||
local bank = pickFromList(nextInt, bankNotes, nil) | local bank = pickFromList(nextInt, bankNotes, nil) | ||
-- econ | |||
local econ = "" | |||
if showEcon and econSignals and econSignals[cityName] then econ = econSignals[cityName].summary or "" end | |||
local | |||
if | |||
return { | return { | ||
style = sevStyle(sev), | style = sevStyle(sev), | ||
date = di.text, | date = di.text, | ||
city = string.format("%s %s", getCityFlagMarkup(cityName), cityLink(cityName)), | city = string.format("%s %s", getCityFlagMarkup(cityName), cityLink(cityName)), | ||
ops = opsBadge, | region = c.region, | ||
order = | ops = opsBadge, | ||
order = getCultFlagMarkup(o.name) .. o.link, | |||
unit = string.format("%s (%d) [%s]", unitName, unitMeta.size, unitCode), | unit = string.format("%s (%d) [%s]", unitName, unitMeta.size, unitCode), | ||
task = taskText, | task = taskText, | ||
bank = bank, | bank = bank, | ||
threat = | threat = (includeThreat and th and th.event) and th.event or "", | ||
threatUrg = | threatUrg = (includeThreat and th and th.urg) and th.urg or "", | ||
sev = sev, | sev = sev, | ||
econ = econ, | |||
} | } | ||
end | end | ||
| Line 882: | Line 813: | ||
local di = getDateInfo() | local di = getDateInfo() | ||
-- Threat map | |||
local threatMap = {} | local threatMap = {} | ||
if includeThreat then | if includeThreat then | ||
| Line 890: | Line 821: | ||
threatMap = parseReligiousEventsTable(rel.makeTable()) | threatMap = parseReligiousEventsTable(rel.makeTable()) | ||
end | end | ||
end | |||
-- Econ signals (expensive; only when showEcon=yes) | |||
local econSignals = {} | |||
if showEcon then | |||
econSignals = getEconSignals() | |||
end | end | ||
local out = {} | local out = {} | ||
table.insert(out, "{| class='wikitable sortable'") | table.insert(out, "{| class='wikitable sortable'") | ||
local header = "! style='white-space:nowrap;' | Date !! City !! Region !! Ops Alert !! Cult Order !! Unit !! Tasking" | local header = "! style='white-space:nowrap;' | Date !! City !! Region !! Ops Alert !! Cult Order !! Unit !! Tasking" | ||
if showEcon then header = header .. " !! Economic signal" end | if showEcon then header = header .. " !! Economic signal" end | ||
| Line 905: | Line 841: | ||
for _, c in ipairs(cityOrder) do | for _, c in ipairs(cityOrder) do | ||
if regionPass(regionFilter, c.region) then | if regionPass(regionFilter, c.region) then | ||
local row = buildCityRow(di, c | local row = buildCityRow(di, c, threatMap, includeThreat, showEcon, econSignals) | ||
table.insert(out, "|-" .. row.style) | table.insert(out, "|-" .. row.style) | ||
| Line 911: | Line 847: | ||
row.date, row.city, row.region, row.ops, row.order, row.unit, row.task | row.date, row.city, row.region, row.ops, row.order, row.unit, row.task | ||
) | ) | ||
if showEcon then | if showEcon then line = line .. " || " .. (row.econ ~= "" and row.econ or "n/a") end | ||
line = line .. " || " .. row.bank | line = line .. " || " .. row.bank | ||
if includeThreat then | if includeThreat then | ||
| Line 935: | Line 869: | ||
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:#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, "<span style='background:#f8d7da;padding:2px 6px;border:1px solid #f5c6cb;'>Red</span> = Crisis response / unrest / outbreak / high-risk operations") | ||
table.insert(out, "<br/>Ops Alert badge reflects forecast-driven operational risk | table.insert(out, "<br/>Ops Alert badge reflects forecast-driven operational risk and aligns with War League / Hatch posture.") | ||
table.insert(out, "<br/>For overseas deployments please see [[Bassaridian_involvement_in_Corum#Corumian_Missionary_Deployment_Reports|Corumian Missionary Deployment Reports]].") | |||
table.insert(out, "</div>") | table.insert(out, "</div>") | ||
end | end | ||
| Line 959: | Line 894: | ||
local di = getDateInfo() | local di = getDateInfo() | ||
local threatMap = {} | local threatMap = {} | ||
| Line 968: | Line 902: | ||
end | end | ||
end | end | ||
local econSignals = {} | |||
if showEcon then econSignals = getEconSignals() end | |||
local region, tier = "Core", "minor" | local region, tier = "Core", "minor" | ||
| Line 974: | Line 911: | ||
end | end | ||
local row = buildCityRow(di, {name=city, region=region, tier=tier} | local row = buildCityRow(di, {name=city, region=region, tier=tier}, threatMap, includeThreat, showEcon, econSignals) | ||
local out = {} | local out = {} | ||
| Line 997: | Line 934: | ||
line = line .. " || " .. row.sev | line = line .. " || " .. row.sev | ||
table.insert(out, line) | table.insert(out, line) | ||
table.insert(out, "|}") | table.insert(out, "|}") | ||
return table.concat(out, "\n") | return table.concat(out, "\n") | ||
| Line 1,019: | Line 957: | ||
local di = getDateInfo() | local di = getDateInfo() | ||
local threatMap = {} | local threatMap = {} | ||
| Line 1,028: | Line 965: | ||
end | end | ||
end | end | ||
local econSignals = {} | |||
if showEcon then econSignals = getEconSignals() end | |||
local out = {} | local out = {} | ||
| Line 1,039: | Line 979: | ||
for _, c in ipairs(cityOrder) do | for _, c in ipairs(cityOrder) do | ||
local row = buildCityRow(di, c | local row = buildCityRow(di, c, threatMap, includeThreat, showEcon, econSignals) | ||
if row.order:find(wantedOrder.link, 1, true) or row.order:find(wantedOrder.name, 1, true) then | if row.order:find(wantedOrder.link, 1, true) or row.order:find(wantedOrder.name, 1, true) then | ||
table.insert(out, "|-" .. row.style) | table.insert(out, "|-" .. row.style) | ||
| Line 1,046: | Line 985: | ||
row.date, row.city, row.region, row.ops, row.unit, row.task | row.date, row.city, row.region, row.ops, row.unit, row.task | ||
) | ) | ||
if showEcon then | if showEcon then line = line .. " || " .. (row.econ ~= "" and row.econ or "n/a") end | ||
line = line .. " || " .. row.bank | line = line .. " || " .. row.bank | ||
if includeThreat then | if includeThreat then | ||
Revision as of 02:27, 2 January 2026
Documentation for this module may be created at Module:MissionaryDailyOpsBVR/doc
-- Module:MissionaryDailyOpsBVR
-- Daily missionary tasking ledger (Temple Bank / RSP).
--
-- WHAT’S FIXED / UPDATED (per your latest notes):
-- • Correct CITY flags and CULT flags:
-- - City flag filename logic + Skýrophos override matches Module:GeneralPortOperations
-- - Cult flag/logo filenames are taken directly from Module:GeneralPortOperations addLogo() mappings
-- - No file-existence checks (avoids “too many expensive function calls”)
-- • Ops Alert integration (Module:BassaridiaForecast):
-- - Adds Ops Alert column (badge + condition)
-- - Orange/Red bumps severity + appends a short weather-driven overlay (Orange/Red only)
-- • Includes Temple Aprobelle with dedicated task pool (10+ tasks)
-- • Optional econ scan (EXPENSIVE) ONLY runs if showEcon=yes
-- • Optional Threat Watch (Module:ReligiousEvents) only if includeThreat=yes
--
-- Usage:
-- {{#invoke:MissionaryDailyOpsBVR|makeTable}}
-- {{#invoke:MissionaryDailyOpsBVR|makeTable|region=Core}}
-- {{#invoke:MissionaryDailyOpsBVR|makeTable|includeThreat=yes}}
-- {{#invoke:MissionaryDailyOpsBVR|makeTable|showEcon=yes}}
-- {{#invoke:MissionaryDailyOpsBVR|makeCity|city=Vaeringheim}}
-- {{#invoke:MissionaryDailyOpsBVR|makeOrder|order=Temple Aprobelle}}
local p = {}
local cal = require('Module:BassaridianCalendar')
local DAYS_IN_YEAR = 183
--------------------------------------------------------------------------------
-- 0) SMALL UTILS
--------------------------------------------------------------------------------
local function trim(s)
s = tostring(s or "")
return (s:gsub("^%s+", ""):gsub("%s+$", ""))
end
local function normalizeSpaces(s)
return (s or ""):gsub("[%s\u00A0]+", " ")
end
local function stripWiki(s)
s = tostring(s or "")
s = s:gsub("%{%b{}%}", "")
s = s:gsub("%[%[[^%]|]+|([^%]]+)%]%]", "%1")
s = s:gsub("%[%[([^%]]+)%]%]", "%1")
s = s:gsub("'''''", ""):gsub("'''", ""):gsub("''", "")
return trim(s)
end
--------------------------------------------------------------------------------
-- 1) CALENDAR PARSING
--------------------------------------------------------------------------------
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
--------------------------------------------------------------------------------
-- 2) DETERMINISTIC RNG
--------------------------------------------------------------------------------
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 = 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 not list or #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
local function pickWeighted(nextInt, weighted)
local total = 0
for _, it in ipairs(weighted) do total = total + (it.w or 0) end
if total <= 0 then return weighted[1] end
local r = nextInt() % total
local acc = 0
for _, it in ipairs(weighted) do
acc = acc + (it.w or 0)
if r < acc then return it end
end
return weighted[#weighted]
end
--------------------------------------------------------------------------------
-- 3) STYLE / SEVERITY
--------------------------------------------------------------------------------
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 urgencyScore(u)
u = tostring(u or ""):upper()
if u == "CRITICAL" then return 4 end
if u == "HIGH" then return 3 end
if u == "MEDIUM" then return 2 end
if u == "LOW" then return 1 end
return 0
end
--------------------------------------------------------------------------------
-- 4) CITY NETWORK
--------------------------------------------------------------------------------
local function cityLink(name)
return string.format("[[List_of_cities_in_Bassaridia_Vaeringheim#%s|%s]]", name, name)
end
local cityOrder = {
-- Core majors
{name="Vaeringheim", region="Core", tier="major"},
{name="Luminaria", region="Core", tier="major"},
{name="Serena", region="Core", tier="major"},
{name="Pyralis", region="Core", tier="major"},
{name="Symphonara", region="Core", tier="major"},
{name="Aurelia", region="Core", tier="major"},
{name="Somniumpolis", region="Core", tier="major"},
{name="Nexa", region="Core", tier="major"},
{name="Lunalis Sancta", region="Core", tier="major"},
{name="Sylvapolis", region="Core", tier="major"},
-- Core minors
{name="Saluria", region="Core", tier="minor"},
{name="Aetherium", region="Core", tier="minor"},
{name="Ferrum Citadel", region="Core", tier="minor"},
{name="Acheron", region="Core", tier="minor"},
{name="Erythros", region="Core", tier="minor"},
{name="Catonis Atrium", region="Core", tier="minor"},
{name="Delphica", region="Core", tier="minor"},
{name="Koinonía", region="Core", tier="minor"},
{name="Aureum", region="Core", tier="minor"},
-- Alperkin
{name="The Alpazkigz", region="Alperkin", tier="minor"},
{name="Odiferia", region="Alperkin", tier="minor"},
-- New South Jangsong
{name="Skýrophos", region="New South Jangsong", tier="major"},
{name="Bjornopolis", region="New South Jangsong", tier="major"},
{name="Aegirheim", region="New South Jangsong", tier="major"},
{name="Norsolyra", region="New South Jangsong", tier="major"},
{name="Thorsalon", region="New South Jangsong", tier="major"},
{name="Pelagia", region="New South Jangsong", tier="minor"},
{name="Myrene", region="New South Jangsong", tier="minor"},
{name="Thyrea", region="New South Jangsong", tier="minor"},
{name="Ephyra", region="New South Jangsong", tier="minor"},
{name="Halicarn", region="New South Jangsong", tier="minor"},
-- Haifan Bassaridia
{name="Keybir-Aviv", region="Haifan Bassaridia", tier="major"},
{name="Tel-Amin", region="Haifan Bassaridia", tier="major"},
{name="Diamandis", region="Haifan Bassaridia", tier="major"},
{name="Jogi", region="Haifan Bassaridia", tier="major"},
{name="Lewisburg", region="Haifan Bassaridia", tier="major"},
{name="Thermosalem", region="Haifan Bassaridia", tier="minor"},
{name="Akróstadium", region="Haifan Bassaridia", tier="minor"},
{name="Sufriya", region="Haifan Bassaridia", tier="minor"},
{name="Lykopolis", region="Haifan Bassaridia", tier="minor"},
-- Normark
{name="Ardclach", region="Normark", tier="major"},
{name="Riddersborg", region="Normark", tier="major"},
-- Ouriana
{name="Bashkim", region="Ouriana", tier="major"},
{name="Ourid", region="Ouriana", tier="major"},
{name="Tonar", region="Ouriana", tier="minor"},
-- Caledonia
{name="Fanghorn", region="Caledonia", tier="major"},
{name="Eikbu", region="Caledonia", tier="major"},
{name="Galvø", region="Caledonia", tier="major"},
{name="Sårensby", region="Caledonia", tier="minor"},
{name="Slevik", region="Caledonia", tier="minor"},
{name="Sjøsborg", region="Caledonia", tier="minor"},
{name="Storesund", region="Caledonia", tier="minor"},
{name="Notranskja", region="Caledonia", tier="minor"},
{name="Kaledonija", region="Caledonia", tier="minor"},
}
local citySet = {}
for _, c in ipairs(cityOrder) do citySet[c.name] = true end
local function regionPass(filter, cityRegion)
filter = tostring(filter or "All")
if filter == "" or filter == "All" then return true end
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.1) CITY FLAGS (matches GeneralPortOperations naming; NO existence checks)
--------------------------------------------------------------------------------
local DEFAULT_CITY_FLAG_FILE = "Imperial Trade Union flag.png"
local function normCityForFlag(name)
-- Matches Module:GeneralPortOperations
return (name or "")
:gsub("[%s%-’]", "")
:gsub("[Áá]", "A"):gsub("[Éé]", "E")
:gsub("[Íí]", "I"):gsub("[Óó]", "O")
:gsub("[Úú]", "U"):gsub("[Ýý]", "Y")
end
local cityFlagOverrides = {
-- Same override used in GeneralPortOperations
["Skýrophos"] = "SkyrophosFlag.png",
-- Known filename oddity (you noted this explicitly)
["Slevik"] = "SlevikFlag.png.png",
}
local cityFlagCache = {}
local function getCityFlagMarkup(cityName)
if cityFlagCache[cityName] then return cityFlagCache[cityName] end
local f = cityFlagOverrides[cityName] or (normCityForFlag(cityName) .. "Flag.png")
-- If a city isn't on the list or is a special case, fallback is still safe:
if not cityName or cityName == "" then f = DEFAULT_CITY_FLAG_FILE end
local mk = string.format("[[File:%s|20px]]", f)
cityFlagCache[cityName] = mk
return mk
end
--------------------------------------------------------------------------------
-- 5) ORG UNITS (Missionary echelons)
--------------------------------------------------------------------------------
local org = {
Lampade = { code="L", size=8 },
Hetairoi = { code="H", size=12 },
Dodekade = { code="D", size=18 },
Kleisthenes = { code="K", size=25 },
Stoa = { code="S", size=40 },
Thiasos = { code="T", size=60 },
}
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
--------------------------------------------------------------------------------
-- 6) CULT FLAGS (taken from Module:GeneralPortOperations addLogo mappings)
-- NO existence checks. These filenames are the source of truth.
--------------------------------------------------------------------------------
local DEFAULT_CULT_FLAG_FILE = "Imperial Trade Union flag.png"
local cultLogoFile = {
-- From GeneralPortOperations addLogo() lines
["Order of Aurora Mystica"] = "Auroramystica.png",
["Harmony Sanctum"] = "Harmonysanctum.png",
["Celestial Harmony Sect"] = "HarmonySect.png",
["Guild of Golden Shadows"] = "GoldenShadows.png",
["Azure Sentinel Sect"] = "AzureSentinel.png",
["Reverie Nebulous"] = "RevNeb.png",
["Mystery of the Verdant Embrace"] = "Verdant.png",
["Conclave Illuminara"] = "Conclave.png",
["Sanctum Vitalis"] = "Vitalis.png",
["Court of the Ironclad"] = "Ironclad.png",
["Sanctum Delphica"] = "SanctumDel.png",
-- Not in ritualCults list, but GeneralPortOperations also adds a logo for Temple of Aprobelle
["Temple Aprobelle"] = "TempleAprobelleLogo.png",
-- Pharmacon Sect is in serviceCompanies; GeneralPortOperations doesn’t map it in the cult list
-- but you likely have a logo file; set it explicitly here:
["Pharmacon Sect"] = "PharmaconSect.png",
}
local function getCultFlagMarkup(cultName)
local f = cultLogoFile[cultName] or DEFAULT_CULT_FLAG_FILE
return string.format("[[File:%s|18px]] ", f)
end
--------------------------------------------------------------------------------
-- 7) ORDERS + TASKS
--------------------------------------------------------------------------------
local function orderLink(pageTitle, anchor, label)
return string.format("[[%s#%s|%s]]", pageTitle, anchor, label)
end
local function T(text, sev, tags) return { text=text, sev=sev, tags=tags or {} } end
-- Orders: chosen to match “cult logo” coverage from GeneralPortOperations.
local orders = {
{key="AUM", name="Order of Aurora Mystica",
link=orderLink("Eos (Reformed Stripping Path)","Order_of_Aurora_Mystica","Order of Aurora Mystica"),
tasks={
T("Dawn reconciliation rite for competing guilds; written pledges sealed with Temple Bank clerks.","green",{"mediation","guild"}),
T("Sunrise ‘first-light’ counseling circle for strained households; referrals issued through shrine stewards.","green",{"care","mediation"}),
T("Dawn-watch bulletin: correct false omen claims and stabilize shift work across canal gates.","green",{"omen","public"}),
T("Crowd choreography support at morning festivals to prevent stampede behavior and pickpocket chaos.","yellow",{"crowd","festival"}),
T("Targeted rumor-clearing procession after unsigned pamphlets; witness statements taken and archived.","yellow",{"rumor","investigation"}),
T("Joint patrol with civic stewards through tunnels; lighting faults logged; intimidation incidents recorded.","yellow",{"security","infrastructure"}),
T("Emergency reconciliation summit after street violence; truce terms read and posted in public squares.","red",{"unrest","mediation"}),
T("Crisis dawn vigil after night incident; grief support + ‘no retaliation’ pledge drive.","red",{"crisis","grief","public"}),
T("Escort of a vulnerable witness to hearing; route screened for ambush or intimidation.","red",{"security","crisis"}),
T("Post-incident narrative repair: speaker coaching and public clarity bulletin to prevent reprisals.","yellow",{"public","analysis"}),
}
},
{key="HSA", name="Harmony Sanctum",
link="[[Harmony_Sanctum|Harmony Sanctum]]",
tasks={
T("Neighborhood harmony circle: mediated contract dispute between vendors and dock labor overseers.","green",{"mediation","trade"}),
T("Public etiquette instruction at shrine square; queue discipline and respectful speech enforcement.","green",{"public","order"}),
T("Household conflict de-escalation visits after complaint surge; written behavioral plan issued.","green",{"care","mediation"}),
T("Festival crowd-lane planning with stewards to prevent crush risks and rumor cascades.","yellow",{"festival","crowd"}),
T("Rumor triage: identify source nodes, issue corrective bulletins, and coordinate speaker messaging.","yellow",{"rumor","public"}),
T("Joint calming patrol at transit choke point; separation corridor opened for commuters.","yellow",{"transit","crowd"}),
T("Emergency stabilization during riot flare: dispersal corridors + visible care stations deployed.","red",{"unrest","crowd"}),
T("Night calm-litany enforcement after panic episode; coordinated supervised dispersal.","red",{"panic","public"}),
T("Public apology ceremony after inter-group violence; pledge collection and restitution ledger filed.","yellow",{"mediation","governance"}),
T("Oversight coaching for district clerks after audit findings; corrective protocol training delivered.","yellow",{"audit","governance"}),
}
},
{key="CHS", name="Celestial Harmony Sect",
link="[[Celestial_Harmony_Sect|Celestial Harmony Sect]]",
tasks={
T("Cosmic-order lecture at shrine school: reduce anxiety about star-omens and market rumors.","green",{"education","omen"}),
T("Guided meditation session for canal workers; stress reduction paired with safety brief.","green",{"care","public"}),
T("Omen bulletin update: correct a false eclipse claim before it drives hoarding behavior.","yellow",{"rumor","omen"}),
T("Quiet-zone enforcement near hospitals; controlled chanting to prevent crowd agitation.","yellow",{"health","order"}),
T("Investigation of counterfeit star-charts being sold to pilgrims; vendor interviews conducted.","yellow",{"investigation","trade"}),
T("Crisis counseling during mass scare; supervised calm stations established at shrine gates.","red",{"crisis","public"}),
T("Emergency messaging team embedded with War League stewards to prevent retaliatory panic.","red",{"crisis","rumor"}),
T("Nighttime stabilization patrol during fog warnings; guidance posted and speakers briefed.","yellow",{"transit","public"}),
T("Post-event debrief: update approved narratives; speaker scripts distributed to shrines.","yellow",{"analysis","public"}),
T("Festival harmony protocol review: ensure rites align with civic order requirements.","green",{"festival","audit"}),
}
},
{key="GGS", name="Guild of Golden Shadows",
link="[[Guild_of_Golden_Shadows|Guild of Golden Shadows]]",
tasks={
T("Quiet intelligence intake: compile rumor sources and identify forged vouchers without public escalation.","green",{"analysis","investigation"}),
T("Market observation rotation to spot coercion and counterfeit seals; reports filed with Temple Bank.","green",{"audit","trade"}),
T("Discreet witness protection arrangement for a vendor threatened by smugglers.","yellow",{"security","investigation"}),
T("Forgery trace: identify printing stall distributing schismatic pamphlets; evidence packet assembled.","yellow",{"investigation","archive"}),
T("Undercover purchase of fake shrine seals; seize ledgers and hand off to clerks.","yellow",{"trade","security"}),
T("Night extraction of a compromised informant; safe route and temporary housing arranged.","red",{"crisis","security"}),
T("Emergency suppression of panic narrative: remove false broadsheets and replace with approved bulletin.","red",{"panic","rumor"}),
T("Coordination with Temple Aprobelle on narrative discipline during crowd surge.","yellow",{"analysis","public"}),
T("Intercepted contraband manifest review; cross-check with corridor registry for evasions.","yellow",{"audit","logistics"}),
T("Post-incident attribution report delivered to War League liaison; next-day prevention plan drafted.","yellow",{"analysis","governance"}),
}
},
{key="ASS", name="Azure Sentinel Sect",
link=orderLink("Aether (Reformed Stripping Path)","Azure_Sentinel_Sect","Azure Sentinel Sect"),
tasks={
T("Waterfront perimeter walk and safety rope checks along canal districts.","green",{"infrastructure","safety"}),
T("Storm-drain inspection with civil works; blockages cleared and logged.","green",{"infrastructure","service"}),
T("Port-house briefing on embarkation discipline and baggage rules; theft prevention guidance posted.","green",{"public","transit"}),
T("Escort for medical supplies across canal bridges; accountability signatures obtained.","yellow",{"logistics","security"}),
T("Harbor rumor suppression: correct false ‘quarantine’ claims to prevent stampede and hoarding.","yellow",{"rumor","public"}),
T("Search-and-rescue standby after storm warnings; muster roster and equipment check.","yellow",{"safety","crisis"}),
T("Emergency flood response: evacuation guidance at waterfront shrines; calm channels maintained.","red",{"crisis","relief"}),
T("High-risk escort of officials through crowd at port gate; separation corridor enforced.","red",{"security","crowd"}),
T("Debris clearance coordination after gale; hazard markings placed on docks.","yellow",{"infrastructure","safety"}),
T("Crisis harbor closure messaging: distribute official notices; prevent vigilante enforcement.","red",{"crisis","public"}),
}
},
{key="RNB", name="Reverie Nebulous",
link=orderLink("Nephele (Reformed Stripping Path)","Reverie_Nebulous","Reverie Nebulous"),
tasks={
T("Dream interpretation circle with strict moderation; anxiety de-escalation emphasized.","green",{"care","public"}),
T("Quiet art therapy session for children after a scare; parent coaching provided.","green",{"care","education"}),
T("Public ‘sleep hygiene’ teaching for shift workers; rest scheduling guidance posted.","green",{"health","education"}),
T("Rumor mapping: identify which dreams are feeding a harmful narrative; corrective story deployed.","yellow",{"rumor","analysis"}),
T("Witness interviews after trance-gathering complaint; compliance conditions issued.","yellow",{"audit","public"}),
T("Nighttime crowd calming near a transit node; chants regulated to prevent stampede behavior.","yellow",{"crowd","transit"}),
T("Containment support for mass panic dream episode; calm stations and supervised rest zones established.","red",{"crisis","crowd"}),
T("Emergency counseling for families after death; grief rites paired with aid referrals.","red",{"grief","crisis"}),
T("High-risk ‘dream breach’ report assessed with Aprobelle liaisons; misinformation suppressed.","red",{"crisis","rumor"}),
T("Post-event debrief: public narrative edited to reduce retaliatory impulses; speaker training conducted.","yellow",{"public","analysis"}),
}
},
{key="VDE", name="Mystery of the Verdant Embrace",
link=orderLink("Thalassa (Reformed Stripping Path)","Mystery_of_the_Verdant_Embrace","Mystery of the Verdant Embrace"),
tasks={
T("Irrigation ritual + practical canal cleanup; silt removed and flow restored.","green",{"infrastructure","agriculture"}),
T("Seed blessing for market gardeners; pest-prevention guidance distributed.","green",{"agriculture","public"}),
T("Wetland path maintenance; travel warnings posted to prevent drownings.","green",{"safety","infrastructure"}),
T("Waterbank audit at an aqueduct; leakage reported and repair crews coordinated.","yellow",{"audit","infrastructure"}),
T("Field mediation over irrigation rights; shared schedule drafted and signed.","yellow",{"mediation","governance"}),
T("Brushfire risk patrol in dry districts; controlled burns prohibited and notices issued.","yellow",{"fire","public"}),
T("Disease vector mitigation in swamp edges; standing water treated and guidance posted.","yellow",{"health","infrastructure"}),
T("Post-storm debris clearance with shrine volunteers; hazard markings placed.","yellow",{"safety","infrastructure"}),
T("Emergency flood relief coordination: sandbag lines, food staging, and shelter support.","red",{"crisis","relief"}),
T("Crisis canal breach response: temporary closure, water reroute, and crowd calming.","red",{"crisis","infrastructure","crowd"}),
}
},
{key="CIL", name="Conclave Illuminara",
link="[[Conclave_Illuminara|Conclave Illuminara]]",
tasks={
T("Night-sky lecture and calendar correction posted to calm omen anxiety.","green",{"education","omen"}),
T("Quiet meditation ward established at a shrine; controlled breathing protocols taught.","green",{"care","public"}),
T("Lantern-route inspection for canal patrols; reflective markers placed to reduce accidents.","green",{"infrastructure","transit"}),
T("Dream-space audit: mirror pools cleaned, offerings catalogued, safety signage posted.","green",{"shrine","audit"}),
T("Investigation of counterfeit moon tokens; vendor interviews and seizure log.","yellow",{"investigation","trade"}),
T("Controlled night gathering after panic rumor; calm-litany enforced; dispersal plan coordinated.","yellow",{"rumor","crowd"}),
T("Nighttime crowd calming near transit node; chants regulated to prevent stampede behavior.","yellow",{"crowd","transit"}),
T("Emergency anti-panic messaging after ‘demonic sign’ claim; script distributed to shrine speakers.","red",{"crisis","rumor"}),
T("Escort for sensitive nocturnal rite during unrest; perimeter observers posted.","red",{"security","unrest"}),
T("Blackout safety drill during storm season (lanterns, guides, canal warning ropes).","yellow",{"infrastructure","safety"}),
}
},
{key="SVI", name="Sanctum Vitalis",
link="[[Sanctum_Vitalis|Sanctum Vitalis]]",
tasks={
T("Clinic resupply run: inventory reconciled with Temple Bank vouchers.","green",{"health","logistics"}),
T("Water-quality checks at canal taps; purification guidance posted.","green",{"health","infrastructure"}),
T("School hygiene lecture paired with modest blessing service.","green",{"health","education"}),
T("Routine fever screening at market gate; clinic referral slips issued.","green",{"health","public"}),
T("Temporary clinic tent near shrine square; triage lines marked and staffed.","yellow",{"health","crowd"}),
T("Contamination rumor assessment: test kits deployed to prevent panic.","yellow",{"health","rumor"}),
T("Heat illness prevention training and hydration discipline coaching.","yellow",{"health","public"}),
T("Mass casualty support after accident; triage, referral, and grief counseling coordination.","red",{"crisis","health"}),
T("Quarantine support in dormitory ward; supplies custody and compliance checks.","red",{"crisis","health","audit"}),
T("Night clinic staffing for unrest injuries; neutral triage posture announced.","red",{"unrest","health"}),
}
},
{key="SDE", name="Sanctum Delphica",
link="[[Sanctum_Delphica|Sanctum Delphica]]",
tasks={
T("Archive custody inspection: seals verified; storage conditions checked; ledger updated.","green",{"archive","audit"}),
T("Scribe training on custody chain; convoy paperwork rehearsed for shrine transfers.","green",{"education","logistics"}),
T("Pilgrim etiquette workshop at library shrine; damage prevention and quiet-zone enforcement.","green",{"public","education"}),
T("Index confiscated pamphlets for signature motifs; forward packet for correlation.","yellow",{"investigation","archive"}),
T("Witness interviews after shrine vandalism; sworn statements taken and sealed.","yellow",{"investigation","security"}),
T("Doctrinal quarantine assistance: entry controls and rumor suppression around restricted ward.","yellow",{"security","rumor"}),
T("Court packet assembly for desecration suspects; evidence collated with clerks.","yellow",{"governance","audit"}),
T("Night watch rotation at archive stacks following arson threat.","red",{"security","unrest"}),
T("Escort a sensitive artifact transfer; route denial markers and perimeter rites deployed.","red",{"security","logistics"}),
T("Emergency custody lockdown after attempted breach; rapid relocation plan activated.","red",{"crisis","security","archive"}),
}
},
{key="PHA", name="Pharmacon Sect",
link="[[Pharmacon_Sect|Pharmacon Sect]]",
tasks={
T("Dispensary audit: dosage seals verified; controlled compounds logged and countersigned.","green",{"health","audit"}),
T("Field compounding session with herb vendors; purity protocols enforced.","green",{"health","industry"}),
T("Clinic support for chronic cases; treatment plans documented in Temple Bank ledgers.","green",{"health","service"}),
T("Water purification consult at canal gate; intake screens inspected for sabotage.","yellow",{"infrastructure","investigation"}),
T("Extraction team dispatched to secure a medically useful mineral/resin source under escort.","yellow",{"logistics","security"}),
T("Rumor suppression briefing about ‘cursed medicine’; evidence packet and calm script issued.","yellow",{"rumor","public"}),
T("Emergency antidote preparation after suspected poisoning; vendor supply chain traced.","red",{"crisis","investigation","health"}),
T("Outbreak response: isolation guidance and clinic routing control at transit nodes.","red",{"crisis","health","public"}),
T("Hospital reinforcement for Eidolan exposure aftershock cases; monitoring schedule created.","red",{"crisis","health"}),
T("Post-incident debrief with Temple Aprobelle on panic dynamics; clinic scripts updated.","yellow",{"analysis","rumor"}),
}
},
{key="TAP", name="Temple Aprobelle",
link="[[Temple_Aprobelle|Temple Aprobelle]]",
tasks={
T("Festival hygiene audit: vendor protocols verified; seals issued to compliant stalls.","green",{"health","audit","festival","public"}),
T("Rumor-control bulletin cycle: shrine-leader briefing + posted clarifications.","green",{"rumor","public","service"}),
T("Grief-cadre home visits after accident; de-escalation coaching for relatives and stewards.","green",{"grief","mediation","service"}),
T("School immunization coordination: clinic schedule posted and consent ledgers reconciled.","green",{"health","school","coordination"}),
T("Clinic-queue stabilization drill: entry metering and conflict prevention training.","yellow",{"queue","crowd","health","order"}),
T("Loyalty and behavior audit of district office after complaint surge; corrective training issued.","yellow",{"audit","governance","compliance"}),
T("Anomaly-response dispatch after ‘demonic contamination’ claims; care paired with narrative discipline.","yellow",{"anomaly","rumor","public","health"}),
T("Pattern-analysis intake: confiscated schism texts logged and forwarded for signature correlation.","yellow",{"analysis","schism","security","archive"}),
T("Transit node stabilization after crowd surge: calm stations and signage; steward handoffs established.","yellow",{"transit","crowd","stabilization"}),
T("Silencing-cadre activated after panic flare; controlled messaging + visible care stations deployed.","red",{"panic","rumor","stabilization"}),
T("Emergency stabilization in evacuation/fever center: calming rites, grief triage, rumor suppression.","red",{"crisis","health","crowd","stabilization"}),
}
},
}
local orderByName = {}
for _, o in ipairs(orders) do orderByName[o.name] = o end
--------------------------------------------------------------------------------
-- 8) CITY PROFILES (bias order selection)
--------------------------------------------------------------------------------
local cityProfiles = {
["Vaeringheim"] = {weights={AUM=2,HSA=2,CHS=1,GGS=1,ASS=2,RNB=1,VDE=2,CIL=2,SVI=2,SDE=2,PHA=1,TAP=3}},
["Somniumpolis"] = {weights={RNB=3,SVI=2,PHA=2,TAP=3,GGS=2,ASS=1}},
["Luminaria"] = {weights={AUM=3,CIL=2,HSA=2,TAP=2,SVI=1}},
["Lunalis Sancta"] = {weights={SDE=3,CIL=2,TAP=2,HSA=1}},
["Delphica"] = {weights={SDE=3,TAP=2,CIL=1,SVI=1}},
["Thermosalem"] = {weights={SVI=2,PHA=2,TAP=2,ASS=2,HSA=1}},
["Pyralis"] = {weights={SVI=2,TAP=2,ASS=2,AUM=2,HSA=1}},
["Keybir-Aviv"] = {weights={ASS=3,TAP=2,GGS=2,HSA=1,SVI=1}},
["Lewisburg"] = {weights={ASS=2,GGS=2,TAP=2,HSA=1,SVI=1}},
["Diamandis"] = {weights={TAP=2,SVI=2,AUM=1,RNB=1,HSA=1}},
["Tonar"] = {weights={SDE=2,SVI=2,VDE=2,TAP=1,ASS=1}},
["Bashkim"] = {weights={VDE=3,SVI=2,TAP=1,HSA=1}},
["Ourid"] = {weights={VDE=3,SVI=2,TAP=1}},
["Ardclach"] = {weights={ASS=2,SVI=2,TAP=1,CIL=1}},
["Riddersborg"] = {weights={ASS=2,HSA=2,TAP=1,SVI=1}},
}
local function getProfile(cityName)
return cityProfiles[cityName] or {weights={AUM=1,HSA=1,ASS=1,SVI=1,SDE=1,TAP=1}}
end
--------------------------------------------------------------------------------
-- 9) OPTIONAL: ECON SIGNALS (EXPENSIVE; only run when showEcon=yes)
--------------------------------------------------------------------------------
local function getEconSignals()
local title = mw.title.new("General Port of Lake Morovia")
if not title then return {} end
local ok, content = pcall(function() return title:getContent() end)
if not ok or not content or content == "" then return {} end
local signals = {}
local text = normalizeSpaces(content)
for _, c in ipairs(cityOrder) do
local n = c.name
local hits = 0
local pattern = n:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])","%%%1")
for _ in text:gmatch(pattern) do
hits = hits + 1
if hits >= 6 then break end
end
if hits >= 3 then
signals[n] = {summary=string.format("High Port linkage (mentions: %d)", hits)}
end
end
return signals
end
--------------------------------------------------------------------------------
-- 10) OPTIONAL: THREAT WATCH (Module:ReligiousEvents)
--------------------------------------------------------------------------------
local function parseReligiousEventsTable(wikitext)
local map = {}
wikitext = tostring(wikitext or "")
for line in wikitext:gmatch("[^\n]+") do
if line:match("^|%s") and not line:match("^|%-%s*$") then
local parts = mw.text.split(line:gsub("^|%s*", ""), "%s*||%s*")
if #parts >= 4 then
local city = stripWiki(parts[2])
local ev = stripWiki(parts[3])
local urg = stripWiki(parts[4])
if city ~= "" and ev ~= "" then
map[city] = {event=ev, urg=urg}
end
end
end
end
return map
end
--------------------------------------------------------------------------------
-- 11) WX OPS ALERT INTEGRATION (Module:BassaridiaForecast)
--------------------------------------------------------------------------------
local wxOk, wx = pcall(require, "Module:BassaridiaForecast")
if not wxOk then wx = nil end
local function wxColor(level)
level = tonumber(level) or 0
if level == 3 then return "#f8d7da" end
if level == 2 then return "#ffe5cc" end
if level == 1 then return "#fff3cd" end
return "#d4edda"
end
local function opsCell(cityName, year, dayOfYear)
if not wx or type(wx.getOpsData) ~= "function" then return "n/a", nil end
local op = wx.getOpsData(cityName, year, dayOfYear)
if not op or not op.alert then return "n/a", nil end
local lvl = tonumber(op.alert.level) or 0
local label = tostring(op.alert.label or "Green")
local cond = tostring(op.condition or "")
local bg = wxColor(lvl)
local cell = string.format(
"<span style='background:%s;padding:2px 6px;border:1px solid #ccc;border-radius:10px;white-space:nowrap;'>%s</span>%s",
bg, label, (cond ~= "" and ("<br/><span style='font-size:90%;'>" .. cond .. "</span>") or "")
)
return cell, op
end
local function bumpSevWeather(sev, op)
if not op or not op.alert then return sev end
local lvl = tonumber(op.alert.level) or 0
local imp = op.impacts or {}
if lvl == 2 and sev == "green" then sev = "yellow" end
if lvl >= 3 then sev = "red" end
if (imp.ground == "Hazard" or imp.sea == "Closed" or imp.air == "No-Go") and sev == "green" then
sev = "yellow"
end
return sev
end
local function weatherTaskOverlay(op)
if not op or not op.alert then return "" end
local lvl = tonumber(op.alert.level) or 0
if lvl < 2 then return "" end -- only Orange/Red append
local haz = tostring(op.alert.hazard or "weather disruption")
local imp = op.impacts or {}
local cond = tostring(op.condition or "")
local base = " Weather-driven tasking: " .. haz .. " (" ..
string.format("Gnd %s / Sea %s / Air %s", imp.ground or "OK", imp.sea or "OK", imp.air or "OK") .. ")."
if cond == "Storm" or cond == "Rain" then
return base .. " Clinic/relief queues stabilized; rumor bulletins posted; shrine-district lanes marked."
elseif cond == "Blizzard" or cond == "Snow" then
return base .. " Warming-shelter checks; fuel/medicine custody verified; travel guidance posted."
elseif cond == "Fog" then
return base .. " Navigation warnings and crowd guidance in port/canal corridors; panic narratives suppressed."
elseif cond == "Dust" then
return base .. " Mask discipline and vendor hygiene enforcement prioritized; indoor refuge advisories posted."
else
return base .. " Welfare checks and public messaging prioritized."
end
end
--------------------------------------------------------------------------------
-- 12) ORDER/TASK CHOICE
--------------------------------------------------------------------------------
local function inferSeverity(cityTier, taskSev, threatUrg)
local sev = tostring(taskSev or "green")
if cityTier == "major" and sev == "green" then sev = "yellow" end
if urgencyScore(threatUrg) >= 3 then
sev = "red"
elseif urgencyScore(threatUrg) == 2 and sev == "green" then
sev = "yellow"
end
return sev
end
local function pickOrder(nextInt, cityName, threat)
local profile = getProfile(cityName)
local weighted = {}
for key, w in pairs(profile.weights or {}) do
weighted[#weighted+1] = {key=key, w=tonumber(w) or 0}
end
-- threat bump: prefer Aprobelle / health / security / shadows
if threat and urgencyScore(threat.urg) >= 2 then
for _, it in ipairs(weighted) do
if it.key == "TAP" or it.key == "SVI" or it.key == "PHA" or it.key == "ASS" or it.key == "GGS" then
it.w = it.w + 2
end
end
end
local picked = pickWeighted(nextInt, weighted).key
for _, o in ipairs(orders) do
if o.key == picked then return o end
end
return orders[1]
end
local function pickTask(nextInt, o, sev)
local pool = {}
for _, t in ipairs(o.tasks or {}) do
local w = 1
if t.sev == sev then w = 4
elseif sev == "red" and t.sev == "yellow" then w = 2
elseif sev == "yellow" and t.sev == "green" then w = 2 end
pool[#pool+1] = {t=t, w=w}
end
return pickWeighted(nextInt, pool).t
end
--------------------------------------------------------------------------------
-- 13) BANK NOTES
--------------------------------------------------------------------------------
local bankNotes = {
"Stipend disbursed after checklist verification; receipt countersigned by local steward.",
"Voucher reconciliation complete; supplies logged and stored under dual-key protocol.",
"Observer note: crowd discipline improved after posted guidance and steward coaching.",
"Repair authorization issued; labor scheduled at off-peak hours.",
"Custody ledger updated; medicines/artifacts sealed and moved to secure storage.",
"Training memo filed; corrective instruction issued to volunteers for queue discipline.",
}
--------------------------------------------------------------------------------
-- 14) BUILD ONE CITY ROW
--------------------------------------------------------------------------------
local function buildCityRow(di, c, threatMap, includeThreat, showEcon, econSignals)
local cityName = c.name
local seed = hash31(string.format("mops:%d:%03d:%s", di.year, di.dayOfYear, cityName))
local nextInt = lcg(seed)
local th = includeThreat and threatMap and threatMap[cityName] or nil
local o = pickOrder(nextInt, cityName, th)
-- task + severity
local baseTask = pickTask(nextInt, o, "yellow")
local threatUrg = th and th.urg or ""
local sev = inferSeverity(c.tier, baseTask.sev, threatUrg)
-- wx overlay
local opsBadge, wxData = opsCell(cityName, di.year, di.dayOfYear)
sev = bumpSevWeather(sev, wxData)
local taskText = baseTask.text .. weatherTaskOverlay(wxData)
-- unit
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)
-- bank note
local bank = pickFromList(nextInt, bankNotes, nil)
-- econ
local econ = ""
if showEcon and econSignals and econSignals[cityName] then econ = econSignals[cityName].summary or "" end
return {
style = sevStyle(sev),
date = di.text,
city = string.format("%s %s", getCityFlagMarkup(cityName), cityLink(cityName)),
region = c.region,
ops = opsBadge,
order = getCultFlagMarkup(o.name) .. o.link,
unit = string.format("%s (%d) [%s]", unitName, unitMeta.size, unitCode),
task = taskText,
bank = bank,
threat = (includeThreat and th and th.event) and th.event or "",
threatUrg = (includeThreat and th and th.urg) and th.urg or "",
sev = sev,
econ = econ,
}
end
--------------------------------------------------------------------------------
-- 15) 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 showEcon = tostring(args.showEcon or "no"):lower() == "yes"
local limit = tonumber(args.limit or "")
local di = getDateInfo()
-- Threat map
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
-- Econ signals (expensive; only when showEcon=yes)
local econSignals = {}
if showEcon then
econSignals = getEconSignals()
end
local out = {}
table.insert(out, "{| class='wikitable sortable'")
local header = "! style='white-space:nowrap;' | Date !! City !! Region !! Ops Alert !! Cult Order !! Unit !! Tasking"
if showEcon then header = header .. " !! Economic signal" end
header = header .. " !! Temple Bank Notes"
if includeThreat then header = header .. " !! Threat Watch" end
header = header .. " !! Severity"
table.insert(out, header)
local count = 0
for _, c in ipairs(cityOrder) do
if regionPass(regionFilter, c.region) then
local row = buildCityRow(di, c, threatMap, includeThreat, showEcon, econSignals)
table.insert(out, "|-" .. row.style)
local line = string.format("| %s || %s || %s || %s || %s || %s || %s",
row.date, row.city, row.region, row.ops, row.order, row.unit, row.task
)
if showEcon then line = line .. " || " .. (row.econ ~= "" and row.econ or "n/a") end
line = line .. " || " .. row.bank
if includeThreat then
local threatCell = (row.threat ~= "" and (row.threat .. (row.threatUrg ~= "" and (" ("..row.threatUrg..")") or ""))) or "No reports / n.a."
line = line .. " || " .. threatCell
end
line = line .. " || " .. row.sev
table.insert(out, line)
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, "<br/>Ops Alert badge reflects forecast-driven operational risk and aligns with War League / Hatch posture.")
table.insert(out, "<br/>For overseas deployments please see [[Bassaridian_involvement_in_Corum#Corumian_Missionary_Deployment_Reports|Corumian Missionary Deployment Reports]].")
table.insert(out, "</div>")
end
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
--------------------------------------------------------------------------------
-- 16) SINGLE CITY 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
if not citySet[city] then return "''Error: unknown city (must match canonical name).''" end
local includeThreat = tostring(args.includeThreat or "no"):lower() == "yes"
local showEcon = tostring(args.showEcon or "no"):lower() == "yes"
local di = getDateInfo()
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 econSignals = {}
if showEcon then econSignals = getEconSignals() end
local region, tier = "Core", "minor"
for _, c in ipairs(cityOrder) do
if c.name == city then region, tier = c.region, c.tier break end
end
local row = buildCityRow(di, {name=city, region=region, tier=tier}, threatMap, includeThreat, showEcon, econSignals)
local out = {}
table.insert(out, "{| class='wikitable'")
local header = "! Date !! City !! Region !! Ops Alert !! Cult Order !! Unit !! Tasking"
if showEcon then header = header .. " !! Economic signal" end
header = header .. " !! Temple Bank Notes"
if includeThreat then header = header .. " !! Threat Watch" end
header = header .. " !! Severity"
table.insert(out, header)
table.insert(out, "|-" .. row.style)
local line = string.format("| %s || %s || %s || %s || %s || %s || %s",
row.date, row.city, row.region, row.ops, row.order, row.unit, row.task
)
if showEcon then line = line .. " || " .. (row.econ ~= "" and row.econ or "n/a") end
line = line .. " || " .. row.bank
if includeThreat then
local threatCell = (row.threat ~= "" and (row.threat .. (row.threatUrg ~= "" and (" ("..row.threatUrg..")") or ""))) or "No reports / n.a."
line = line .. " || " .. threatCell
end
line = line .. " || " .. row.sev
table.insert(out, line)
table.insert(out, "|}")
return table.concat(out, "\n")
end
--------------------------------------------------------------------------------
-- 17) ORDER-ONLY VIEW
--------------------------------------------------------------------------------
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).''"
end
local wantedOrder = orderByName[wanted]
if not wantedOrder then
return "''Error: unknown order name (must match module order names).''"
end
local includeThreat = tostring(args.includeThreat or "no"):lower() == "yes"
local showEcon = tostring(args.showEcon or "no"):lower() == "yes"
local di = getDateInfo()
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 econSignals = {}
if showEcon then econSignals = getEconSignals() end
local out = {}
table.insert(out, "{| class='wikitable sortable'")
local header = "! Date !! City !! Region !! Ops Alert !! Unit !! Tasking"
if showEcon then header = header .. " !! Economic signal" end
header = header .. " !! Temple Bank Notes"
if includeThreat then header = header .. " !! Threat Watch" end
header = header .. " !! Severity"
table.insert(out, header)
for _, c in ipairs(cityOrder) do
local row = buildCityRow(di, c, threatMap, includeThreat, showEcon, econSignals)
if row.order:find(wantedOrder.link, 1, true) or row.order:find(wantedOrder.name, 1, true) then
table.insert(out, "|-" .. row.style)
local line = string.format("| %s || %s || %s || %s || %s || %s",
row.date, row.city, row.region, row.ops, row.unit, row.task
)
if showEcon then line = line .. " || " .. (row.econ ~= "" and row.econ or "n/a") end
line = line .. " || " .. row.bank
if includeThreat then
local threatCell = (row.threat ~= "" and (row.threat .. (row.threatUrg ~= "" and (" ("..row.threatUrg..")") or ""))) or "No reports / n.a."
line = line .. " || " .. threatCell
end
line = line .. " || " .. row.sev
table.insert(out, line)
end
end
table.insert(out, "|}")
return table.concat(out, "\n")
end
return p