Module:MissionaryDailyOpsBVR: Difference between revisions

From MicrasWiki
Jump to navigationJump to search
NewZimiaGov (talk | contribs)
Undo revision 387695 by NewZimiaGov (talk)
Tag: Undo
NewZimiaGov (talk | contribs)
No edit summary
Line 1: Line 1:
-- Module:MissionaryDailyOpsBVR (REWRITE + FLAG RESOLVER + CULT ICONS)
-- Module:MissionaryDailyOpsBVR (REWRITE + WX OPS ALERT + TEMPLE APROBELLE)
-- Daily (PSSC-calendar keyed) missionary tasking ledger for the Temple Bank of the Reformed Stripping Path.
-- Daily (PSSC-calendar keyed) missionary tasking ledger for the Temple Bank of the Reformed Stripping Path.
-- Deterministic output (no math.random / no os.time seeding).
-- Deterministic output (no math.random / no os.time seeding).
--
--
-- Features:
-- Features:
--  • City profiles (culture/spiritual tags + per-city order bias)
--  • Canonical city network (regions + tiers)
--  • Economic signals parsed from General Port of Lake Morovia "Company Table"
--    (counts by sector + sample HQ companies) and used to bias order + task.
--  • Optional Threat Watch integration from Module:ReligiousEvents (opt-in).
--  • City flags: existence-checked; fallback to Imperial Trade Union flag when missing.
--  • City flags: existence-checked; fallback to Imperial Trade Union flag when missing.
--  • Cult/Order icons: from Module:GeneralPortOperations addLogo(...) mapping.
--  • Orders + tasks (tagged, 10+ per order)
--  • Optional Threat Watch integration from Module:ReligiousEvents (opt-in)
--  • Optional Economic signal parsing from General Port of Lake Morovia (opt-in; safe fallback)
--  • WX Ops Alert integration from Module:BassaridiaForecast:
--      - Adds “Ops Alert” column (badge + condition)
--      - Bumps severity (Orange/Red) + appends weather-driven task overlay (Orange/Red only)
--
--
-- Usage:
-- Usage:
Line 17: Line 19:
--  {{#invoke:MissionaryDailyOpsBVR|makeTable|showEcon=yes}}
--  {{#invoke:MissionaryDailyOpsBVR|makeTable|showEcon=yes}}
--  {{#invoke:MissionaryDailyOpsBVR|makeCity|city=Vaeringheim}}
--  {{#invoke:MissionaryDailyOpsBVR|makeCity|city=Vaeringheim}}
--  {{#invoke:MissionaryDailyOpsBVR|makeOrder|order=Order of Aurora Mystica}}
--  {{#invoke:MissionaryDailyOpsBVR|makeOrder|order=Temple Aprobelle}}


local p = {}
local p = {}
Line 32: Line 34:
s = s:gsub("^%s+", ""):gsub("%s+$", "")
s = s:gsub("^%s+", ""):gsub("%s+$", "")
return s
return s
end
local function safeTitleContent(titleText)
local ok, t = pcall(mw.title.new, titleText)
if not ok or not t then return nil end
local ok2, content = pcall(function() return t:getContent() end)
if not ok2 then return nil end
return content
end
end


Line 59: Line 53:
end
end


local function containsAny(s, words)
local function joinTags(tags)
s = (s or ""):lower()
if not tags or #tags == 0 then return "" end
for _, w in ipairs(words) do
return table.concat(tags, ", ")
if s:find(w, 1, true) then return true end
end
return false
end
end


local function joinTags(tags)
local function safeTitleContent(titleText)
if not tags or #tags == 0 then return "" end
local ok, t = pcall(mw.title.new, titleText)
local out = {}
if not ok or not t then return nil end
for _, t in ipairs(tags) do out[#out+1] = t end
local ok2, content = pcall(function() return t:getContent() end)
return table.concat(out, ", ")
if not ok2 then return nil end
return content
end
end


Line 125: Line 117:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local function hash31(str)
local function hash31(str)
str = tostring(str or "")
local h = 0
local h = 0
for i = 1, #str do
for i = 1, #str do
Line 142: Line 135:


local function pickFromList(nextInt, list, avoidValue)
local function pickFromList(nextInt, list, avoidValue)
if #list == 0 then return nil end
if not list or #list == 0 then return nil end
if #list == 1 then return list[1] end
if #list == 1 then return list[1] end
local tries = 0
local tries = 0
Line 155: Line 148:
end
end
return list[1]
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
end


Line 173: Line 179:
-- 4) CITY NETWORK (canonical list)
-- 4) CITY NETWORK (canonical list)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local function normalizeForFlag(name)
return (name or "")
:gsub("[%s%-’]", "")
:gsub("[Áá]", "A"):gsub("[Éé]", "E")
:gsub("[Íí]", "I"):gsub("[Óó]", "O")
:gsub("[Úú]", "U"):gsub("[Ýý]", "Y")
end
local function cityLink(name)
local function cityLink(name)
return string.format("[[List of cities in Bassaridia Vaeringheim#%s|%s]]", name, name)
return string.format("[[List of cities in Bassaridia Vaeringheim#%s|%s]]", name, name)
Line 198: Line 196:
{name="Lunalis Sancta",  region="Core", tier="major"},
{name="Lunalis Sancta",  region="Core", tier="major"},
{name="Sylvapolis",      region="Core", tier="major"},
{name="Sylvapolis",      region="Core", tier="major"},
-- Core minors
-- Core minors
{name="Saluria",          region="Core", tier="minor"},
{name="Saluria",          region="Core", tier="minor"},
Line 209: Line 206:
{name="Koinonía",        region="Core", tier="minor"},
{name="Koinonía",        region="Core", tier="minor"},
{name="Aureum",          region="Core", tier="minor"},
{name="Aureum",          region="Core", tier="minor"},
-- Alperkin
-- Alperkin
{name="The Alpazkigz",    region="Alperkin", tier="minor"},
{name="The Alpazkigz",    region="Alperkin", tier="minor"},
{name="Odiferia",        region="Alperkin", tier="minor"},
{name="Odiferia",        region="Alperkin", tier="minor"},
-- New South Jangsong
-- New South Jangsong
{name="Skýrophos",        region="New South Jangsong", tier="major"},
{name="Skýrophos",        region="New South Jangsong", tier="major"},
Line 225: Line 220:
{name="Ephyra",          region="New South Jangsong", tier="minor"},
{name="Ephyra",          region="New South Jangsong", tier="minor"},
{name="Halicarn",        region="New South Jangsong", tier="minor"},
{name="Halicarn",        region="New South Jangsong", tier="minor"},
-- Haifan Bassaridia
-- Haifan Bassaridia
{name="Keybir-Aviv",      region="Haifan Bassaridia", tier="major"},
{name="Keybir-Aviv",      region="Haifan Bassaridia", tier="major"},
Line 236: Line 230:
{name="Sufriya",          region="Haifan Bassaridia", tier="minor"},
{name="Sufriya",          region="Haifan Bassaridia", tier="minor"},
{name="Lykopolis",        region="Haifan Bassaridia", tier="minor"},
{name="Lykopolis",        region="Haifan Bassaridia", tier="minor"},
-- Normark
-- Normark
{name="Ardclach",        region="Normark", tier="major"},
{name="Ardclach",        region="Normark", tier="major"},
{name="Riddersborg",      region="Normark", tier="major"},
{name="Riddersborg",      region="Normark", tier="major"},
-- Ouriana
-- Ouriana
{name="Bashkim",          region="Ouriana", tier="major"},
{name="Bashkim",          region="Ouriana", tier="major"},
{name="Ourid",            region="Ouriana", tier="major"},
{name="Ourid",            region="Ouriana", tier="major"},
{name="Tonar",            region="Ouriana", tier="minor"},
{name="Tonar",            region="Ouriana", tier="minor"},
-- Caledonia
-- Caledonia
{name="Fanghorn",        region="Caledonia", tier="major"},
{name="Fanghorn",        region="Caledonia", tier="major"},
Line 257: Line 248:
{name="Kaledonija",      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)
local function regionPass(filter, cityRegion)
if not filter or filter == "" or filter == "All" then return true end
filter = tostring(filter or "All")
if filter == "" or filter == "All" then return true end
if filter == "Core" then return cityRegion == "Core" end
if filter == "Core" then return cityRegion == "Core" end
if filter == "NSJ" then return cityRegion == "New South Jangsong" end
if filter == "NSJ" then return cityRegion == "New South Jangsong" end
Line 272: Line 267:
local DEFAULT_CITY_FLAG_FILE = "Imperial Trade Union flag.png"
local DEFAULT_CITY_FLAG_FILE = "Imperial Trade Union flag.png"


local function asciiNormalizeForFlag(name)
local function normalizeForFlag(name)
return (name or "")
return (name or "")
:gsub("[%s%-’]", "")
:gsub("[%s%-’]", "")
Line 293: Line 288:
return exists
return exists
end
end
local citySet = {}
for _, c in ipairs(cityOrder) do citySet[c.name] = true end


-- Explicit odd filenames you noted (plus Skyrophos).
-- Explicit odd filenames you noted (plus Skyrophos).
Line 319: Line 311:
-- 2) try normalized candidates
-- 2) try normalized candidates
local norm = normalizeForFlag(cityName)
local norm = normalizeForFlag(cityName)
local ascii = asciiNormalizeForFlag(cityName)
local candidates = {
local candidates = {
norm .. "Flag.png",
norm .. "Flag.png",
ascii .. "Flag.png",
cityName:gsub(" ", "") .. "Flag.png",
cityName:gsub(" ", "") .. "flag.png",
}
}
 
for _, f in ipairs(candidates) do
for _, fn in ipairs(candidates) do
if fileExists(f) then
if fileExists(fn) then
local mk = string.format("[[File:%s|20px]]", f)
local mk = string.format("[[File:%s|20px]]", fn)
cityFlagMarkupCache[cityName] = mk
cityFlagMarkupCache[cityName] = mk
return mk
return mk
Line 333: Line 324:
end
end


-- 3) fallback to ITU
-- 3) fallback
local mk = string.format("[[File:%s|20px]]", DEFAULT_CITY_FLAG_FILE)
local mk = string.format("[[File:%s|20px]]", DEFAULT_CITY_FLAG_FILE)
cityFlagMarkupCache[cityName] = mk
cityFlagMarkupCache[cityName] = mk
Line 340: Line 331:


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- 5) CORRIDORS / ROLE HINTS (hard-coded)
-- 5) ORG UNITS (Missionary echelons)
--------------------------------------------------------------------------------
local RAIL = {
["Lykopolis"]=true, ["Lewisburg"]=true, ["Bashkim"]=true, ["Somniumpolis"]=true,
["Erythros"]=true, ["Vaeringheim"]=true, ["Symphonara"]=true, ["Skýrophos"]=true,
["Norsolyra"]=true, ["Pelagia"]=true, ["Riddersborg"]=true, ["Sårensby"]=true,
}
local FERRY = {["Jogi"]=true, ["Ardclach"]=true}
 
--------------------------------------------------------------------------------
-- 6) MISSIONARY ORGANIZATION (sizes)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local org = {
local org = {
Lampade     = {size=3,  code="L"},
Lampade   = { code="L", size=8  },
Hetairoi   = {size=6,  code="H"},
Hetairoi   = { code="H", size=12 },
Dodekade   = {size=12,  code="D"},
Dodekade   = { code="D", size=18 },
Kleisthenes = {size=25,  code="K"},
Kleisthenes= { code="K", size=25 },
Stoa       = {size=50,  code="S"},
Stoa       = { code="S", size=40 },
Thiasos     = {size=100, code="T"},
Thiasos   = { code="T", size=60 },
}
}


Line 369: Line 350:
return pickFromList(nextInt, {"Kleisthenes","Stoa","Thiasos"}, nil)
return pickFromList(nextInt, {"Kleisthenes","Stoa","Thiasos"}, nil)
end
end
end
--------------------------------------------------------------------------------
-- 6) ORDER ICONS (optional; safe if file missing)
--------------------------------------------------------------------------------
local orderIconFile = {
["Ordo Solis Invicti"]        = "OrdoSolisInvicti.png",
["Order of Aurora Mystica"]  = "OrderOfAuroraMystica.png",
["Conclave Illuminara"]      = "ConclaveIlluminara.png",
["Sanctum Delphica"]          = "SanctumDelphica.png",
["Sanctum Vitalis"]          = "SanctumVitalis.png",
["Pharmacon Sect"]            = "PharmaconSect.png",
["Mystery of the Verdant Embrace"] = "VerdantEmbrace.png",
["Court of the Ironclad"]    = "CourtOfTheIronclad.png",
["Reverie Nebulous"]          = "ReverieNebulous.png",
["Azure Sentinel Sect"]      = "AzureSentinelSect.png",
["Temple Aprobelle"]          = "TempleAprobelle.png", -- NEW
}
local function getOrderIconMarkup(orderName)
local f = orderIconFile[orderName]
if f and fileExists(f) then
return string.format("[[File:%s|18px]] ", f)
end
return ""
end
end


Line 388: Line 394:
T("Dawn rites and public illumination drill to restore civic clarity and discourage rumor cascades in market wards.","green",{"public","education","service"}),
T("Dawn rites and public illumination drill to restore civic clarity and discourage rumor cascades in market wards.","green",{"public","education","service"}),
T("Temple-school literacy session paired with cosmology instruction and shrine maintenance inspections.","green",{"education","shrine","service"}),
T("Temple-school literacy session paired with cosmology instruction and shrine maintenance inspections.","green",{"education","shrine","service"}),
T("Solar route inspection at major junctions; traffic etiquette posted for pilgrims and commuters.","green",{"transit","rail","public"}),
T("Solar route inspection at major junctions; traffic etiquette posted for pilgrims and commuters.","green",{"transit","public"}),
T("Sun-pledge renewal for civic clerks; oath sheets sealed and logged with Temple Bank scribes.","green",{"governance","audit","service"}),
T("Sun-pledge renewal for civic clerks; oath sheets sealed and logged with Temple Bank scribes.","green",{"governance","audit"}),
T("Public lamp and beacon blessing for tunnel nodes; wayfinding clarified for night labor shifts.","green",{"infrastructure","transit","industry"}),
T("Public lamp and beacon blessing for tunnel nodes; wayfinding clarified for night labor shifts.","green",{"infrastructure","transit"}),
T("Radiance audit of public lamps, mirrored courts, and shrine sightlines; repair orders issued under Temple Bank stipend.","yellow",{"infrastructure","audit","service"}),
T("Radiance audit of public lamps, mirrored courts, and shrine sightlines; repair orders issued under Temple Bank stipend.","yellow",{"infrastructure","audit"}),
T("Solar Circle consecration for a new neighborhood shrine, with crowd choreography and compliance wardens.","yellow",{"shrine","crowd","public"}),
T("Solar Circle consecration for a new neighborhood shrine, with crowd choreography and compliance wardens.","yellow",{"shrine","crowd"}),
T("Counter-broadsheet sermon cycle following unsigned pamphlets; witness interviews conducted with local elders.","yellow",{"heresy","investigation","public"}),
T("Counter-broadsheet sermon cycle following unsigned pamphlets; witness interviews conducted with local elders.","yellow",{"investigation","public"}),
T("Joint patrol-litany along rail platforms to reduce theft and panic during peak transit hours.","yellow",{"rail","security","crowd"}),
T("Joint patrol-litany along rail platforms to reduce theft and panic during peak transit hours.","yellow",{"rail","security"}),
T("Emergency morale procession after night unrest; hymn leadership and calm restoration at transit choke points.","red",{"unrest","crowd","transit"}),
T("Emergency morale procession after night unrest; hymn leadership and calm restoration at transit choke points.","red",{"unrest","crowd","transit"}),
T("Rapid de-escalation team embedded in a contentious council session; staged ‘clarity interval’ to prevent walkouts.","red",{"governance","mediation"}),
}
}
},
},
Line 402: Line 409:
link=orderLink("Eos (Reformed Stripping Path)","Order_of_Aurora_Mystica","Order of Aurora Mystica"),
link=orderLink("Eos (Reformed Stripping Path)","Order_of_Aurora_Mystica","Order of Aurora Mystica"),
tasks={
tasks={
T("Sunrise vigil and reconciliation rite for competing guilds; signed harmony pledges archived by Temple Bank clerks.","green",{"mediation","guild","service"}),
T("Sunrise reconciliation rite for competing guilds; signed harmony pledges archived by Temple Bank clerks.","green",{"mediation","guild"}),
T("Dawn-watch observatory check and omen bulletin issued to reduce panic and stabilize labor shifts.","green",{"omen","public","service"}),
T("Dawn-watch observatory check and omen bulletin issued to reduce panic and stabilize labor shifts.","green",{"omen","public"}),
T("Quiet home-visits to recent migrants; orientation blessings and sanctioned neighborhood guides issued.","green",{"public","service","mediation"}),
T("Neighborhood ‘first-light’ counseling circle for strained households; referrals placed with shrine caretakers.","green",{"care","mediation"}),
T("Rooftop shrine upkeep and dawn-banner replacement; civic lanes kept clear for morning processions.","green",{"shrine","infrastructure","public"}),
T("Public blessings for early-shift workers at canal gates; accident-prevention guidance posted.","green",{"public","infrastructure"}),
T("Pilgrim flow timing review for a dawn festival; route markers placed to prevent crush points.","green",{"festival","crowd","public"}),
T("Aurora choir service at hospital ward; morale uplift paired with practical volunteer roster coordination.","green",{"health","service"}),
T("Civic blessing for new housing blocks; routing of foot-traffic to prevent crush points and misdirection.","yellow",{"crowd","infrastructure","service"}),
T("Targeted rumor-clearing procession after a false ‘divine abandonment’ claim; witness statements collected.","yellow",{"rumor","investigation"}),
T("Counter-heresy sermon cycle following illicit broadsides; witness interviews conducted with local elders.","yellow",{"heresy","investigation","public"}),
T("Crowd choreography support at a dawn festival to prevent stampedes and reduce pickpocket opportunities.","yellow",{"crowd","festival"}),
T("Dawn mediation under escort after a workplace dispute threatens walkouts; settlement notarized.","yellow",{"mediation","industry","governance"}),
T("Joint patrol with civic stewards through tunnel districts; wayfinding, lighting, and intimidation checks.","yellow",{"security","infrastructure"}),
T("Omen-risk advisory for coastal weather or fog days; port stewards briefed and signage posted.","yellow",{"port","public","service"}),
T("Emergency reconciliation summit after a street brawl between factions; formal truce terms read aloud.","red",{"unrest","mediation"}),
T("Rapid-response pacification after arson rumor; dawn procession under guard with emergency stipend release.","red",{"unrest","arson","crowd"}),
T("Crisis dawn vigil after violent night incident; grief support, calm messaging, and ‘no retaliation’ pledge drive.","red",{"crisis","public","grief"}),
T("Stabilization briefings for district clerks following audit findings; correction plan countersigned.","yellow",{"audit","governance"}),
}
}
},
},


{key="HSA", name="Harmony Sanctum",
{key="CIC", name="Conclave Illuminara",
link=orderLink("Micras (Reformed Stripping Path)","Harmony_Sanctum","Harmony Sanctum"),
link=orderLink("Tarsica (Reformed Stripping Path)","Conclave_Illuminara","Conclave Illuminara"),
tasks={
T("Neighborhood mediation session for family disputes and trade-feud spillover; binding accords notarized.","green",{"mediation","service"}),
T("Harmony clinic for stress and rumor contagion; quiet-room rotations and guided breathing rites.","green",{"health","rumor","service"}),
T("Queue-discipline coaching at relief points; volunteers trained to prevent confrontations.","green",{"order","queue","relief"}),
T("Workplace concord circle for service districts; complaint channels clarified and posted.","green",{"service","mediation","governance"}),
T("Inter-shrine courtesy visit to reduce rivalry; shared calendar commitments established.","green",{"balance","mediation","public"}),
T("Loyalty and morale audit of a civic office after complaint surge; corrective training scheduled.","yellow",{"audit","governance","service"}),
T("Containment of a cult schism meeting; supervised dialogue and sanction warnings delivered.","yellow",{"schism","containment","public"}),
T("Rumor forensics on a circulating ‘forbidden rite’ claim; witness tree compiled and forwarded.","yellow",{"rumor","investigation","public"}),
T("Crowd-stabilization drill for festival season; steward assignments and safe exits mapped.","yellow",{"festival","crowd","public"}),
T("Major crowd-stabilization operation after riot signals; staged dispersal with peace-liturgy and escorts.","red",{"riot","crowd","public"}),
}
},
 
{key="IGA", name="Ignis Aeternum",
link=orderLink("Pyros (Reformed Stripping Path)","Ignis_Aeternum","Ignis Aeternum"),
tasks={
tasks={
T("Controlled fire blessing for workshops; safety rite doubled as inspection of fuel and storage.","green",{"industry","safety"}),
T("Night-sky lecture and calendar correction posted to calm public anxiety about omens and eclipses.","green",{"education","omen"}),
T("Kiln-and-forge consecration for a new artisan street; insurance seals verified.","green",{"industry","infrastructure","service"}),
T("Quiet meditation ward established at a busy shrine; controlled breathing protocols taught to visitors.","green",{"care","public"}),
T("Firebreak marking for dry-season hills; public notices issued to farmers and herders.","green",{"agri","fire","public"}),
T("Lantern-route inspection for canal patrols; reflective markers placed to reduce night accidents.","green",{"infrastructure","transit"}),
T("Hearth-blessing round for crowded tenements; cooking hazards corrected with wardens.","green",{"public","safety","service"}),
T("Dream-space audit: mirror pools cleaned, offerings catalogued, and safety signage posted.","green",{"shrine","audit"}),
T("Candle and incense quality check at shrine vendors; adulterants rejected and logged.","green",{"shrine","audit","service"}),
T("Star-chart reading for civic council; guidance recorded for later reference by scribes.","green",{"governance","education"}),
T("Purification burn at a compromised shrine-store; ash sealed and catalogued for Temple Bank disposal.","yellow",{"contam","shrine","service"}),
T("Investigation of counterfeit ‘moon tokens’ being sold to pilgrims; vendor interviews and seizure log.","yellow",{"investigation","trade"}),
T("Investigation of incendiary threats near a gate-temple; firebreak plan issued to local wardens.","yellow",{"arson","investigation","security"}),
T("Controlled night gathering after panic rumor; calm-litany enforced, dispersal plan coordinated with stewards.","yellow",{"rumor","crowd"}),
T("Explosive-storage compliance check near port or rail yards; seals renewed and routes revised.","yellow",{"rail","port","security","industry"}),
T("Escort for a sensitive nocturnal rite during unrest; perimeter observers posted.","red",{"security","unrest"}),
T("Emergency response to active arson or explosive rumor; sanctified suppression teams deployed with escorts.","red",{"arson","crisis","security"}),
T("Emergency anti-panic messaging after a ‘demonic sign’ claim; script distributed to shrine speakers.","red",{"crisis","rumor"}),
T("Post-incident purification circuit across multiple oratories; public confidence restored by nightfall.","red",{"incident","public","crowd"}),
T("Coordinated blackout safety drill (lanterns, guides, canal warning ropes) during storm season.","yellow",{"infrastructure","safety"}),
}
}
},
},


{key="CHS", name="Celestial Harmony Sect",
{key="SCD", name="Sanctum Delphica",
link=orderLink("Indigo (Reformed Stripping Path)","Celestial_Harmony_Sect","Celestial Harmony Sect"),
link=orderLink("Sisera (Reformed Stripping Path)","The_Cult_of_Sanctum_Delphica","Sanctum Delphica"),
tasks={
tasks={
T("Diplomatic courtesy visit to a city council; alignments review and oath renewal with minimal spectacle.","green",{"diplomacy","governance","service"}),
T("Archive custody inspection: artifact seals verified, witness ledger updated, and storage humidity checked.","green",{"archive","audit"}),
T("Star-chart instruction for apprentices; quiet observatory checks and record sealing.","green",{"education","astronomy","service"}),
T("Scribe training on doctrinal custody chain; convoy paperwork rehearsed for shrine transfers.","green",{"education","logistics"}),
T("Inter-city liaison packet prepared for corridor towns; travel permissions confirmed.","green",{"rail","service","governance"}),
T("Pilgrim etiquette workshop at a library shrine; damage prevention and quiet-zone enforcement.","green",{"public","education"}),
T("Boundary-harmony rite at a district line; neighbor shrines exchange tokens under witness.","green",{"balance","public","mediation"}),
T("Indexing confiscated pamphlets for signature motifs; forwarded to Temple Bank clerks for correlation.","yellow",{"investigation","archive"}),
T("Calm briefing for merchant captains on festival timing; reduce congestion and rumor spread.","green",{"port","service","public"}),
T("Witness interviews after shrine vandalism; sworn statements taken and sealed.","yellow",{"investigation","security"}),
T("Faction-balance mediation between rival shrines; written compromise placed in Temple Bank archive.","yellow",{"balance","mediation","archive"}),
T("Doctrinal quarantine assistance: entry controls, signage, and rumor suppression around a restricted ward.","yellow",{"security","rumor"}),
T("Rumor forensics on ‘forbidden rite’ claims; witness tree compiled and forwarded to Temple authorities.","yellow",{"rumor","investigation","public"}),
T("Escort a sensitive artifact transfer under heightened risk; perimeter rites and route denial markers deployed.","red",{"security","logistics"}),
T("Quiet arbitration after contract dispute between a guild and a council office.","yellow",{"service","governance","mediation"}),
T("Emergency custody lockdown after attempted breach; rapid relocation plan activated.","red",{"crisis","security","archive"}),
T("Transit coordination for high-profile delegates; platform escorts and crowd lanes established.","yellow",{"rail","crowd","security"}),
T("Court hearing preparation for accused desecrators; evidence packet assembled with scribal counsel.","yellow",{"governance","audit"}),
T("High-risk stabilization after cult confrontation; negotiators embedded with escorts to prevent escalation.","red",{"confrontation","crowd","security"}),
T("Night watch rotation at archive stacks following arson threat; coordination with civic guards.","red",{"security","unrest"}),
}
},
 
{key="GGS", name="Guild of Golden Shadows",
link=orderLink("Chrysos (Reformed Stripping Path)","Guild_of_Golden_Shadows","Guild of Golden Shadows"),
tasks={
T("Discrete audit of offertory ledgers and voucher seals; minor variances reconciled quietly.","green",{"finance","audit","service"}),
T("Merchant vetting interviews for a sensitive consignment; anonymity rites certified.","green",{"merchant","security","service"}),
T("Port-side seal verification for sacred cargo; chain-of-custody stamped and witnessed.","green",{"port","finance","security"}),
T("Rail consignment escort paperwork checked; handover points confirmed with station stewards.","green",{"rail","service","security"}),
T("Quiet fraud-prevention briefing for shrine treasurers; common forgery patterns circulated.","green",{"finance","education","service"}),
T("Counter-counterfeit sweep targeting forged stamps and false Temple consignments; evidence boxed and logged.","yellow",{"forgery","investigation","finance"}),
T("Debt dispute arbitration for a sanctioned charity line; terms rewritten and sealed.","yellow",{"finance","mediation","service"}),
T("Watchlist update for suspected smugglers using festival crowds; surveillance plan filed.","yellow",{"security","crowd","port"}),
T("Recovery detail for stolen reliquary property; surveillance and lawful seizure under civic warrant.","red",{"theft","security","investigation"}),
T("Crisis finance stabilization after panic; emergency stipend routing and rumor suppression coordination.","red",{"panic","finance","service"}),
}
},
 
{key="ASS", name="Azure Sentinel Sect",
link=orderLink("Thalassa (Reformed Stripping Path)","Azure_Sentinel_Sect","Azure Sentinel Sect"),
tasks={
T("Canal and fountain sanctification circuit; water-quality blessings paired with public reassurance.","green",{"water","public","service"}),
T("Harbor hygiene briefing for fish markets; cold-chain and wash stations checked.","green",{"port","health","service"}),
T("Flood-gate inspection rite; maintenance tickets issued and signed by civic stewards.","green",{"infrastructure","water","service"}),
T("Ferry pier blessing and rope inspection; boarding lines reorganized to reduce accidents.","green",{"ferry","port","public"}),
T("Reservoir vigil for drought days; ration etiquette posted at distribution points.","green",{"water","relief","public"}),
T("Harbor-side watch for sabotage indicators; shrine-wardens trained on lock and pier checks.","yellow",{"port","sabotage","security"}),
T("Storm or flood readiness rite; evacuation hymn routes verified with civic stewards.","yellow",{"flood","infrastructure","public"}),
T("Waterborne rumor containment bulletin; discourage panic buying and hoarding.","yellow",{"rumor","service","public"}),
T("Quarantine support for suspected waterborne illness; controlled distribution points blessed and monitored.","red",{"outbreak","water","health"}),
T("Major incident response: water infrastructure damage; emergency sanctification and ration-order enforcement.","red",{"infrastructure","crisis","public"}),
}
},
 
{key="REN", name="Reverie Nebulous",
link=orderLink("Nephele (Reformed Stripping Path)","Reverie_Nebulous","Reverie Nebulous"),
tasks={
T("Dream-interpretation clinic to reduce anxiety; rumor narratives redirected into sanctioned symbolism.","green",{"dream","rumor","service"}),
T("Nocturne counseling for insomnia cluster; soft-lit rites and chant rotation schedules.","green",{"health","sleep","service"}),
T("Public guidance sheet on lawful visions vs. sedition pamphlets; distributed at shrines.","green",{"education","public","service"}),
T("Quiet household visits after a death or displacement; stabilize grief cycles with sanctioned imagery.","green",{"service","health","public"}),
T("Festival mood stewardship: prevent illicit revel by offering sanctioned evening rites.","green",{"festival","public","morale"}),
T("Investigation of forbidden homily broadcasts; witness interviews and calm messaging drafted.","yellow",{"broadcast","investigation","service"}),
T("Rumor cascade mapping in service districts; identify the first carrier nodes.","yellow",{"rumor","investigation","service"}),
T("Containment of mass-vision panic; staged public reassurance with tightly controlled procession.","red",{"panic","crowd","public"}),
T("Stabilization after hallucination-linked violence; liaison with Harmony Sanctum for long-tail care.","red",{"violence","health","service"}),
T("Emergency night curfew-support rite; safe corridors marked to reduce shadow-fear incidents.","yellow",{"security","public","service"}),
}
},
 
{key="EOF", name="Eon Fellowship",
link=orderLink("Glinos (Reformed Stripping Path)","Eon_Fellowship","Eon Fellowship"),
tasks={
T("Cold-weather relief checks and ration fairness review; storage seals verified.","green",{"relief","logistics","service"}),
T("Infrastructure endurance audit: roofs, walls, and bridges; damage triage list issued.","green",{"infrastructure","audit","industry"}),
T("Shelter staffing rotation planned for storm season; volunteer rosters sealed.","green",{"relief","service","governance"}),
T("Rail convoy support brief for frontier deliveries; pacing and rest points set.","green",{"rail","logistics","service"}),
T("Queue discipline at distribution points; conflict triggers reduced through signage.","green",{"order","queue","public"}),
T("Conflict de-escalation for resource disputes; binding queue rules posted at distribution points.","yellow",{"order","queue","governance"}),
T("Emergency shelter activation after storm loss; warm-hall rites and controlled entry.","red",{"storm","relief","public"}),
T("Frontier outpost morale visit; endurance rites and supply accountability review.","yellow",{"frontier","audit","service"}),
T("Bridge closure coordination after damage report; detours posted and escorted.","yellow",{"infrastructure","transit","public"}),
T("High-risk escort for frontier convoys under threat; endurance cadres embedded with wardens.","red",{"frontier","security","logistics"}),
}
},
 
{key="UOR", name="Order of the Umbral Oracle",
link=orderLink("Noctis (Reformed Stripping Path)","Order_of_the_Umbral_Oracle","Order of the Umbral Oracle"),
tasks={
T("Night watch and omen-reading in the temple quarter; guidance slips sealed for officials.","green",{"omen","security","service"}),
T("Shadow-lane safety marking near canals and alleys; lantern placement logged.","green",{"public","security","transit"}),
T("Quiet counsel for a magistrate; truth-testing rite witnessed by two scribes.","green",{"governance","inquiry","service"}),
T("Missing-persons intake desk hosted at a shrine; reports standardized and forwarded.","green",{"service","investigation","public"}),
T("Nighttime rumor monitoring in taverns and stations; early interventions logged.","green",{"rumor","rail","service"}),
T("Discrete consultation for a magistrate; truth-testing rite witnessed by two scribes.","yellow",{"governance","inquiry","audit"}),
T("Shadow-route mapping after disappearances; safe-path markers installed with civic approval.","yellow",{"security","transit","public"}),
T("Evidence sealing after a midnight incident; chain-of-custody verified for Temple authorities.","yellow",{"investigation","security","service"}),
T("Emergency intervention after ‘demonic sign’ sightings; controlled crowd handling and ward placement.","red",{"demonic","panic","crowd"}),
T("High-risk extraction of a threatened cantor; night escort to secure sanctuary.","red",{"abduction","security","service"}),
}
},
 
{key="MVE", name="Mystery of the Verdant Embrace",
link=orderLink("Opsithe (Reformed Stripping Path)","Mystery_of_the_Verdant_Embrace","Mystery of the Verdant Embrace"),
tasks={
T("Harvest blessing and storage rite; spoilage prevention guidance issued to granaries.","green",{"agriculture","harvest","agri"}),
T("Garden-shrine restoration day with community labor; devotional planting aligned to calendar.","green",{"garden","shrine","agri"}),
T("Seed and tool stipend audit for smallholders; vouchers revalidated.","green",{"audit","agri","service"}),
T("Irrigation channel blessing and silt-clearing volunteer day; water etiquette posted.","green",{"water","agri","public"}),
T("Market stall purity checks for produce; vendors instructed on safe storage.","green",{"public","health","agri"}),
T("Food shortage mediation and ration arbitration; Temple Bank stipend adjustments proposed.","yellow",{"shortage","logistics","agri"}),
T("Pest or blight containment rites; field inspections with farmers’ associations.","yellow",{"blight","agriculture","agri"}),
T("Livestock procession routing for festival markets; prevent crush and theft.","yellow",{"crowd","festival","agri"}),
T("Famine-risk surge response; emergency distribution queues sanctified and monitored.","red",{"famine","relief","agri"}),
T("Emergency containment of contaminated grain rumor; sample testing coordinated with Sanctum Vitalis.","yellow",{"rumor","health","agri"}),
}
},
 
{key="CIL", name="Conclave Illuminara",
link=orderLink("Tarsica (Reformed Stripping Path)","Conclave_Illuminara","Conclave Illuminara"),
tasks={
T("Moonlit instruction circle; eclipse-watch protocol rehearsed with apprentice scribes.","green",{"lunar","education","service"}),
T("Night-sky bulletin posted to reduce panic during overcast weeks; lawful interpretations provided.","green",{"public","rumor","service"}),
T("Observatory instrument check; calibration seals signed and archived.","green",{"archive","audit","service"}),
T("Lantern route blessing for tunnels and arcades; wayfinding clarified for late shifts.","green",{"transit","public","service"}),
T("Quiet archive preservation: scroll humidity checks and secure storage review.","green",{"archive","service","audit"}),
T("Archive sealing of sensitive liturgies; access logs reviewed for anomalies.","yellow",{"archive","audit","security"}),
T("Public lantern rite to counter rumor cascades; controlled routes and timing set with wardens.","yellow",{"rumor","crowd","public"}),
T("Transit hub advisory after night panic; crowd lanes re-drawn and posted.","yellow",{"rail","crowd","transit"}),
T("Emergency omen bulletin after night panic; official statement drafted and delivered.","red",{"panic","public","service"}),
T("High-risk protection of an observatory site under threat; wards reinforced and watchers stationed.","red",{"threat","security","service"}),
}
}
},
},
Line 578: Line 458:
link=orderLink("Plateau (Reformed Stripping Path)","Sanctum_Vitalis","Sanctum Vitalis"),
link=orderLink("Plateau (Reformed Stripping Path)","Sanctum_Vitalis","Sanctum Vitalis"),
tasks={
tasks={
T("Clinic day: basic health screenings and hygiene instruction; water points checked.","green",{"health","public","service"}),
T("Clinic resupply run: bandage, tonic, and clean-water amphorae inventory reconciled with Temple Bank vouchers.","green",{"health","logistics"}),
T("Sanctioned healer coordination meeting; triage roles rehearsed for outbreaks.","green",{"training","health","service"}),
T("Water-quality checks at canal taps; chlorination guidance posted for household stewards.","green",{"health","infrastructure"}),
T("Hygiene signage deployment at markets; handwash stations verified.","green",{"public","health","service"}),
T("School hygiene lecture (handwashing, food safety) paired with gentle blessing service.","green",{"health","education"}),
T("Nutrition counseling for ration lines; supplement vouchers distributed under Temple Bank authorization.","green",{"nutrition","relief","service"}),
T("Routine fever screening at a market gate during festival season; referral slips issued.","green",{"health","public"}),
T("Quiet home checks for vulnerable households; prevent panic spirals after illness rumors.","green",{"service","health","rumor"}),
T("Temporary clinic tent established near shrine square; triage lines marked and staffed.","yellow",{"health","crowd"}),
T("Supplement and ration fortification distribution under Temple Bank authorization.","yellow",{"nutrition","relief","service"}),
T("Contamination rumor assessment: test kits deployed to prove/disprove claims and prevent panic.","yellow",{"health","rumor"}),
T("Outbreak protocol rehearsal with civic healers; spacing rules posted at shrines.","yellow",{"outbreak","training","public"}),
T("Mass casualty support after an accident; triage, referral, and grief counseling coordination.","red",{"crisis","health"}),
T("Quarantine lane setup drill at a terminal or station; entry control rehearsed.","yellow",{"rail","security","health"}),
T("Quarantine support in a dormitory ward; supplies custody and compliance checks.","red",{"crisis","health","audit"}),
T("Active outbreak response: triage lanes established; quarantine support and calm messaging.","red",{"outbreak","crisis","public"}),
T("Joint training with local stewards on heat illness prevention and hydration discipline.","yellow",{"health","public"}),
T("Mass-casualty contingency drill after violence reports; intake points and transport routes mapped.","red",{"violence","logistics","service"}),
T("Night clinic staffing for unrest injuries; neutral triage posture announced to reduce retaliation violence.","red",{"unrest","health"}),
}
}
},
},


{key="ALB", name="Temple Alabaster",
{key="PHA", name="Pharmacon Sect",
link=orderLink("Ivory (Reformed Stripping Path)","Temple_Alabaster","Temple Alabaster"),
link=orderLink("Styx (Reformed Stripping Path)","The_Pharmacon_Sect","Pharmacon Sect"),
tasks={
tasks={
T("Purity inspection of ritual vessels and incense stock; minor replacements approved.","green",{"purity","shrine","service"}),
T("Dispensary audit: dosage seals verified; controlled compounds logged and countersigned.","green",{"health","audit"}),
T("Doctrinal reading circle for novices; sanctioned texts distributed with margin notes.","green",{"education","service","public"}),
T("Field compounding session with local herb vendors; purity protocols enforced.","green",{"health","industry"}),
T("Shrine inventory check for relic storage; locks and witness rules enforced.","green",{"security","shrine","audit"}),
T("Clinic support for chronic cases; treatment plans documented in Temple Bank ledgers.","green",{"health","service"}),
T("Quiet correction of unsanctioned household rites; guidance issued without spectacle.","green",{"service","public","education"}),
T("Water purification engineering consult at a canal gate; intake screens inspected for sabotage.","yellow",{"infrastructure","investigation"}),
T("Blessing of new white-draped oratory; entry etiquette posted to reduce disorder.","green",{"shrine","public","service"}),
T("Extraction team dispatched to secure a medically useful mineral/resin source under escort.","yellow",{"logistics","security"}),
T("Doctrinal correction session after unsigned broadsides; sanctioned texts distributed.","yellow",{"heresy","education","public"}),
T("Emergency antidote preparation after suspected poisoning event; vendor supply chain traced.","red",{"crisis","investigation","health"}),
T("Sanctuary security review for valuables; locking discipline and witness rules enforced.","yellow",{"security","shrine","service"}),
T("Outbreak response: isolation guidance, symptom screening, and clinic routing control at transit nodes.","red",{"crisis","health","public"}),
T("Investigation of tainted incense vendors; samples seized and chain-of-custody logged.","yellow",{"poison","investigation","service"}),
T("Rumor suppression briefing about ‘cursed medicine’; evidence packet and calm script issued.","yellow",{"rumor","public"}),
T("Emergency purification after poisoned incense discovery; wards placed and offenders sought.","red",{"poison","crisis","security"}),
T("Hospital ward reinforcement for Eidolan exposure aftershock cases; monitoring schedule created.","red",{"crisis","health"}),
T("High-risk protection of a central temple during unrest; entry control and calm liturgy.","red",{"unrest","security","crowd"}),
T("Post-incident debrief with Temple Aprobelle on panic dynamics; messaging updated for clinics.","yellow",{"analysis","rumor"}),
}
}
},
},


{key="CIC", name="Court of the Ironclad",
{key="VEG", name="Mystery of the Verdant Embrace",
link=orderLink("Fenrir (Reformed Stripping Path)","Court_of_the_Ironclad","Court of the Ironclad"),
link=orderLink("Thalassa (Reformed Stripping Path)","Mystery_of_the_Verdant_Embrace","Mystery of the Verdant Embrace"),
tasks={
tasks={
T("Discipline drill for shrine wardens; non-lethal control and restraint practices.","green",{"training","security","service"}),
T("Irrigation ritual + practical canal cleanup; silt removed and flow restored with steward crews.","green",{"infrastructure","agriculture"}),
T("Escort for a threatened courier; route cleared and handover documented.","green",{"escort","security","service"}),
T("Seed blessing for market gardeners; pest-prevention guidance distributed.","green",{"agriculture","public"}),
T("Patrol coordination with civic guards at terminals; hand signals and cordon rules standardized.","green",{"rail","security","service"}),
T("Wetland path maintenance; travel warnings posted to prevent drownings.","green",{"safety","infrastructure"}),
T("Gatehouse posture review for festival season; crowd lanes chalked and posted.","green",{"festival","crowd","security"}),
T("Waterbank audit at an aqueduct; leakage reported and repair crews coordinated.","yellow",{"audit","infrastructure"}),
T("Weapons discipline check for attached wardens; compliance logs signed.","green",{"audit","security","service"}),
T("Field mediation over irrigation rights; shared schedule drafted and signed.","yellow",{"mediation","governance"}),
T("Guard coordination in a frontier district; watch rotations synchronized with civic militia.","yellow",{"militia","security","frontier"}),
T("Emergency flood relief coordination: sandbag lines, food staging, and shelter support.","red",{"crisis","relief"}),
T("Immediate reinforcement during riot signals; cordons established and safe egress opened.","red",{"riot","crowd","security"}),
T("Brushfire risk patrol in dry districts; controlled burns prohibited and notices issued.","yellow",{"fire","public"}),
T("Interdiction rehearsal for barricade incidents; safe corridors mapped.","yellow",{"barricade","security","public"}),
T("Post-storm debris clearance with shrine volunteers; hazard markings placed.","yellow",{"safety","infrastructure"}),
T("High-risk interdiction support for a seized oratory; secure perimeter until authorities arrive.","red",{"siege","security","public"}),
T("Disease vector mitigation in swamp edges; standing water treated and guidance posted.","yellow",{"health","infrastructure"}),
T("Protection detail for water or rail infrastructure after sabotage rumor; sentries posted.","yellow",{"sabotage","infrastructure","security"}),
T("Crisis canal breach response: temporary closure, reroute of water flow, and crowd calming.","red",{"crisis","infrastructure","crowd"}),
}
}
},
},


{key="ACC", name="Accord Concordia",
{key="CICL", name="Court of the Ironclad",
link=orderLink("Lukedu (Reformed Stripping Path)","Accord_Concordia","Accord Concordia"),
link=orderLink("Fenrir (Reformed Stripping Path)","Court_of_the_Ironclad","Court of the Ironclad"),
tasks={
tasks={
T("Public harmony dialogue; disputants guided to a signed concord recorded by Temple Bank scribes.","green",{"mediation","public","service"}),
T("Strength-discipline training for youth patrol volunteers; nonviolent restraint emphasized.","green",{"education","security"}),
T("Garden pavilion mediation for family inheritance disputes; settlement sealed.","green",{"mediation","service"}),
T("Public order presence at shrine steps; crowd etiquette enforced with calm posture.","green",{"security","public"}),
T("Civic cooperation workshop for rival guilds; shared calendar commitments set.","green",{"guild","coordination","service"}),
T("Workshop on oath-keeping and duty; pledge cards sealed for civic stewards.","green",{"governance","education"}),
T("Inter-shrine calendar alignment meeting; procession timings standardized.","green",{"balance","public","service"}),
T("Route hardening walk: identify vulnerable alleys, broken lamps, and unsafe crossings.","yellow",{"security","infrastructure"}),
T("Quiet counseling for newly formed neighborhood councils; rules of debate issued.","green",{"governance","education","service"}),
T("Contraband confiscation support at a market stall cluster; evidence logged for Temple Bank clerks.","yellow",{"security","investigation"}),
T("Catacomb Harmonic Communion support: logistics, spacing, and participant verification.","yellow",{"rite","logistics","service"}),
T("High-risk escort for a witness leaving a hearing; safehouse transfer arranged.","red",{"security","crisis"}),
T("Emergency reconciliation after faction clash; mediators embedded with escorts.","red",{"clash","crowd","security"}),
T("Emergency deterrence patrol after riot flare; separation lines and dispersal corridor opened.","red",{"unrest","crowd"}),
T("Transit-lane mediation after station dispute; queues reset and monitored.","yellow",{"rail","queue","public"}),
T("Night watch on canal bridges after sabotage attempt; checkpoint discipline enforced.","yellow",{"security","infrastructure"}),
T("Harmonization response after rumor spike; joint bulletin with Harmony Sanctum circulated.","yellow",{"rumor","service","public"}),
T("Crisis reinforcement at a hospital gate during panic surge; access and queue discipline established.","red",{"crisis","security","health"}),
T("High-risk crowd calming after panic; sound-liturgical dispersal and safe routes enforced.","red",{"panic","crowd","public"}),
T("Post-incident intimidation review; complaints investigated and corrective discipline applied.","yellow",{"audit","governance"}),
}
}
},
},


{key="MSV", name="Mystery of the Stygian Veil",
{key="RNB", name="Reverie Nebulous",
link=orderLink("Styx (Reformed Stripping Path)","Mystery_of_the_Stygian_Veil","Mystery of the Stygian Veil"),
link=orderLink("Nephele (Reformed Stripping Path)","Reverie_Nebulous","Reverie Nebulous"),
tasks={
tasks={
T("Funerary liaison and cemetery vigil support; grief rites standardized.","green",{"funeral","public","service"}),
T("Dream interpretation circle with strict moderation; anxiety de-escalation emphasized.","green",{"care","public"}),
T("Registry update for burials and memorial offerings; records sealed and witnessed.","green",{"archive","audit","service"}),
T("Quiet art therapy session for children after a minor scare; parents briefed on calm routines.","green",{"care","education"}),
T("Transition counseling for displaced families; resettlement blessings recorded.","green",{"displacement","relief","service"}),
T("Public ‘sleep hygiene’ teaching for shift workers; rest scheduling guidance posted.","green",{"health","education"}),
T("Cemetery perimeter inspection; prevent vandalism during festival nights.","green",{"security","public","service"}),
T("Rumor mapping exercise: identify which dreams are spreading a harmful narrative; corrective story deployed.","yellow",{"rumor","analysis"}),
T("Quiet rites for recovered remains after an accident; soothe panic and rumor.","green",{"public","rumor","service"}),
T("Witness interviews after a trance-gathering complaint; compliance conditions issued.","yellow",{"audit","public"}),
T("Investigation of graveyard desecration rumor; ward placement and witness interviews.","yellow",{"desecration","investigation","security"}),
T("Containment support for mass panic dream episode; calm stations, water, and supervised rest zones established.","red",{"crisis","crowd"}),
T("Relic transfer paperwork verified for a shrine move; escorts scheduled.","yellow",{"escort","security","service"}),
T("Emergency counseling for families after death; grief rites paired with practical aid referrals.","red",{"grief","crisis"}),
T("Emergency containment after death-cult provocation; controlled rites to prevent escalation.","red",{"provocation","crowd","security"}),
T("Nighttime crowd calming near a transit node; chants regulated to prevent stampede behavior.","yellow",{"crowd","transit"}),
T("High-risk escort of relics through hostile streets; secure route planning with wardens.","red",{"escort","security","public"}),
T("High-risk ‘dream breach’ report assessed with Aprobelle liaisons; misinformation suppressed.","red",{"crisis","rumor"}),
T("Post-violence reconciliation vigil at a canal memorial; prevent retaliation cycles.","yellow",{"violence","public","mediation"}),
T("Post-event debrief: public narrative edited to reduce retaliatory impulses; speaker training conducted.","yellow",{"public","analysis"}),
}
}
},
},


{key="SYF", name="Sylvan Fellowship",
{key="ASS", name="Azure Sentinel Sect",
link=orderLink("Faun (Reformed Stripping Path)","Sylvan_Fellowship","Sylvan Fellowship"),
link=orderLink("Aether (Reformed Stripping Path)","Azure_Sentinel_Sect","Azure Sentinel Sect"),
tasks={
tasks={
T("Forest-edge shrine maintenance and trail safety check; minor repairs logged.","green",{"trail","shrine","agri"}),
T("Waterfront perimeter walk and ‘blue line’ marking refresh; safety rope checks in canal districts.","green",{"infrastructure","safety"}),
T("Ecology rite with farmers: pest balance and water stewardship guidance.","green",{"ecology","agri","service"}),
T("Port-house briefing on safe embarkation and baggage rules; theft prevention guidance posted.","green",{"public","transit"}),
T("Wildlife boundary notices posted; discourage poaching near sacred groves.","green",{"public","security","agri"}),
T("Storm-drain inspection with civil works; blockages reported and cleared.","green",{"infrastructure","service"}),
T("Community foraging orientation; prevent poisoning and rumor spirals.","green",{"education","health","agri"}),
T("Escort for medical supplies across canal bridges; accountability signatures obtained.","yellow",{"logistics","security"}),
T("Riverbank cleanup day; improve water flow and reduce disease risk.","green",{"water","health","public"}),
T("Harbor rumor suppression: clarify false ‘quarantine’ claims to prevent stampede and hoarding.","yellow",{"rumor","public"}),
T("Containment of illegal logging or poaching rumor; community patrol coordination.","yellow",{"poaching","security","investigation"}),
T("Emergency flood response: evacuation guidance at waterfront shrines; calm channels maintained.","red",{"crisis","relief"}),
T("Fire season preparedness for woodland towns; firebreak rites and evacuation path marking.","yellow",{"wildfire","public","security"}),
T("Search-and-rescue standby after storm warnings; muster roster and equipment check.","yellow",{"safety","crisis"}),
T("Blight liaison with Verdant Embrace; field sampling routes scheduled.","yellow",{"blight","agri","service"}),
T("High-risk escort of officials through crowd at port gate; separation corridor enforced.","red",{"security","crowd"}),
T("Emergency response to wildfire risk; firebreak rites and evacuation path marking.","red",{"wildfire","crisis","public"}),
T("Debris clearance coordination after gale; hazard markings placed on docks.","yellow",{"infrastructure","safety"}),
T("High-risk protection for a threatened grove shrine; cordon and ward reinforcement.","red",{"threat","security","public"}),
T("Crisis harbor closure messaging: distribute official notices; prevent vigilante enforcement.","red",{"crisis","public"}),
}
}
},
},


{key="MRM", name="Mystery of Red Mirth",
-- NEW: Temple Aprobelle
link=orderLink("Silenus (Reformed Stripping Path)","Myster_of_Red_Mirth","Mystery of Red Mirth"),
{key="TAP", name="Temple Aprobelle",
link="[[Temple_Aprobelle|Temple Aprobelle]]",
tasks={
tasks={
T("Morale rite and sanctioned celebration oversight; prevent disorder while restoring joy.","green",{"morale","public","service"}),
T("Festival hygiene audit: vendor protocols verified (ladle rotation, water handling, waste control); seals issued to compliant stalls.","green",{"health","audit","festival","public"}),
T("Festival logistics support; safe routes and water points verified with civic stewards.","green",{"festival","crowd","public"}),
T("Rumor-control bulletin cycle: shrine-leader briefing + posted clarifications to prevent panic narratives from hardening.","green",{"rumor","public","service"}),
T("Sanctioned tavern-chaplain circuit; reduce brawls and theft through presence.","green",{"public","security","service"}),
T("Grief-cadre home visits after a recent accident; de-escalation coaching for relatives and neighborhood stewards.","green",{"grief","mediation","service"}),
T("Reconciliation feast for rival crews; agreements sealed before witnesses.","green",{"mediation","festival","service"}),
T("School immunization coordination: clinic schedule posted, consent ledgers reconciled, and reassurance rites conducted.","green",{"health","school","coordination"}),
T("Public song-rotation at market hours; discourage rumor panic with sanctioned refrains.","green",{"rumor","public","service"}),
T("Clinic-queue stabilization drill: entry metering, calm-litany, and conflict prevention training for volunteers.","yellow",{"queue","crowd","health","order"}),
T("Containment of illicit revel causing unrest; correction by sanctioned rite and fines.","yellow",{"illicit","order","security"}),
T("Loyalty and behavior audit of a district office after complaint surge; corrective training issued with Temple Bank clerk witness.","yellow",{"audit","governance","compliance"}),
T("Crowd-density enforcement for a stadium day; steward posts assigned.","yellow",{"crowd","festival","security"}),
T("Anomaly-response dispatch to a shrine ward after ‘demonic contamination’ claims; care provision paired with narrative discipline.","yellow",{"anomaly","rumor","public","health"}),
T("Emergency morale stabilization after violence; controlled gatherings and escorts.","red",{"violence","crowd","public"}),
T("Pattern-analysis intake: confiscated schism texts and recordings logged, indexed, and forwarded for doctrinal signature correlation.","yellow",{"analysis","schism","security","archive"}),
T("High-risk de-escalation during riot-prone festival; staged dispersal and peace hymns.","red",{"riot","crowd","security"}),
T("Transit node stabilization after crowd surge: calm stations, signage, and steward handoffs established.","yellow",{"transit","crowd","stabilization"}),
T("Post-panic comfort rites near transit hubs; prevent stampedes and retaliatory fights.","yellow",{"panic","rail","crowd"}),
T("Silencing-cadre activated after panic flare; controlled messaging + visible care stations deployed under escort.","red",{"panic","rumor","stabilization"}),
}
T("Emergency stabilization in evacuation/fever center: calming rites, grief triage, and rumor suppression until order restored.","red",{"crisis","health","crowd","stabilization"}),
},
T("Post-incident narrative repair: rumor sources mapped, apology ceremonies arranged, and retaliation prevention pledges collected.","yellow",{"analysis","mediation","public"}),
 
{key="RCA", name="Rex Catonis",
link=orderLink("Cato (Reformed Stripping Path)","Rex_Catonis","Rex Catonis"),
tasks={
T("Civic order inspection: queue rules, market discipline, and shrine traffic flow.","green",{"order","queue","public"}),
T("Courtroom courtesy briefing; witness etiquette and silence rules reinforced.","green",{"governance","public","service"}),
T("Market weights-and-measures rite; deter fraud and stabilize pricing disputes.","green",{"audit","service","public"}),
T("Transit node discipline review; platform lines repainted and stewards assigned.","green",{"rail","order","transit"}),
T("Curfew signage and quiet-hours enforcement brief; reduce night disturbances.","green",{"security","public","service"}),
T("Administrative compliance review after complaints; corrective edicts posted.","yellow",{"compliance","governance","service"}),
T("Guard liaison for a threatened civic hall; protocol rehearsal and watch rotation.","yellow",{"security","governance","service"}),
T("Emergency restoration of order after mob action; escorted dispersal and curfew-litany.","red",{"mob","crowd","security"}),
T("High-risk support to authorities during barricade incident; perimeter discipline enforced.","red",{"barricade","security","public"}),
T("Port and rail dispute arbitration; enforce lawful priority lanes for relief cargo.","yellow",{"port","rail","order","relief"}),
}
},
 
{key="SDL", name="Sanctum Delphica",
link=orderLink("Sisera (Reformed Stripping Path)","Sanctum_Delphica","Sanctum Delphica"),
tasks={
T("Oracle consultation clinic for officials; questions logged and sealed.","green",{"oracle","governance","service"}),
T("Archive and library integrity check; catalog drift corrected and access verified.","green",{"archive","audit","service"}),
T("Public instruction on lawful prophecy vs. pamphlet fraud; leaflets distributed.","green",{"education","public","service"}),
T("Scribe training session; improve recordkeeping for relief and stipend flows.","green",{"education","audit","service"}),
T("Observatory-liaison briefing with Conclave Illuminara; shared bulletins standardized.","green",{"service","archive","public"}),
T("Investigation of forged prophecy pamphlets; counterfeit sources traced.","yellow",{"forgery","investigation","service"}),
T("Archive sealing of a sensitive docket after unrest; access reduced and witnessed.","yellow",{"security","archive","service"}),
T("Emergency counter-homily after loudspeaker seizure; sanctioned address delivered.","red",{"broadcast","crisis","public"}),
T("High-risk protection for a key temple scholar; escort and secure lodging arranged.","red",{"threat","security","service"}),
T("Transit bulletin for corridor cities after omen panic; prevent stampedes and conspiracies.","yellow",{"rail","panic","public"}),
}
},
 
{key="OAI", name="Ordo Amicitia",
link=orderLink("Erasmus (Reformed Stripping Path)","Ordo_Amicitia","Ordo Amicitia"),
tasks={
T("Charity distribution and household check-ins; kindness register updated for stipend support.","green",{"charity","relief","service"}),
T("Food-line volunteer training; prevent cutting and fights through posted rules.","green",{"relief","queue","public"}),
T("Shelter visitation for displaced workers; job referrals routed through civic offices.","green",{"service","relief","governance"}),
T("Agri stipend review for smallholders; vouchers revalidated and extensions logged.","green",{"agri","audit","service"}),
T("Compassion circuit to hospitals and clinics; morale support and supply notes recorded.","green",{"health","service","relief"}),
T("Offering logistics for a major rite; livestock and treasure accounted and sealed.","yellow",{"offering","audit","service"}),
T("Mediation for neighborhood poverty disputes; aid allocations made transparent.","yellow",{"aid","mediation","service"}),
T("Emergency relief surge after incident; soup lines sanctified and guarded.","red",{"relief","crowd","public"}),
T("High-risk protection for a vulnerable procession route; escorts and safe shelters staged.","red",{"procession","security","public"}),
T("Port-side charity intake after a ferry accident; registries updated and families notified.","yellow",{"port","relief","service"}),
}
},
 
{key="TIL", name="Temple Illuminata",
link=orderLink("Nikolaj (Reformed Stripping Path)","Temple_Illuminata","Temple Illuminata"),
tasks={
T("Lamp and beacon maintenance blessing; public wayfinding clarity restored.","green",{"beacon","transit","service"}),
T("Infrastructure illumination audit for tunnels and transit nodes; repair notices issued.","green",{"infrastructure","transit","industry"}),
T("Rail signal blessing at key junctions; reduce accidents and rumor panic.","green",{"rail","transit","public"}),
T("Workshop visit: lantern production quality review; defective stock quarantined.","green",{"industry","audit","service"}),
T("Public lantern-route map refreshed for festival season; prevent crush points.","green",{"festival","crowd","public"}),
T("Investigation of signal tampering near a transport hub; seals replaced and watchers posted.","yellow",{"tamper","security","transit"}),
T("Emergency lighting deployment after sabotage; temporary beacons installed with escorts.","red",{"sabotage","crisis","security"}),
T("High-risk stabilization at a transport hub after panic; controlled flow and sanctuary lanes.","red",{"panic","crowd","transit"}),
T("Port navigation-light inspection for fog days; harbor masters briefed.","yellow",{"port","infrastructure","service"}),
T("Tunnel crowd-discipline drill; stewards assigned and entry metering practiced.","yellow",{"crowd","infrastructure","transit"}),
}
}
},
},
}
}


 
local orderByName = {}
local orderByKey, orderByName, orderKeys, orderNames = {}, {}, {}, {}
for _, o in ipairs(orders) do orderByName[o.name] = o end
for _, o in ipairs(orders) do
orderByKey[o.key] = o
orderByName[o.name] = o
orderKeys[#orderKeys+1] = o.key
orderNames[#orderNames+1] = o.name
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- 7.1) ORDER ICONS (from Module:GeneralPortOperations addLogo mapping)
-- 8) CITY PROFILES (tags + order bias weights)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local orderIconFile = {
local cityProfiles = {
["Order of Aurora Mystica"]         = "Auroramystica.png",
["Vaeringheim"] = {tags={"capital","port","canals"}, weights={OSI=2,OAM=2,CIC=2,SCD=2,SCV=2,ASS=2,TAP=3}},
["Harmony Sanctum"]                = "Harmonysanctum.png",
["Somniumpolis"] = {tags={"wetlands","dream-space","panic-risk"}, weights={RNB=3,SCV=2,PHA=2,TAP=3,OSI=1}},
["Ignis Aeternum"]                 = "Ignis Aeternum.png",
["Luminaria"] = {tags={"dawn","tunnels","enlightenment"}, weights={OAM=3,CIC=2,OSI=2,TAP=2}},
["Celestial Harmony Sect"]        = "HarmonySect.png",
["Lunalis Sancta"] = {tags={"libraries","court","ritual"}, weights={SCD=3,CIC=2,TAP=2,OSI=1}},
["Guild of Golden Shadows"]       = "GoldenShadows.png",
["Delphica"] = {tags={"archive","custody"}, weights={SCD=3,TAP=2,CIC=1}},
["Azure Sentinel Sect"]            = "AzureSentinel.png",
["Acheron"] = {tags={"canyonlands","medicine"}, weights={PHA=3,SCV=2,TAP=1,CICL=1}},
["Reverie Nebulous"]               = "RevNeb.png",
["Thermosalem"] = {tags={"springs","crowds"}, weights={SCV=2,PHA=2,TAP=2,ASS=1}},
["Eon Fellowship"]                = "EonFel.png",
["Pyralis"] = {tags={"festival","risk"}, weights={SCV=2,TAP=2,ASS=2,OAM=1}},
["Order of the Umbral Oracle"]     = "Umbral.png",
["Keybir-Aviv"] = {tags={"harbor","compliance"}, weights={ASS=3,TAP=2,OSI=1,CIC=1}},
["Mystery of the Verdant Embrace"] = "Verdant.png",
["Tel-Amin"] = {tags={"coastal","guilds"}, weights={OAM=2,TAP=2,ASS=1,SCV=1}},
["Conclave Illuminara"]           = "Conclave.png",
["Diamandis"] = {tags={"festival","cemeteries"}, weights={TAP=2,SCV=2,OAM=1,RNB=1}},
["Sanctum Vitalis"]               = "Vitalis.png",
["Lewisburg"] = {tags={"port-node","interdiction"}, weights={ASS=2,CICL=2,TAP=2,OSI=1}},
["Temple Alabaster"]               = "Alabaster.png",
["Lykopolis"] = {tags={"trade","crowds"}, weights={ASS=2,TAP=2,CICL=1,OSI=1}},
["Court of the Ironclad"]         = "Ironclad.png",
["Tonar"] = {tags={"occupation","canals"}, weights={SCD=2,SCV=2,VEG=2,TAP=1,CICL=1}},
["Accord Concordia"]               = "Concordia.png",
["Bashkim"] = {tags={"steppe","irrigation"}, weights={VEG=3,SCV=2,TAP=1,CICL=1}},
["Mystery of the Stygian Veil"]   = "StygianVe.png",
["Ourid"] = {tags={"river","irrigation"}, weights={VEG=3,SCV=2,TAP=1}},
["Sylvan Fellowship"]             = "Sylvan.png",
["Ardclach"] = {tags={"north","cold"}, weights={ASS=2,SCV=2,TAP=1,CIC=1}},
["Mystery of Red Mirth"]           = "MirthFlag.png",
["Riddersborg"] = {tags={"north","order"}, weights={CICL=2,OSI=2,TAP=1,SCV=1}},
["Rex Catonis"]                   = "Catonis.png",
["Sanctum Delphica"]               = "SanctumDel.png",
["Ordo Amicitia"]                 = "OrdoFl.png",
["Temple Illuminata"]             = "AureumFlag.png",
}
}


local function getOrderIconMarkup(orderName)
local function getProfile(cityName)
local fn = orderIconFile[orderName]
local p0 = cityProfiles[cityName]
if fn and fileExists(fn) then
if p0 then return p0 end
return string.format("[[File:%s|20px]] ", fn)
return {tags={"standard"}, weights={OSI=1,OAM=1,CIC=1,SCV=1,SCD=1,TAP=1}}
end
return ""
end
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- 8) CITY PROFILES (complete)
-- 9) OPTIONAL: ECON SIGNALS (safe fallback)
-- Returns a map: econSignals[cityName] = {summary="...", tags={"industry","agri"}}
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local cityProfiles = {
local function getEconSignals()
-- =========================
local title = mw.title.new("General Port of Lake Morovia")
-- CORE MAJORS
if not title then return {} end
-- =========================
local content = safeTitleContent(title.fullText)
["Vaeringheim"] = {
if not content or content == "" then return {} end
tags={"capital","admin","port","templebank","canals"},
weights={GGS=4,HSA=3,RCA=3,ASS=2,CIL=2,CHS=2,OSI=1},
},
["Luminaria"] = {
tags={"industry","rollingstock","innovation","dawn","infrastructure"},
weights={TIL=4,OAM=3,EOF=2,IGA=2,OSI=1,GGS=1},
},
["Serena"] = {
tags={"civic","lake","hospitality","public"},
weights={HSA=3,ACC=2,OAI=2,ASS=1,CHS=1,OSI=1},
},
["Pyralis"] = {
tags={"volcanic","hazard","fire","reconstruction"},
weights={IGA=4,ASS=2,SCV=2,EOF=1,CIC=1},
baseSev="yellow",
},
["Symphonara"] = {
tags={"civic","arts","festival","public"},
weights={MRM=3,ACC=2,OSI=2,OAM=2,HSA=1,RCA=1},
},
["Aurelia"] = {
tags={"civic","trade","public"},
weights={HSA=2,CHS=2,GGS=2,ACC=1,OSI=1},
},
["Somniumpolis"] = {
tags={"finance","dream","rumorcontrol","noctic"},
weights={REN=3,GGS=3,HSA=2,UOR=2,CIL=2,RCA=1},
baseSev="yellow",
},
["Nexa"] = {
tags={"market","trade","logistics"},
weights={GGS=3,HSA=2,CHS=2,RCA=1,ASS=1,ACC=1},
},
["Lunalis Sancta"] = {
tags={"lunar","archives","pilgrimage","omens"},
weights={CIL=4,REN=2,UOR=2,SDL=1,HSA=1},
},
["Sylvapolis"] = {
tags={"forest","garden","ecology","shrinework"},
weights={SYF=3,MVE=3,OAI=1,HSA=1,ASS=1},
},


-- =========================
-- Lightweight heuristic: find occurrences of city names near “Company” blocks.
-- CORE MINORS
local signals = {}
-- =========================
local text = normalizeSpaces(content)
["Saluria"] = {
tags={"civic","artisan","wildlife"},
weights={SYF=2,CHS=2,ACC=1,HSA=1,OAI=1},
},
["Aetherium"] = {
tags={"sky","omens","education"},
weights={CIL=2,OAM=2,CHS=2,OSI=1},
},
["Ferrum Citadel"] = {
tags={"fortress","industry","security","infrastructure"},
weights={CIC=3,RCA=2,TIL=2,EOF=1,IGA=1},
baseSev="yellow",
},
["Acheron"] = {
tags={"frontier","containment","mines","security"},
weights={CIC=3,EOF=2,MSV=1,UOR=1,IGA=1,RCA=1},
baseSev="yellow",
},
["Erythros"] = {
tags={"corridor","trade","security"},
weights={GGS=2,RCA=2,TIL=2,ASS=1,IGA=1},
},
["Catonis Atrium"] = {
tags={"civic","law","order"},
weights={RCA=4,HSA=1,CHS=1,ACC=1},
},
["Delphica"] = {
tags={"oracle","archives","scholarship"},
weights={SDL=4,CIL=2,CHS=1,HSA=1},
},
["Koinonía"] = {
tags={"harmony","mediation","civic"},
weights={ACC=3,HSA=3,CHS=1,OAI=1},
},
["Aureum"] = {
tags={"wealth","craft","trade","finance"},
weights={GGS=4,HSA=1,RCA=1,ALB=1},
},


-- =========================
for _, c in ipairs(cityOrder) do
-- ALPERKIN
local n = c.name
-- =========================
local hits = 0
["The Alpazkigz"] = {
-- count mentions as proxy for “activity”
tags={"tribal","festival","intercession","mediation"},
for _ in text:gmatch(n:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])","%%%1")) do
weights={ACC=3,HSA=2,CHS=2,OAI=1,CIC=1},
hits = hits + 1
baseSev="yellow",
if hits >= 6 then break end
},
end
["Odiferia"] = {
if hits >= 3 then
tags={"noctic","rumorcontrol","dream","folk"},
signals[n] = {summary=string.format("High Port linkage (mentions: %d)", hits), tags={"port"}}
weights={REN=3,HSA=2,GGS=2,UOR=2,MSV=1},
end
baseSev="yellow",
end
},
return signals
end


-- =========================
local function econSummary(sig)
-- NEW SOUTH JANGSONG (MAJOR)
if not sig then return "" end
-- =========================
return sig.summary or ""
["Skýrophos"] = {
end
tags={"integration","civic","corridor","governance"},
weights={CHS=2,HSA=2,RCA=2,TIL=2,EOF=2,ACC=1},
},
["Bjornopolis"] = {
tags={"north","fortified","industry","civic"},
weights={EOF=3,CIC=2,TIL=2,ASS=1,RCA=1},
baseSev="yellow",
},
["Aegirheim"] = {
tags={"north","coastal","harbor","watch"},
weights={ASS=3,EOF=2,CIC=1,TIL=1},
baseSev="yellow",
},
["Norsolyra"] = {
tags={"agriculture","wetlands","river","supply"},
weights={MVE=3,ASS=2,SYF=2,OAI=1,SCV=1},
},
["Thorsalon"] = {
tags={"north","vineyards","festival","supply"},
weights={MVE=2,MRM=2,EOF=2,OAI=1,ASS=1},
baseSev="yellow",
},
 
-- =========================
-- NEW SOUTH JANGSONG (MINOR)
-- =========================
["Pelagia"] = {
tags={"coastal","distribution","water","relief"},
weights={ASS=3,SCV=2,HSA=1,EOF=1},
},
["Myrene"] = {
tags={"coastal","civic","trade"},
weights={ASS=2,HSA=2,CHS=1,ACC=1},
},
["Thyrea"] = {
tags={"agriculture","market","relief"},
weights={MVE=3,OAI=2,SYF=1,HSA=1},
},
["Ephyra"] = {
tags={"forest","frontier","watch","omens"},
weights={SYF=3,UOR=2,EOF=1,CIC=1},
baseSev="yellow",
},
["Halicarn"] = {
tags={"archives","scholarship","civic"},
weights={SDL=3,CIL=2,CHS=1,HSA=1},
},
 
-- =========================
-- HAIFAN BASSARIDIA (MAJOR)
-- =========================
["Keybir-Aviv"] = {
tags={"port","trade","gateway","security"},
weights={ASS=3,GGS=2,HSA=2,RCA=1,CIC=1},
baseSev="yellow",
},
["Tel-Amin"] = {
tags={"plantation","coffee","trade","agriculture"},
weights={MVE=3,OAI=2,SYF=2,GGS=1,ASS=1},
},
["Diamandis"] = {
tags={"luxury","trade","vaulting","security"},
weights={GGS=4,RCA=2,ALB=1,HSA=1},
baseSev="yellow",
},
["Jogi"] = {
tags={"agroprocessing","market","ferry","trade"},
weights={MVE=3,OAI=2,ASS=2,SYF=1,GGS=1},
},
["Lewisburg"] = {
tags={"rail","hub","logistics","governance"},
weights={TIL=4,RCA=3,GGS=2,HSA=1,EOF=1,ASS=1},
baseSev="yellow",
},
 
-- =========================
-- HAIFAN BASSARIDIA (MINOR)
-- =========================
["Thermosalem"] = {
tags={"healing","resort","water","crowd"},
weights={SCV=3,ASS=2,HSA=2,ACC=1,RCA=1},
baseSev="yellow",
},
["Akróstadium"] = {
tags={"stadium","festival","crowd","public"},
weights={MRM=3,OSI=2,ACC=2,HSA=1},
baseSev="yellow",
},
["Sufriya"] = {
tags={"terminal","security","port","logistics"},
weights={ASS=3,GGS=2,CIC=2,RCA=1,TIL=1},
baseSev="yellow",
},
["Lykopolis"] = {
tags={"rail","corridor","security","logistics"},
weights={TIL=4,RCA=2,EOF=2,CIC=1,GGS=1},
baseSev="yellow",
},
 
-- =========================
-- NORMARK
-- =========================
["Ardclach"] = {
tags={"north","ferry","coastal","watch"},
weights={ASS=3,EOF=2,CIC=1,GGS=1},
baseSev="yellow",
},
["Riddersborg"] = {
tags={"north","rail","corridor","storm"},
weights={EOF=3,TIL=2,CIC=2,ASS=1,RCA=1},
baseSev="yellow",
},
 
-- =========================
-- OURIANA
-- =========================
["Bashkim"] = {
tags={"rail","corridor","agriculture","logistics"},
weights={MVE=2,TIL=3,RCA=2,OAI=1,EOF=1},
baseSev="yellow",
},
["Ourid"] = {
tags={"river","fisheries","supply","trade"},
weights={ASS=3,MVE=2,OAI=2,HSA=1},
},
["Tonar"] = {
tags={"quarantine","doctrine","containment","security"},
weights={ALB=3,CIC=3,RCA=2,HSA=1,SDL=1},
baseSev="red",
},
 
-- =========================
-- CALEDONIAN DEPENDENCIES
-- =========================
["Fanghorn"] = {
tags={"frontier","forests","security","watch"},
weights={EOF=3,CIC=2,SYF=2,UOR=1,RCA=1},
baseSev="yellow",
},
["Eikbu"] = {
tags={"ranching","agriculture","trail","supply"},
weights={MVE=3,SYF=2,OAI=2,EOF=1},
},
["Galvø"] = {
tags={"island","coastal","port","storm"},
weights={ASS=3,EOF=2,CIC=1},
baseSev="yellow",
},
["Sårensby"] = {
tags={"rail","corridor","industry","civic"},
weights={TIL=3,EOF=2,RCA=2,GGS=1,ASS=1},
baseSev="yellow",
},
["Slevik"] = {
tags={"north","security","civic"},
weights={EOF=3,CIC=2,ASS=1,RCA=1},
baseSev="yellow",
},
["Sjøsborg"] = {
tags={"coastal","port","trade","watch"},
weights={ASS=3,EOF=2,GGS=1},
baseSev="yellow",
},
["Storesund"] = {
tags={"coastal","trade","storm"},
weights={ASS=3,EOF=2,CIC=1},
baseSev="yellow",
},
["Notranskja"] = {
tags={"north","frontier","watch","omens"},
weights={EOF=2,CIC=2,UOR=2,ASS=1},
baseSev="yellow",
},
["Kaledonija"] = {
tags={"regionalhub","governance","trade","civic"},
weights={RCA=3,EOF=2,GGS=2,HSA=1,ASS=1},
baseSev="yellow",
},
}


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- 9) ECONOMIC SIGNALS: parse "Company Table" on General Port page
-- 10) OPTIONAL: THREAT WATCH (Module:ReligiousEvents)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local econCache = nil
local function parseReligiousEventsTable(wikitext)
 
local map = {}
local function sectorOf(industryCell)
wikitext = tostring(wikitext or "")
local s = (industryCell or ""):lower()
for line in wikitext:gmatch("[^\n]+") do
if s:find("agric", 1, true) then return "Agricultural" end
if line:match("^|%s") and not line:match("^|%-%s*$") then
if s:find("service", 1, true) then return "Service" end
local parts = mw.text.split(line:gsub("^|%s*", ""), "%s*||%s*")
if s:find("manufact", 1, true) or s:find("mining", 1, true) or s:find("energy", 1, true) then
-- expected: Date || City || Event || Urgency
return "Industry"
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
end
return "Other"
return map
end
end


local function extractCompanyName(cell)
local function urgencyScore(u)
local n = cell:match("'''(.-)'''")
u = tostring(u or ""):upper()
if n and n ~= "" then return stripWiki(n) end
if u == "CRITICAL" then return 4 end
n = cell:match("%*%*(.-)%*%*")
if u == "HIGH" then return 3 end
if n and n ~= "" then return stripWiki(n) end
if u == "MEDIUM" then return 2 end
n = stripWiki(cell)
if u == "LOW" then return 1 end
if #n > 80 then n = n:sub(1, 80) .. "…" end
return 0
return n
end
end


local function extractHQ(cell)
--------------------------------------------------------------------------------
local s = stripWiki(cell)
-- 11) WX (Ops Alert) integration from Module:BassaridiaForecast
if s == "" then return nil end
--------------------------------------------------------------------------------
if s:lower():find("fleet", 1, true) then return nil end
local wxOk, wx = pcall(require, "Module:BassaridiaForecast")
if s:lower():find("no fixed", 1, true) then return nil end
if not wxOk then wx = nil end
s = s:gsub("%s*;%s*.*$", "")
 
s = s:gsub("%s*%b()%s*", " ")
local function wxColor(level)
s = normalizeSpaces(s)
level = tonumber(level) or 0
s = trim(s)
if level == 3 then return "#f8d7da" end
if citySet[s] then return s end
if level == 2 then return "#ffe5cc" end
return nil
if level == 1 then return "#fff3cd" end
return "#d4edda"
end
end


local function parseCompanyTable(wikitext)
local function opsCell(cityName, year, dayOfYear)
local signals = {}
if not wx or type(wx.getOpsData) ~= "function" then return "n/a", nil end
for cname, _ in pairs(citySet) do
local op = wx.getOpsData(cityName, year, dayOfYear)
signals[cname] = {Agricultural=0, Service=0, Industry=0, Other=0, samples={}}
if not op or not op.alert then return "n/a", nil end
end
if type(wikitext) ~= "string" then return signals end


local startPos = wikitext:find("====%s*Company Table%s*====")
local lvl  = tonumber(op.alert.level) or 0
if not startPos then startPos = wikitext:find("####%s*Company Table") end
local label = tostring(op.alert.label or "Green")
if not startPos then return signals end
local cond  = tostring(op.condition or "")
local bg    = wxColor(lvl)


local tablePos = wikitext:find("{|", startPos)
local cell = string.format(
if not tablePos then return signals end
"<span style='background:%s;padding:2px 6px;border:1px solid #ccc;border-radius:10px;white-space:nowrap;'>%s</span>%s",
local endPos = wikitext:find("|}", tablePos)
bg, label, (cond ~= "" and ("<br/><span style='font-size:90%;'>" .. cond .. "</span>") or "")
if not endPos then return signals end
)
 
return cell, op
local tableText = wikitext:sub(tablePos, endPos + 1)
end
 
local rows = {}
for chunk in tableText:gmatch("\n|%-%s*\n(.-)\n|%-%s*\n") do
rows[#rows+1] = chunk
end
local lastChunk = tableText:match("\n|%-%s*\n(.-)\n|%}")
if lastChunk then rows[#rows+1] = lastChunk end
 
for _, row in ipairs(rows) do
if not row:match("^%s*!") then
local line = normalizeSpaces(row:gsub("\n", " "))
line = line:gsub("^%s*|%s*", "")


local cells = {}
local function bumpSevWeather(sev, op)
for cell in line:gmatch("([^|]-)%s*||%s*") do
if not op or not op.alert then return sev end
cells[#cells+1] = trim(cell)
local lvl = tonumber(op.alert.level) or 0
end
local imp = op.impacts or {}
local tail = line:match("||%s*([^|].-)%s*$")
if tail and tail ~= "" then
if #cells == 0 or cells[#cells] ~= trim(tail) then
cells[#cells+1] = trim(tail)
end
end


local industryCell = ""
-- Only Orange/Red force severity bumps (Yellow stays informational)
for _, c in ipairs(cells) do
if lvl == 2 and sev == "green" then sev = "yellow" end
if containsAny(c, {"agric", "service", "manufact", "mining", "energy"}) then
if lvl >= 3 then sev = "red" end
industryCell = c
break
end
end
local sector = sectorOf(industryCell)


local hqCell = cells[#cells] or ""
-- Hard constraints should push to at least yellow
local hq = extractHQ(hqCell)
if (imp.ground == "Hazard" or imp.sea == "Closed" or imp.air == "No-Go") and sev == "green" then
if hq and signals[hq] then
sev = "yellow"
signals[hq][sector] = (signals[hq][sector] or 0) + 1
local nameCell = cells[3] or cells[2] or cells[1] or ""
local companyName = extractCompanyName(nameCell)
if companyName ~= "" and #signals[hq].samples < 2 then
signals[hq].samples[#signals[hq].samples+1] = companyName
end
end
end
end
end
 
return sev
return signals
end
end


local function getEconSignals()
local function weatherTaskOverlay(op)
if econCache then return econCache end
if not op or not op.alert then return "" end
local content = safeTitleContent("General Port of Lake Morovia")
local lvl = tonumber(op.alert.level) or 0
econCache = parseCompanyTable(content)
if lvl < 2 then return "" end -- only Orange/Red appends operational language
return econCache
end


local function dominantSector(sig)
local haz = tostring(op.alert.hazard or "weather disruption")
if not sig then return "Other" end
local imp = op.impacts or {}
local best, bestN = "Other", -1
local cond = tostring(op.condition or "")
for _, k in ipairs({"Agricultural","Industry","Service","Other"}) do
local n = sig[k] or 0
if n > bestN then best, bestN = k, n end
end
return best
end


local function econTagFromSector(sec)
local base = " Weather-driven tasking: " .. haz .. " (" ..
if sec == "Agricultural" then return "agri" end
string.format("Gnd %s / Sea %s / Air %s", imp.ground or "OK", imp.sea or "OK", imp.air or "OK") .. ")."
if sec == "Industry" then return "industry" end
if sec == "Service" then return "service" end
return "mixed"
end


local function econSummary(sig)
if cond == "Storm" or cond == "Rain" then
if not sig then return "n/a" end
return base .. " Queue stabilization at clinics/relief points; rumor containment bulletins posted; shrine-district patrol lanes marked."
local a = sig.Agricultural or 0
elseif cond == "Blizzard" or cond == "Snow" then
local i = sig.Industry or 0
return base .. " Warming-shelter checks; fuel and medicine custody verification; travel guidance posted at shrine gates."
local s = sig.Service or 0
elseif cond == "Fog" then
local o = sig.Other or 0
return base .. " Navigation warnings and crowd guidance in canal/port corridors; panic narratives suppressed early."
local dom = dominantSector(sig)
elseif cond == "Dust" then
local samp = (sig.samples and #sig.samples > 0) and ("; e.g., " .. table.concat(sig.samples, ", ")) or ""
return base .. " Mask discipline, indoor refuge advisories, and vendor hygiene enforcement prioritized."
return string.format("%s (%d agri / %d ind / %d serv / %d other)%s", dom, a, i, s, o, samp)
else
return base .. " Welfare checks and public messaging prioritized."
end
end
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- 10) OPTIONAL: Threat Watch integration from Module:ReligiousEvents
-- 12) SEVERITY + ORDER/TASK CHOICE
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local function parseReligiousEventsTable(wikitext)
local function inferSeverity(cityTier, taskSev, threatUrg)
local out = {}
-- base from task text classification
if type(wikitext) ~= "string" then return out end
local sev = tostring(taskSev or "green")
for line in wikitext:gmatch("[^\n]+") do
-- major cities run “hotter”
if line:match("^|%s*%d+/%d+/%d+%s+PSSC") then
if cityTier == "major" and sev == "green" then sev = "yellow" end
local date, city, event, urg = line:match("^|%s*(.-)%s*||%s*(.-)%s*||%s*(.-)%s*||%s*(.-)%s*$")
-- threat override
if city and urg then
if urgencyScore(threatUrg) >= 3 then sev = "red"
out[stripWiki(city)] = {urg=trim(urg), event=stripWiki(event or "")}
elseif urgencyScore(threatUrg) == 2 and sev == "green" then sev = "yellow" end
end
return sev
end
end
return out
end
 
local function threatSev(th)
if not th or not th.urg then return nil end
local u = (th.urg or ""):upper()
if u == "N/A" or u == "LOW" then return "green" end
if u == "MEDIUM" then return "yellow" end
if u == "HIGH" or u == "CRITICAL" then return "red" end
return "yellow"
end
end


--------------------------------------------------------------------------------
local function pickOrder(nextInt, profile, econSig, threat)
-- 11) WEIGHTED ORDER PICK + TASK PICK
local weighted = {}
--------------------------------------------------------------------------------
-- start with profile weights
local function weightedPickKey(nextInt, weights)
for key, w in pairs(profile.weights or {}) do
local total = 0
weighted[#weighted+1] = {key=key, w=tonumber(w) or 0}
for _, ok in ipairs(orderKeys) do
total = total + (weights[ok] or 0)
end
end
if total <= 0 then
return pickFromList(nextInt, orderKeys, nil)
end
local r = (nextInt() % total) + 1
local acc = 0
for _, ok in ipairs(orderKeys) do
acc = acc + (weights[ok] or 0)
if r <= acc then return ok end
end
return orderKeys[1]
end
local function buildTagSet(tags)
local set = {}
for _, t in ipairs(tags or {}) do set[t] = true end
return set
end


local function pickTaskForCity(nextInt, taskList, tagSet)
-- econ bump: if port-linked, bias to compliance/stability orders
local candidates = {}
if econSig and econSig.tags and econSig.tags[1] == "port" then
for _, task in ipairs(taskList or {}) do
for _, it in ipairs(weighted) do
local ok = false
if it.key == "ASS" or it.key == "TAP" or it.key == "OSI" then it.w = it.w + 1 end
for _, tg in ipairs(task.tags or {}) do
if tagSet[tg] then ok = true break end
end
end
if ok then candidates[#candidates+1] = task end
end
end
if #candidates > 0 then
return pickFromList(nextInt, candidates, nil)
end
return pickFromList(nextInt, taskList, nil)
end


local function baseWeights()
-- threat bump: prefer Aprobelle / security / medical on threat days
local w = {}
if threat and urgencyScore(threat.urg) >= 2 then
for _, ok in ipairs(orderKeys) do w[ok] = 1 end
for _, it in ipairs(weighted) do
return w
if it.key == "TAP" or it.key == "CICL" or it.key == "SCV" or it.key == "PHA" then it.w = it.w + 2 end
end
 
local function applyWeights(w, add)
if not add then return end
for k, v in pairs(add) do
if orderByKey[k] then
w[k] = (w[k] or 0) + tonumber(v or 0)
end
end
end
end
end


local function biasFromEcon(w, econTag)
local picked = pickWeighted(nextInt, weighted)
if econTag == "agri" then
for _, o in ipairs(orders) do
applyWeights(w, {MVE=3,SYF=2,OAI=2,ASS=1})
if o.key == picked.key then return o end
elseif econTag == "industry" then
applyWeights(w, {TIL=3,EOF=2,IGA=2,CIC=1,RCA=1})
elseif econTag == "service" then
applyWeights(w, {GGS=3,HSA=2,RCA=2,CIL=1,CHS=1})
else
applyWeights(w, {CHS=1,HSA=1,ASS=1})
end
end
return orders[1]
end
end


local function biasFromCorridor(w, isRail, isFerry)
local function pickTask(nextInt, o, sev)
if isRail then applyWeights(w, {TIL=2,RCA=2,GGS=1,EOF=1,ASS=1}) end
-- bias toward sev-matching tasks
if isFerry then applyWeights(w, {ASS=2,GGS=1,RCA=1}) end
local pool = {}
end
for _, t in ipairs(o.tasks or {}) do
 
local w = 1
local function biasFromTier(w, tier)
if t.sev == sev then w = 4
if tier == "major" then
elseif sev == "red" and t.sev == "yellow" then w = 2
applyWeights(w, {HSA=1,RCA=1,CIL=1,CHS=1})
elseif sev == "yellow" and t.sev == "green" then w = 2 end
pool[#pool+1] = {t=t, w=w}
end
end
local chosen = pickWeighted(nextInt, pool).t
return chosen
end
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- 12) TEMPLE BANK NOTES
-- 13) TEMPLE BANK NOTES (flavor + compliance)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local bankNotes = {
local bankNotes = {
"Temple Bank stipend verified; local shrine receipts reconciled; next audit window scheduled.",
"Stipend disbursed after checklist verification; receipt countersigned by local steward.",
"Voucher authorizations issued for ritual materials; chain-of-custody notes logged for any seized items.",
"Voucher reconciliation complete; supplies logged and stored under dual-key protocol.",
"Investor liaison note filed; payroll routing confirmed for the deployed unit; compliance clerk assigned.",
"Observer note: crowd discipline improved after posted guidance and steward coaching.",
"Donation flow stabilized; offertory seals inspected; variance thresholds set for the next three days.",
"Repair authorization issued for lamps/rail signage; labor scheduled at off-peak hours.",
"Emergency stipend line opened for crowd-control and relief logistics; disbursement witnessed by two scribes.",
"Custody ledger updated; artifacts/medicines sealed and moved to secure storage.",
"Training memo filed; corrective instruction issued to volunteers for queue discipline.",
}
}


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- 13) BUILD ONE CITY ROW
-- 14) BUILD ONE CITY ROW
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local function inferSeverity(cityProfile, econTag, taskSev, threatLevel)
local sev = taskSev or "green"
if cityProfile and cityProfile.baseSev then
sev = cityProfile.baseSev
end
if econTag == "industry" or econTag == "service" then
if sev == "green" then sev = "yellow" end
end
if threatLevel == "yellow" and sev == "green" then sev = "yellow" end
if threatLevel == "red" and sev ~= "red" then sev = "red" end
return sev
end
local function buildCityRow(di, c, econSignals, threatMap, includeThreat, showEcon)
local function buildCityRow(di, c, econSignals, threatMap, includeThreat, showEcon)
local cityName, cityRegion, tier = c.name, c.region, c.tier
local cityName = c.name
local seedStr = string.format("%d:%03d:%s", di.year, di.dayOfYear, cityName)
local cityRegion = c.region
local nextInt = lcg(hash31(seedStr))
local tier = c.tier


local profile = cityProfiles[cityName] or {tags={}, weights=nil}
local seed = hash31(string.format("mops:%d:%03d:%s", di.year, di.dayOfYear, cityName))
local tags = {}
local nextInt = lcg(seed)
 
for _, t in ipairs(profile.tags or {}) do tags[#tags+1] = t end
if RAIL[cityName] then tags[#tags+1] = "rail" end
if FERRY[cityName] then tags[#tags+1] = "ferry" end


local profile = getProfile(cityName)
local sig = econSignals and econSignals[cityName] or nil
local sig = econSignals and econSignals[cityName] or nil
local dom = dominantSector(sig)
local econTag = econTagFromSector(dom)
tags[#tags+1] = econTag
local tagSet = buildTagSet(tags)
local th = includeThreat and threatMap and threatMap[cityName] or nil
local th = includeThreat and threatMap and threatMap[cityName] or nil
local tSev = threatSev(th)


local w = baseWeights()
-- choose order, then task
biasFromTier(w, tier)
local o = pickOrder(nextInt, profile, sig, th)
biasFromEcon(w, econTag)
local task = pickTask(nextInt, o, "yellow") -- starting bias; severity may shift below
biasFromCorridor(w, RAIL[cityName], FERRY[cityName])
applyWeights(w, profile.weights)


local orderKey = weightedPickKey(nextInt, w)
local threatUrg = th and th.urg or ""
local o = orderByKey[orderKey] or orders[1]
local sev = inferSeverity(tier, task.sev, threatUrg)
local task = pickTaskForCity(nextInt, o.tasks, tagSet)


local sev = inferSeverity(profile, econTag, task.sev, tSev)
-- WX: ops badge + severity bump + overlay (Orange/Red)
local opsBadge, wxData = opsCell(cityName, di.year, di.dayOfYear)
sev = bumpSevWeather(sev, wxData)
local taskText = task.text .. weatherTaskOverlay(wxData)


local unitName = pickOrgUnit(nextInt, sev)
local unitName = pickOrgUnit(nextInt, sev)
Line 1,400: Line 841:
local unitIndex = (nextInt() % 12) + 1
local unitIndex = (nextInt() % 12) + 1
local unitCode = string.format("%s-%s%d", o.key, unitMeta.code, unitIndex)
local unitCode = string.format("%s-%s%d", o.key, unitMeta.code, unitIndex)
local headcount = unitMeta.size


local bank = pickFromList(nextInt, bankNotes, nil)
local bank = pickFromList(nextInt, bankNotes, nil)


local threatText, threatUrg = "", ""
local tags = {}
if includeThreat and th then
for _, t in ipairs(profile.tags or {}) do tags[#tags+1] = t end
threatText = th.event or ""
for _, t in ipairs(task.tags or {}) do tags[#tags+1] = t end
threatUrg = th.urg or ""
if th and th.event and th.event ~= "" then tags[#tags+1] = "threat-watch" end
end
 
local threatText = ""
if includeThreat and th then threatText = th.event or "" end


return {
return {
Line 1,415: Line 857:
region = cityRegion,
region = cityRegion,
city = string.format("%s %s", getCityFlagMarkup(cityName), cityLink(cityName)),
city = string.format("%s %s", getCityFlagMarkup(cityName), cityLink(cityName)),
ops = opsBadge, -- NEW
order = getOrderIconMarkup(o.name) .. o.link,
order = getOrderIconMarkup(o.name) .. o.link,
unit = string.format("%s (%d) [%s]", unitName, headcount, unitCode),
unit = string.format("%s (%d) [%s]", unitName, unitMeta.size, unitCode),
task = task.text,
task = taskText,
tags = joinTags(tags),
tags = joinTags(tags),
econ = (showEcon and sig) and econSummary(sig) or "",
econ = (showEcon and sig) and econSummary(sig) or "",
Line 1,428: Line 871:


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- 14) MAIN TABLE
-- 15) MAIN TABLE
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function p.makeTable(frame)
function p.makeTable(frame)
Line 1,451: Line 894:
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 !! 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
header = header .. " !! Temple Bank Notes"
header = header .. " !! Temple Bank Notes"
Line 1,464: Line 908:
table.insert(out, "|-" .. row.style)
table.insert(out, "|-" .. row.style)


local line = string.format("| %s || %s || %s || %s || %s || %s",
local line = string.format("| %s || %s || %s || %s || %s || %s || %s",
row.date, row.city, row.region, 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 1,476: Line 920:
end
end
line = line .. " || " .. row.sev
line = line .. " || " .. row.sev
table.insert(out, line)
table.insert(out, line)


Line 1,492: Line 935:
table.insert(out, "<span style='background:#fff3cd;padding:2px 6px;border:1px solid #ffeeba;'>Yellow</span> = Heightened oversight / rumor control / targeted intervention&nbsp; ")
table.insert(out, "<span style='background:#fff3cd;padding:2px 6px;border:1px solid #ffeeba;'>Yellow</span> = Heightened oversight / rumor control / targeted intervention&nbsp; ")
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 (Green/Yellow/Orange/Red) and aligns with War League / Hatch posture.")
table.insert(out, "</div>")
table.insert(out, "</div>")
end
end
Line 1,503: Line 947:


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- 15) SINGLE CITY VIEW
-- 16) SINGLE CITY VIEW
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function p.makeCity(frame)
function p.makeCity(frame)
Line 1,534: Line 978:
local out = {}
local out = {}
table.insert(out, "{| class='wikitable'")
table.insert(out, "{| class='wikitable'")
local header = "! Date !! City !! Region !! Cult Order !! Unit !! Tasking"
local header = "! 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
header = header .. " !! Temple Bank Notes"
header = header .. " !! Temple Bank Notes"
Line 1,540: Line 984:
header = header .. " !! Severity"
header = header .. " !! Severity"
table.insert(out, header)
table.insert(out, header)
table.insert(out, "|-" .. row.style)
table.insert(out, "|-" .. row.style)
 
local line = string.format("| %s || %s || %s || %s || %s || %s || %s",
local line = string.format("| %s || %s || %s || %s || %s || %s",
row.date, row.city, row.region, row.ops, row.order, row.unit, row.task
row.date, row.city, row.region, row.order, row.unit, row.task
)
)
if showEcon then line = line .. " || " .. (row.econ ~= "" and row.econ or "n/a") end
if showEcon then line = line .. " || " .. (row.econ ~= "" and row.econ or "n/a") end
Line 1,553: Line 997:
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,559: Line 1,002:


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- 16) ORDER-ONLY VIEW
-- 17) ORDER-ONLY VIEW
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function p.makeOrder(frame)
function p.makeOrder(frame)
Line 1,588: Line 1,031:
local out = {}
local out = {}
table.insert(out, "{| class='wikitable sortable'")
table.insert(out, "{| class='wikitable sortable'")
local header = "! Date !! City !! Region !! Unit !! Tasking"
local header = "! Date !! City !! Region !! Ops Alert !! Unit !! Tasking"
if showEcon then header = header .. " !! Economic signal" end
if showEcon then header = header .. " !! Economic signal" end
header = header .. " !! Temple Bank Notes"
header = header .. " !! Temple Bank Notes"
Line 1,597: Line 1,040:
for _, c in ipairs(cityOrder) do
for _, c in ipairs(cityOrder) do
local row = buildCityRow(di, c, econSignals, threatMap, includeThreat, showEcon)
local row = buildCityRow(di, c, econSignals, threatMap, includeThreat, showEcon)
if row.order:find(wantedOrder.link, 1, true) then
-- only keep rows where chosen order matches the wanted one
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)
local line = string.format("| %s || %s || %s || %s || %s",
local line = string.format("| %s || %s || %s || %s || %s || %s",
row.date, row.city, row.region, row.unit, row.task
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
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 21:41, 1 January 2026

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

-- Module:MissionaryDailyOpsBVR (REWRITE + WX OPS ALERT + TEMPLE APROBELLE)
-- Daily (PSSC-calendar keyed) missionary tasking ledger for the Temple Bank of the Reformed Stripping Path.
-- Deterministic output (no math.random / no os.time seeding).
--
-- Features:
--  • Canonical city network (regions + tiers)
--  • City flags: existence-checked; fallback to Imperial Trade Union flag when missing.
--  • Orders + tasks (tagged, 10+ per order)
--  • Optional Threat Watch integration from Module:ReligiousEvents (opt-in)
--  • Optional Economic signal parsing from General Port of Lake Morovia (opt-in; safe fallback)
--  • WX Ops Alert integration from Module:BassaridiaForecast:
--      - Adds “Ops Alert” column (badge + condition)
--      - Bumps severity (Orange/Red) + appends weather-driven task overlay (Orange/Red only)
--
-- 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)
	if s == nil then return "" end
	s = tostring(s)
	s = s:gsub("^%s+", ""):gsub("%s+$", "")
	return s
end

local function normalizeSpaces(s)
	return (s or ""):gsub("[%s\u00A0]+", " ")
end

local function stripWiki(s)
	s = tostring(s or "")
	-- Remove files/templates quickly
	s = s:gsub("%[%[File:[^%]]+%]%]", "")
	s = s:gsub("%{%b{}%}", "")
	-- Links: [[A|B]] => B, [[A]] => A
	s = s:gsub("%[%[[^%]|]+|([^%]]+)%]%]", "%1")
	s = s:gsub("%[%[([^%]]+)%]%]", "%1")
	-- Bold/italics
	s = s:gsub("'''''", ""):gsub("'''", ""):gsub("''", "")
	return trim(s)
end

local function joinTags(tags)
	if not tags or #tags == 0 then return "" end
	return table.concat(tags, ", ")
end

local function safeTitleContent(titleText)
	local ok, t = pcall(mw.title.new, titleText)
	if not ok or not t then return nil end
	local ok2, content = pcall(function() return t:getContent() end)
	if not ok2 then return nil end
	return content
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) COLOR CODING (Hatch-style)
--------------------------------------------------------------------------------
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

--------------------------------------------------------------------------------
-- 4) CITY NETWORK (canonical list)
--------------------------------------------------------------------------------
local function cityLink(name)
	return string.format("[[List of cities in Bassaridia Vaeringheim#%s|%s]]", name, name)
end

-- 54 canonical cities; tier used for "major city" baseline.
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 (existence-checked + fallback)
--------------------------------------------------------------------------------
local DEFAULT_CITY_FLAG_FILE = "Imperial Trade Union flag.png"

local function normalizeForFlag(name)
	return (name or "")
		:gsub("[%s%-’]", "")
		:gsub("[ÁáÀàÂâÄäÃãÅåĀā]", "A")
		:gsub("[ÉéÈèÊêËëĒē]", "E")
		:gsub("[ÍíÌìÎîÏïĪī]", "I")
		:gsub("[ÓóÒòÔôÖöÕõØøŌō]", "O")
		:gsub("[ÚúÙùÛûÜüŪū]", "U")
		:gsub("[ÝýŸÿ]", "Y")
		:gsub("[Ææ]", "AE")
end

local fileExistsCache = {}
local function fileExists(fileName)
	if not fileName or fileName == "" then return false end
	if fileExistsCache[fileName] ~= nil then return fileExistsCache[fileName] end
	local ok, t = pcall(mw.title.new, "File:" .. fileName)
	local exists = (ok and t and t.exists == true) or false
	fileExistsCache[fileName] = exists
	return exists
end

-- Explicit odd filenames you noted (plus Skyrophos).
local cityFlagOverrides = {
	["Skýrophos"] = "SkyrophosFlag.png",
	["Sårensby"]  = "SårensbyFlag.png",
	["Slevik"]    = "SlevikFlag.png.png",
	["Kaledonija"]= "KaledonijaFlag.png",
}

local cityFlagMarkupCache = {}
local function getCityFlagMarkup(cityName)
	if cityFlagMarkupCache[cityName] then return cityFlagMarkupCache[cityName] end

	-- 1) explicit override
	local override = cityFlagOverrides[cityName]
	if override and fileExists(override) then
		local mk = string.format("[[File:%s|20px]]", override)
		cityFlagMarkupCache[cityName] = mk
		return mk
	end

	-- 2) try normalized candidates
	local norm = normalizeForFlag(cityName)
	local candidates = {
		norm .. "Flag.png",
		cityName:gsub(" ", "") .. "Flag.png",
		cityName:gsub(" ", "") .. "flag.png",
	}
	for _, f in ipairs(candidates) do
		if fileExists(f) then
			local mk = string.format("[[File:%s|20px]]", f)
			cityFlagMarkupCache[cityName] = mk
			return mk
		end
	end

	-- 3) fallback
	local mk = string.format("[[File:%s|20px]]", DEFAULT_CITY_FLAG_FILE)
	cityFlagMarkupCache[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) ORDER ICONS (optional; safe if file missing)
--------------------------------------------------------------------------------
local orderIconFile = {
	["Ordo Solis Invicti"]        = "OrdoSolisInvicti.png",
	["Order of Aurora Mystica"]   = "OrderOfAuroraMystica.png",
	["Conclave Illuminara"]       = "ConclaveIlluminara.png",
	["Sanctum Delphica"]          = "SanctumDelphica.png",
	["Sanctum Vitalis"]           = "SanctumVitalis.png",
	["Pharmacon Sect"]            = "PharmaconSect.png",
	["Mystery of the Verdant Embrace"] = "VerdantEmbrace.png",
	["Court of the Ironclad"]     = "CourtOfTheIronclad.png",
	["Reverie Nebulous"]          = "ReverieNebulous.png",
	["Azure Sentinel Sect"]       = "AzureSentinelSect.png",
	["Temple Aprobelle"]          = "TempleAprobelle.png", -- NEW
}

local function getOrderIconMarkup(orderName)
	local f = orderIconFile[orderName]
	if f and fileExists(f) then
		return string.format("[[File:%s|18px]] ", f)
	end
	return ""
end

--------------------------------------------------------------------------------
-- 7) ORDERS + TASKS (tagged)
--------------------------------------------------------------------------------
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

local orders = {
	{key="OSI", name="Ordo Solis Invicti",
		link=orderLink("Atos (Reformed Stripping Path)","Ordo_Solis_Invicti","Ordo Solis Invicti"),
		tasks={
			T("Dawn rites and public illumination drill to restore civic clarity and discourage rumor cascades in market wards.","green",{"public","education","service"}),
			T("Temple-school literacy session paired with cosmology instruction and shrine maintenance inspections.","green",{"education","shrine","service"}),
			T("Solar route inspection at major junctions; traffic etiquette posted for pilgrims and commuters.","green",{"transit","public"}),
			T("Sun-pledge renewal for civic clerks; oath sheets sealed and logged with Temple Bank scribes.","green",{"governance","audit"}),
			T("Public lamp and beacon blessing for tunnel nodes; wayfinding clarified for night labor shifts.","green",{"infrastructure","transit"}),
			T("Radiance audit of public lamps, mirrored courts, and shrine sightlines; repair orders issued under Temple Bank stipend.","yellow",{"infrastructure","audit"}),
			T("Solar Circle consecration for a new neighborhood shrine, with crowd choreography and compliance wardens.","yellow",{"shrine","crowd"}),
			T("Counter-broadsheet sermon cycle following unsigned pamphlets; witness interviews conducted with local elders.","yellow",{"investigation","public"}),
			T("Joint patrol-litany along rail platforms to reduce theft and panic during peak transit hours.","yellow",{"rail","security"}),
			T("Emergency morale procession after night unrest; hymn leadership and calm restoration at transit choke points.","red",{"unrest","crowd","transit"}),
			T("Rapid de-escalation team embedded in a contentious council session; staged ‘clarity interval’ to prevent walkouts.","red",{"governance","mediation"}),
		}
	},

	{key="OAM", name="Order of Aurora Mystica",
		link=orderLink("Eos (Reformed Stripping Path)","Order_of_Aurora_Mystica","Order of Aurora Mystica"),
		tasks={
			T("Sunrise reconciliation rite for competing guilds; signed harmony pledges archived by Temple Bank clerks.","green",{"mediation","guild"}),
			T("Dawn-watch observatory check and omen bulletin issued to reduce panic and stabilize labor shifts.","green",{"omen","public"}),
			T("Neighborhood ‘first-light’ counseling circle for strained households; referrals placed with shrine caretakers.","green",{"care","mediation"}),
			T("Public blessings for early-shift workers at canal gates; accident-prevention guidance posted.","green",{"public","infrastructure"}),
			T("Aurora choir service at hospital ward; morale uplift paired with practical volunteer roster coordination.","green",{"health","service"}),
			T("Targeted rumor-clearing procession after a false ‘divine abandonment’ claim; witness statements collected.","yellow",{"rumor","investigation"}),
			T("Crowd choreography support at a dawn festival to prevent stampedes and reduce pickpocket opportunities.","yellow",{"crowd","festival"}),
			T("Joint patrol with civic stewards through tunnel districts; wayfinding, lighting, and intimidation checks.","yellow",{"security","infrastructure"}),
			T("Emergency reconciliation summit after a street brawl between factions; formal truce terms read aloud.","red",{"unrest","mediation"}),
			T("Crisis dawn vigil after violent night incident; grief support, calm messaging, and ‘no retaliation’ pledge drive.","red",{"crisis","public","grief"}),
			T("Stabilization briefings for district clerks following audit findings; correction plan countersigned.","yellow",{"audit","governance"}),
		}
	},

	{key="CIC", name="Conclave Illuminara",
		link=orderLink("Tarsica (Reformed Stripping Path)","Conclave_Illuminara","Conclave Illuminara"),
		tasks={
			T("Night-sky lecture and calendar correction posted to calm public anxiety about omens and eclipses.","green",{"education","omen"}),
			T("Quiet meditation ward established at a busy shrine; controlled breathing protocols taught to visitors.","green",{"care","public"}),
			T("Lantern-route inspection for canal patrols; reflective markers placed to reduce night accidents.","green",{"infrastructure","transit"}),
			T("Dream-space audit: mirror pools cleaned, offerings catalogued, and safety signage posted.","green",{"shrine","audit"}),
			T("Star-chart reading for civic council; guidance recorded for later reference by scribes.","green",{"governance","education"}),
			T("Investigation of counterfeit ‘moon tokens’ being sold to pilgrims; vendor interviews and seizure log.","yellow",{"investigation","trade"}),
			T("Controlled night gathering after panic rumor; calm-litany enforced, dispersal plan coordinated with stewards.","yellow",{"rumor","crowd"}),
			T("Escort for a sensitive nocturnal rite during unrest; perimeter observers posted.","red",{"security","unrest"}),
			T("Emergency anti-panic messaging after a ‘demonic sign’ claim; script distributed to shrine speakers.","red",{"crisis","rumor"}),
			T("Coordinated blackout safety drill (lanterns, guides, canal warning ropes) during storm season.","yellow",{"infrastructure","safety"}),
		}
	},

	{key="SCD", name="Sanctum Delphica",
		link=orderLink("Sisera (Reformed Stripping Path)","The_Cult_of_Sanctum_Delphica","Sanctum Delphica"),
		tasks={
			T("Archive custody inspection: artifact seals verified, witness ledger updated, and storage humidity checked.","green",{"archive","audit"}),
			T("Scribe training on doctrinal custody chain; convoy paperwork rehearsed for shrine transfers.","green",{"education","logistics"}),
			T("Pilgrim etiquette workshop at a library shrine; damage prevention and quiet-zone enforcement.","green",{"public","education"}),
			T("Indexing confiscated pamphlets for signature motifs; forwarded to Temple Bank clerks 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, signage, and rumor suppression around a restricted ward.","yellow",{"security","rumor"}),
			T("Escort a sensitive artifact transfer under heightened risk; perimeter rites and route denial markers deployed.","red",{"security","logistics"}),
			T("Emergency custody lockdown after attempted breach; rapid relocation plan activated.","red",{"crisis","security","archive"}),
			T("Court hearing preparation for accused desecrators; evidence packet assembled with scribal counsel.","yellow",{"governance","audit"}),
			T("Night watch rotation at archive stacks following arson threat; coordination with civic guards.","red",{"security","unrest"}),
		}
	},

	{key="SCV", name="Sanctum Vitalis",
		link=orderLink("Plateau (Reformed Stripping Path)","Sanctum_Vitalis","Sanctum Vitalis"),
		tasks={
			T("Clinic resupply run: bandage, tonic, and clean-water amphorae inventory reconciled with Temple Bank vouchers.","green",{"health","logistics"}),
			T("Water-quality checks at canal taps; chlorination guidance posted for household stewards.","green",{"health","infrastructure"}),
			T("School hygiene lecture (handwashing, food safety) paired with gentle blessing service.","green",{"health","education"}),
			T("Routine fever screening at a market gate during festival season; referral slips issued.","green",{"health","public"}),
			T("Temporary clinic tent established near shrine square; triage lines marked and staffed.","yellow",{"health","crowd"}),
			T("Contamination rumor assessment: test kits deployed to prove/disprove claims and prevent panic.","yellow",{"health","rumor"}),
			T("Mass casualty support after an accident; triage, referral, and grief counseling coordination.","red",{"crisis","health"}),
			T("Quarantine support in a dormitory ward; supplies custody and compliance checks.","red",{"crisis","health","audit"}),
			T("Joint training with local stewards on heat illness prevention and hydration discipline.","yellow",{"health","public"}),
			T("Night clinic staffing for unrest injuries; neutral triage posture announced to reduce retaliation violence.","red",{"unrest","health"}),
		}
	},

	{key="PHA", name="Pharmacon Sect",
		link=orderLink("Styx (Reformed Stripping Path)","The_Pharmacon_Sect","Pharmacon Sect"),
		tasks={
			T("Dispensary audit: dosage seals verified; controlled compounds logged and countersigned.","green",{"health","audit"}),
			T("Field compounding session with local 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 engineering consult at a 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("Emergency antidote preparation after suspected poisoning event; vendor supply chain traced.","red",{"crisis","investigation","health"}),
			T("Outbreak response: isolation guidance, symptom screening, and clinic routing control at transit nodes.","red",{"crisis","health","public"}),
			T("Rumor suppression briefing about ‘cursed medicine’; evidence packet and calm script issued.","yellow",{"rumor","public"}),
			T("Hospital ward reinforcement for Eidolan exposure aftershock cases; monitoring schedule created.","red",{"crisis","health"}),
			T("Post-incident debrief with Temple Aprobelle on panic dynamics; messaging updated for clinics.","yellow",{"analysis","rumor"}),
		}
	},

	{key="VEG", 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 with steward crews.","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("Emergency flood relief coordination: sandbag lines, food staging, and shelter support.","red",{"crisis","relief"}),
			T("Brushfire risk patrol in dry districts; controlled burns prohibited and notices issued.","yellow",{"fire","public"}),
			T("Post-storm debris clearance with shrine volunteers; hazard markings placed.","yellow",{"safety","infrastructure"}),
			T("Disease vector mitigation in swamp edges; standing water treated and guidance posted.","yellow",{"health","infrastructure"}),
			T("Crisis canal breach response: temporary closure, reroute of water flow, and crowd calming.","red",{"crisis","infrastructure","crowd"}),
		}
	},

	{key="CICL", name="Court of the Ironclad",
		link=orderLink("Fenrir (Reformed Stripping Path)","Court_of_the_Ironclad","Court of the Ironclad"),
		tasks={
			T("Strength-discipline training for youth patrol volunteers; nonviolent restraint emphasized.","green",{"education","security"}),
			T("Public order presence at shrine steps; crowd etiquette enforced with calm posture.","green",{"security","public"}),
			T("Workshop on oath-keeping and duty; pledge cards sealed for civic stewards.","green",{"governance","education"}),
			T("Route hardening walk: identify vulnerable alleys, broken lamps, and unsafe crossings.","yellow",{"security","infrastructure"}),
			T("Contraband confiscation support at a market stall cluster; evidence logged for Temple Bank clerks.","yellow",{"security","investigation"}),
			T("High-risk escort for a witness leaving a hearing; safehouse transfer arranged.","red",{"security","crisis"}),
			T("Emergency deterrence patrol after riot flare; separation lines and dispersal corridor opened.","red",{"unrest","crowd"}),
			T("Night watch on canal bridges after sabotage attempt; checkpoint discipline enforced.","yellow",{"security","infrastructure"}),
			T("Crisis reinforcement at a hospital gate during panic surge; access and queue discipline established.","red",{"crisis","security","health"}),
			T("Post-incident intimidation review; complaints investigated and corrective discipline applied.","yellow",{"audit","governance"}),
		}
	},

	{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 minor scare; parents briefed on calm routines.","green",{"care","education"}),
			T("Public ‘sleep hygiene’ teaching for shift workers; rest scheduling guidance posted.","green",{"health","education"}),
			T("Rumor mapping exercise: identify which dreams are spreading a harmful narrative; corrective story deployed.","yellow",{"rumor","analysis"}),
			T("Witness interviews after a trance-gathering complaint; compliance conditions issued.","yellow",{"audit","public"}),
			T("Containment support for mass panic dream episode; calm stations, water, and supervised rest zones established.","red",{"crisis","crowd"}),
			T("Emergency counseling for families after death; grief rites paired with practical aid referrals.","red",{"grief","crisis"}),
			T("Nighttime crowd calming near a transit node; chants regulated to prevent stampede behavior.","yellow",{"crowd","transit"}),
			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="ASS", name="Azure Sentinel Sect",
		link=orderLink("Aether (Reformed Stripping Path)","Azure_Sentinel_Sect","Azure Sentinel Sect"),
		tasks={
			T("Waterfront perimeter walk and ‘blue line’ marking refresh; safety rope checks in canal districts.","green",{"infrastructure","safety"}),
			T("Port-house briefing on safe embarkation and baggage rules; theft prevention guidance posted.","green",{"public","transit"}),
			T("Storm-drain inspection with civil works; blockages reported and cleared.","green",{"infrastructure","service"}),
			T("Escort for medical supplies across canal bridges; accountability signatures obtained.","yellow",{"logistics","security"}),
			T("Harbor rumor suppression: clarify false ‘quarantine’ claims to prevent stampede and hoarding.","yellow",{"rumor","public"}),
			T("Emergency flood response: evacuation guidance at waterfront shrines; calm channels maintained.","red",{"crisis","relief"}),
			T("Search-and-rescue standby after storm warnings; muster roster and equipment check.","yellow",{"safety","crisis"}),
			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"}),
		}
	},

	-- NEW: Temple Aprobelle
	{key="TAP", name="Temple Aprobelle",
		link="[[Temple_Aprobelle|Temple Aprobelle]]",
		tasks={
			T("Festival hygiene audit: vendor protocols verified (ladle rotation, water handling, waste control); seals issued to compliant stalls.","green",{"health","audit","festival","public"}),
			T("Rumor-control bulletin cycle: shrine-leader briefing + posted clarifications to prevent panic narratives from hardening.","green",{"rumor","public","service"}),
			T("Grief-cadre home visits after a recent accident; de-escalation coaching for relatives and neighborhood stewards.","green",{"grief","mediation","service"}),
			T("School immunization coordination: clinic schedule posted, consent ledgers reconciled, and reassurance rites conducted.","green",{"health","school","coordination"}),
			T("Clinic-queue stabilization drill: entry metering, calm-litany, and conflict prevention training for volunteers.","yellow",{"queue","crowd","health","order"}),
			T("Loyalty and behavior audit of a district office after complaint surge; corrective training issued with Temple Bank clerk witness.","yellow",{"audit","governance","compliance"}),
			T("Anomaly-response dispatch to a shrine ward after ‘demonic contamination’ claims; care provision paired with narrative discipline.","yellow",{"anomaly","rumor","public","health"}),
			T("Pattern-analysis intake: confiscated schism texts and recordings logged, indexed, and forwarded for doctrinal signature correlation.","yellow",{"analysis","schism","security","archive"}),
			T("Transit node stabilization after crowd surge: calm stations, signage, and steward handoffs established.","yellow",{"transit","crowd","stabilization"}),
			T("Silencing-cadre activated after panic flare; controlled messaging + visible care stations deployed under escort.","red",{"panic","rumor","stabilization"}),
			T("Emergency stabilization in evacuation/fever center: calming rites, grief triage, and rumor suppression until order restored.","red",{"crisis","health","crowd","stabilization"}),
			T("Post-incident narrative repair: rumor sources mapped, apology ceremonies arranged, and retaliation prevention pledges collected.","yellow",{"analysis","mediation","public"}),
		}
	},
}

local orderByName = {}
for _, o in ipairs(orders) do orderByName[o.name] = o end

--------------------------------------------------------------------------------
-- 8) CITY PROFILES (tags + order bias weights)
--------------------------------------------------------------------------------
local cityProfiles = {
	["Vaeringheim"] = {tags={"capital","port","canals"}, weights={OSI=2,OAM=2,CIC=2,SCD=2,SCV=2,ASS=2,TAP=3}},
	["Somniumpolis"] = {tags={"wetlands","dream-space","panic-risk"}, weights={RNB=3,SCV=2,PHA=2,TAP=3,OSI=1}},
	["Luminaria"] = {tags={"dawn","tunnels","enlightenment"}, weights={OAM=3,CIC=2,OSI=2,TAP=2}},
	["Lunalis Sancta"] = {tags={"libraries","court","ritual"}, weights={SCD=3,CIC=2,TAP=2,OSI=1}},
	["Delphica"] = {tags={"archive","custody"}, weights={SCD=3,TAP=2,CIC=1}},
	["Acheron"] = {tags={"canyonlands","medicine"}, weights={PHA=3,SCV=2,TAP=1,CICL=1}},
	["Thermosalem"] = {tags={"springs","crowds"}, weights={SCV=2,PHA=2,TAP=2,ASS=1}},
	["Pyralis"] = {tags={"festival","risk"}, weights={SCV=2,TAP=2,ASS=2,OAM=1}},
	["Keybir-Aviv"] = {tags={"harbor","compliance"}, weights={ASS=3,TAP=2,OSI=1,CIC=1}},
	["Tel-Amin"] = {tags={"coastal","guilds"}, weights={OAM=2,TAP=2,ASS=1,SCV=1}},
	["Diamandis"] = {tags={"festival","cemeteries"}, weights={TAP=2,SCV=2,OAM=1,RNB=1}},
	["Lewisburg"] = {tags={"port-node","interdiction"}, weights={ASS=2,CICL=2,TAP=2,OSI=1}},
	["Lykopolis"] = {tags={"trade","crowds"}, weights={ASS=2,TAP=2,CICL=1,OSI=1}},
	["Tonar"] = {tags={"occupation","canals"}, weights={SCD=2,SCV=2,VEG=2,TAP=1,CICL=1}},
	["Bashkim"] = {tags={"steppe","irrigation"}, weights={VEG=3,SCV=2,TAP=1,CICL=1}},
	["Ourid"] = {tags={"river","irrigation"}, weights={VEG=3,SCV=2,TAP=1}},
	["Ardclach"] = {tags={"north","cold"}, weights={ASS=2,SCV=2,TAP=1,CIC=1}},
	["Riddersborg"] = {tags={"north","order"}, weights={CICL=2,OSI=2,TAP=1,SCV=1}},
}

local function getProfile(cityName)
	local p0 = cityProfiles[cityName]
	if p0 then return p0 end
	return {tags={"standard"}, weights={OSI=1,OAM=1,CIC=1,SCV=1,SCD=1,TAP=1}}
end

--------------------------------------------------------------------------------
-- 9) OPTIONAL: ECON SIGNALS (safe fallback)
-- Returns a map: econSignals[cityName] = {summary="...", tags={"industry","agri"}}
--------------------------------------------------------------------------------
local function getEconSignals()
	local title = mw.title.new("General Port of Lake Morovia")
	if not title then return {} end
	local content = safeTitleContent(title.fullText)
	if not content or content == "" then return {} end

	-- Lightweight heuristic: find occurrences of city names near “Company” blocks.
	local signals = {}
	local text = normalizeSpaces(content)

	for _, c in ipairs(cityOrder) do
		local n = c.name
		local hits = 0
		-- count mentions as proxy for “activity”
		for _ in text:gmatch(n:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])","%%%1")) 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), tags={"port"}}
		end
	end
	return signals
end

local function econSummary(sig)
	if not sig then return "" end
	return sig.summary or ""
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*")
			-- expected: Date || City || Event || Urgency
			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

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

--------------------------------------------------------------------------------
-- 11) WX (Ops Alert) integration from 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 {}

	-- Only Orange/Red force severity bumps (Yellow stays informational)
	if lvl == 2 and sev == "green" then sev = "yellow" end
	if lvl >= 3 then sev = "red" end

	-- Hard constraints should push to at least yellow
	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 appends operational language

	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 .. " Queue stabilization at clinics/relief points; rumor containment bulletins posted; shrine-district patrol lanes marked."
	elseif cond == "Blizzard" or cond == "Snow" then
		return base .. " Warming-shelter checks; fuel and medicine custody verification; travel guidance posted at shrine gates."
	elseif cond == "Fog" then
		return base .. " Navigation warnings and crowd guidance in canal/port corridors; panic narratives suppressed early."
	elseif cond == "Dust" then
		return base .. " Mask discipline, indoor refuge advisories, and vendor hygiene enforcement prioritized."
	else
		return base .. " Welfare checks and public messaging prioritized."
	end
end

--------------------------------------------------------------------------------
-- 12) SEVERITY + ORDER/TASK CHOICE
--------------------------------------------------------------------------------
local function inferSeverity(cityTier, taskSev, threatUrg)
	-- base from task text classification
	local sev = tostring(taskSev or "green")
	-- major cities run “hotter”
	if cityTier == "major" and sev == "green" then sev = "yellow" end
	-- threat override
	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, profile, econSig, threat)
	local weighted = {}
	-- start with profile weights
	for key, w in pairs(profile.weights or {}) do
		weighted[#weighted+1] = {key=key, w=tonumber(w) or 0}
	end

	-- econ bump: if port-linked, bias to compliance/stability orders
	if econSig and econSig.tags and econSig.tags[1] == "port" then
		for _, it in ipairs(weighted) do
			if it.key == "ASS" or it.key == "TAP" or it.key == "OSI" then it.w = it.w + 1 end
		end
	end

	-- threat bump: prefer Aprobelle / security / medical on threat days
	if threat and urgencyScore(threat.urg) >= 2 then
		for _, it in ipairs(weighted) do
			if it.key == "TAP" or it.key == "CICL" or it.key == "SCV" or it.key == "PHA" then it.w = it.w + 2 end
		end
	end

	local picked = pickWeighted(nextInt, weighted)
	for _, o in ipairs(orders) do
		if o.key == picked.key then return o end
	end
	return orders[1]
end

local function pickTask(nextInt, o, sev)
	-- bias toward sev-matching tasks
	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
	local chosen = pickWeighted(nextInt, pool).t
	return chosen
end

--------------------------------------------------------------------------------
-- 13) TEMPLE BANK NOTES (flavor + compliance)
--------------------------------------------------------------------------------
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 for lamps/rail signage; labor scheduled at off-peak hours.",
	"Custody ledger updated; artifacts/medicines 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, econSignals, threatMap, includeThreat, showEcon)
	local cityName = c.name
	local cityRegion = c.region
	local tier = c.tier

	local seed = hash31(string.format("mops:%d:%03d:%s", di.year, di.dayOfYear, cityName))
	local nextInt = lcg(seed)

	local profile = getProfile(cityName)
	local sig = econSignals and econSignals[cityName] or nil
	local th = includeThreat and threatMap and threatMap[cityName] or nil

	-- choose order, then task
	local o = pickOrder(nextInt, profile, sig, th)
	local task = pickTask(nextInt, o, "yellow") -- starting bias; severity may shift below

	local threatUrg = th and th.urg or ""
	local sev = inferSeverity(tier, task.sev, threatUrg)

	-- WX: ops badge + severity bump + overlay (Orange/Red)
	local opsBadge, wxData = opsCell(cityName, di.year, di.dayOfYear)
	sev = bumpSevWeather(sev, wxData)
	local taskText = task.text .. weatherTaskOverlay(wxData)

	local unitName = pickOrgUnit(nextInt, sev)
	local unitMeta = org[unitName]
	local unitIndex = (nextInt() % 12) + 1
	local unitCode = string.format("%s-%s%d", o.key, unitMeta.code, unitIndex)

	local bank = pickFromList(nextInt, bankNotes, nil)

	local tags = {}
	for _, t in ipairs(profile.tags or {}) do tags[#tags+1] = t end
	for _, t in ipairs(task.tags or {}) do tags[#tags+1] = t end
	if th and th.event and th.event ~= "" then tags[#tags+1] = "threat-watch" end

	local threatText = ""
	if includeThreat and th then threatText = th.event or "" end

	return {
		style = sevStyle(sev),
		date = di.text,
		region = cityRegion,
		city = string.format("%s %s", getCityFlagMarkup(cityName), cityLink(cityName)),
		ops = opsBadge, -- NEW
		order = getOrderIconMarkup(o.name) .. o.link,
		unit = string.format("%s (%d) [%s]", unitName, unitMeta.size, unitCode),
		task = taskText,
		tags = joinTags(tags),
		econ = (showEcon and sig) and econSummary(sig) or "",
		bank = bank,
		threat = threatText,
		threatUrg = threatUrg,
		sev = sev,
	}
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()
	local econSignals = getEconSignals()

	local threatMap = {}
	if includeThreat then
		local ok, rel = pcall(require, "Module:ReligiousEvents")
		if ok and rel and type(rel.makeTable) == "function" then
			threatMap = parseReligiousEventsTable(rel.makeTable())
		end
	end

	local out = {}
	table.insert(out, "{| class='wikitable sortable'")

	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, econSignals, threatMap, includeThreat, showEcon)
			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&nbsp; ")
		table.insert(out, "<span style='background:#fff3cd;padding:2px 6px;border:1px solid #ffeeba;'>Yellow</span> = Heightened oversight / rumor control / targeted intervention&nbsp; ")
		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 (Green/Yellow/Orange/Red) and aligns with War League / Hatch posture.")
		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 econSignals = getEconSignals()

	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 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}, econSignals, threatMap, includeThreat, showEcon)

	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 econSignals = getEconSignals()

	local threatMap = {}
	if includeThreat then
		local ok, rel = pcall(require, "Module:ReligiousEvents")
		if ok and rel and type(rel.makeTable) == "function" then
			threatMap = parseReligiousEventsTable(rel.makeTable())
		end
	end

	local out = {}
	table.insert(out, "{| class='wikitable sortable'")
	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, econSignals, threatMap, includeThreat, showEcon)
		-- only keep rows where chosen order matches the wanted one
		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