Module:HatchMinistryDailyOps
From MicrasWiki
Jump to navigationJump to search
Documentation for this module may be created at Module:HatchMinistryDailyOps/doc
-- Module:HatchMinistryDailyOps
-- Daily (PSSC-calendar keyed) ops schedule for Hatch Ministry privateers + TME Rail Watch Corps.
-- Deterministic output (no math.random / no os.time seeding).
--
-- Updates embedded (Jan 2026):
-- • Expanded station list to include Trans-Morovian Express major + minor cities (route-table aligned)
-- plus existing coastal/TKE junction nodes.
-- • Added deterministic domain selection: "sea" (maritime/privateering) vs "rail" (TME Rail Watch).
-- - Ports bias toward sea; inland stations bias toward rail.
-- • Added Rail Watch task pools (General / North / South / Central / Interior / Ouriana)
-- and a rail-interdiction pool used by the existing 10% “opportunistic privateering” mechanic.
-- • Weather overlay is now domain-aware (rail posture vs sea posture).
-- • Everything else retained from the current module:
-- - City flags in Home/Station columns with safe fallback to ITU flag (cached)
-- - Ops Alert badge integrated from Module:BassaridiaForecast (if available)
-- - Row color-coding (Green/Yellow/Red)
-- - 10% daily chance per privateer of opportunistic interdiction against “non-aligned” targets
--
-- Usage:
-- {{#invoke:HatchMinistryDailyOps|dailyReport}}
-- {{#invoke:HatchMinistryDailyOps|dailyReport|year=52|day=118}}
-- {{#invoke:HatchMinistryDailyOps|dailyReport|legend=no}}
local p = {}
local cal = require('Module:BassaridianCalendar')
local DAYS_IN_YEAR = 183
--------------------------------------------------------------------------------
-- 0) STATIONS (Trans-Morovian Express major/minor nodes + existing coastal/TKE junction nodes)
--------------------------------------------------------------------------------
local cities = {
-- Haifan Bassaridia (mainline + branches)
"Lykopolis","Lewisburg","Keybir-Aviv","Tel-Amin","Jogi","Diamandis","Thermosalem","Akróstadium","Sufriya",
-- Odiferia
"Somniumpolis","Odiferia",
-- Alpazkigz (branches)
"Erythros","Pyralis","Nexa","Saluria","Acheron","Koinonía","Aureum","Aetherium",
-- Vaeringheim (mainline + branches)
"Vaeringheim","Symphonara","Lunalis Sancta","Luminaria","Serena","Aurelia","Sylvapolis","Delphica","Catonis Atrium","Ferrum Citadel",
-- New South Jangsong (mainline + branches)
"Skýrophos","Norsolyra","Pelagia","Bjornopolis","Aegirheim","Thorsalon","Myrene","Ephyra","Thyrea","Halicarn",
-- Bassaridian Normark (mainline + ferry node)
"Riddersborg","Ardclach",
-- Ouriana (branches)
"Ourid","Bashkim","Tonar",
-- Caledonian Dependencies (rail nodes: mainline + branch)
"Notranskja","Krlsgorod","Hamarrfell","Skøda","Fanghorn","Eikbu","Galvø",
-- Straits Conventions / distant Bassarid brokerage ports
"Port of Corumia","Port of Euranidom","Port of Southern Keltiania",
-- Existing coastal / TKE junction extras
"Sjøsborg","Sårensby","Storesund","Slevik","Kaledonija"
}
--------------------------------------------------------------------------------
-- (WX) OPTIONAL: integrate deterministic Ops Alert 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 wxBadge(level, label, cond)
local bg = wxColor(level)
label = tostring(label or "Green")
cond = tostring(cond or "")
return 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 "")
)
end
local function wxImpactText(imp)
imp = imp or {}
return string.format("Gnd: %s; Sea: %s; Air: %s", imp.ground or "OK", imp.sea or "OK", imp.air or "OK")
end
local function getWx(cityName, year, dayOfYear)
if not wx or type(wx.getOpsData) ~= "function" then return nil end
return wx.getOpsData(cityName, year, dayOfYear)
end
local function applyWeatherOverlay(text, sev, station, year, dayOfYear, domain)
if not wx or type(wx.getOpsData) ~= "function" then return text, sev end
local op = getWx(station, year, dayOfYear)
if not op or not op.alert then return text, sev end
local lvl = tonumber(op.alert.level) or 0
if lvl < 2 then return text, sev end -- only Orange/Red modify ops
local hazard = tostring(op.alert.hazard or "Weather disruption")
local impacts = op.impacts or {}
-- severity bump
if lvl == 2 and sev == "green" then sev = "yellow" end
if lvl >= 3 then sev = "red" end
domain = domain or "sea"
local overlay = " Weather posture: " .. (op.alert.label or "Orange") ..
" – " .. hazard .. " (" .. wxImpactText(impacts) .. ")."
if domain == "rail" then
if impacts.ground == "Hazard" then
overlay = overlay .. " Rail posture: speed restrictions, yard movement control, and escorted maintenance windows; passenger messaging intensified."
if sev == "green" then sev = "yellow" end
elseif impacts.ground == "Slow" then
overlay = overlay .. " Rail posture: metered departures, platform queue control, and schedule deconfliction to prevent surges."
if sev == "green" then sev = "yellow" end
else
overlay = overlay .. " Rail posture: heightened comms discipline and watch for sabotage masked by low visibility."
end
else
-- Sea-domain posture (existing behavior preserved)
if impacts.sea == "Closed" or impacts.air == "No-Go" then
overlay = overlay .. " Corridor hold-and-release windows enforced; noncritical boardings deferred; SAR/debris-clearance standby prioritized."
sev = "red"
elseif impacts.sea == "Restricted" then
overlay = overlay .. " Escort buffers widened; boarding windows shifted to safer intervals; harbor entry metered."
if sev == "green" then sev = "yellow" end
elseif impacts.ground == "Hazard" or impacts.ground == "Slow" then
overlay = overlay .. " Shore leave and pier movement controlled; queues stabilized at inspection points."
if sev == "green" then sev = "yellow" end
else
overlay = overlay .. " Heightened messaging to prevent panic narratives and berth congestion."
end
end
return text .. overlay, sev
end
--------------------------------------------------------------------------------
-- FLAG FALLBACK (use ITU flag when missing) + CACHING (avoid expensive limits)
--------------------------------------------------------------------------------
local DEFAULT_FLAG = "Imperial_Trade_Union_flag.png"
local function normalizeForFlag(name)
-- Keep Nordic letters (ø/å) intact; just remove spacing/punctuation and normalize acute vowels.
-- NOTE: For a few accented city names that have known on-wiki filenames, we use explicit candidates below.
return (name or "")
:gsub("[%s%-’]", "")
:gsub("[Áá]", "A"):gsub("[Éé]", "E")
:gsub("[Íí]", "I"):gsub("[Óó]", "O")
:gsub("[Úú]", "U"):gsub("[Ýý]", "Y")
end
local fileExistsCache = {}
local function fileExists(fileName)
fileName = tostring(fileName or "")
if fileName == "" then return false end
if fileExistsCache[fileName] ~= nil then return fileExistsCache[fileName] end
local t = mw.title.new("File:" .. fileName)
local ok = (t and t.exists) or false
fileExistsCache[fileName] = ok
return ok
end
local function flagIcon(fileName, sizePx)
local size = tonumber(sizePx) or 20
local f = (fileName and fileName ~= "") and fileName or DEFAULT_FLAG
return string.format("[[File:%s|%dpx]]", f, size)
end
local cityFlagCache = {}
local function cityFlagIcon(cityName, sizePx)
local size = tonumber(sizePx) or 20
local key = tostring(cityName or "") .. "|" .. tostring(size)
if cityFlagCache[key] then return cityFlagCache[key] end
local candidates = {}
-- Known filename oddities / diacritic-safe aliases
if cityName == "Skýrophos" then
candidates = { "SkyrophosFlag.png" }
elseif cityName == "Akróstadium" then
candidates = { "AkrostadiumFlag.png", "AkrostadiumFlag.png" }
elseif cityName == "Koinonía" then
candidates = { "KoinoniaFlag.png" }
elseif cityName == "Slevik" then
candidates = { "SlevikFlag.png.png", "SlevikFlag.png" }
elseif cityName == "Ourid" then
candidates = { "Ouridflag.png", "OuridFlag.png" }
elseif cityName == "Notranskja" then
candidates = { "NotranskjaFlag.png", "Notranskjaflag.png" }
elseif cityName == "Krlsgorod" then
candidates = { "KrlsgorodFlag.png", "Krlsgorodflag.png" }
elseif cityName == "Hamarrfell" then
candidates = { "HamarrfellFlag.png", "Hammarfellflag.png" }
elseif cityName == "Skøda" then
candidates = { "SkødaFlag.png", "Skødaflag.png" }
elseif cityName == "Fanghorn" then
candidates = { "FanghornFlag.png", "Fanghornflag.png" }
elseif cityName == "Eikbu" then
candidates = { "EikbuFlag.png", "Eikbuflag.png" }
elseif cityName == "Galvø" then
candidates = { "GalvøFlag.png", "Galvøflag.png" }
elseif cityName == "Sårensby" then
candidates = { "SårensbyFlag.png", "SarensbyFlag.png", "Sårensbyflag.png" }
elseif cityName == "Sjøsborg" then
candidates = { "SjøsborgFlagB.png", "SarensbyFlag.png", "Sårensbyflag.png" }
elseif cityName == "Port of Corumia" then
candidates = { "CorumiaFlag.png" }
elseif cityName == "Port of Euranidom" then
candidates = { "Euranidom.png" }
elseif cityName == "Port of Southern Keltiania" then
candidates = { "SouthKeltiania.png" }
else
local norm = normalizeForFlag(cityName)
candidates = { string.format("%sFlag.png", norm) }
end
for _, f in ipairs(candidates) do
if fileExists(f) then
cityFlagCache[key] = flagIcon(f, size)
return cityFlagCache[key]
end
end
cityFlagCache[key] = flagIcon(DEFAULT_FLAG, size)
return cityFlagCache[key]
end
local function cityLink(name)
local directLinks = {
["Port of Corumia"] = "[[Port of Corumia|Port of Corumia]]",
["Port of Euranidom"] = "[[Port of Euranidom|Port of Euranidom]]",
["Port of Southern Keltiania"] = "[[Southern Port of Keltiania|Port of Southern Keltiania]]"
}
if directLinks[name] then
return directLinks[name]
end
return string.format("[[List_of_cities_in_Bassaridia_Vaeringheim#%s|%s]]", name, name)
end
--------------------------------------------------------------------------------
-- 1) ACTIVE PRIVATEERS
--------------------------------------------------------------------------------
local privateers = {
{first="Arion", last="Theron"},
{first="Cassius", last="Valerus"},
{first="Nefra", last="Sekeri", nick="Sekhem"},
{first="Lysander", last="Pallas"},
{first="Marwan", last="Othman"},
{first="Octavia", last="Marcellus"},
{first="Amenhotep", last="Ankhu"},
{first="Thalia", last="Chrysos"},
{first="Demetrius", last="Gravis"},
{first="Selim", last="Bey"},
{first="Julius", last="Drusus"},
{first="Leyla", last="Han"},
{first="Marcus", last="Flavianus"},
{first="Fatima", last="Pasha"},
{first="Cornelia", last="Urbanus"}, -- Minister-Captain
{first="Eudora", last="Merit"},
{first="Perseus", last="Phoebus"},
{first="Khepri", last="Sobek"},
{first="Lucius", last="Aurelian"},
{first="Aisha", last="Dinar"},
{first="Gaius", last="Cassianus"},
{first="Selene", last="Argus"},
{first="Arsinoe", last="Menkaure"},
{first="Omar", last="Zahir"},
{first="Dorian", last="Grimm"},
{first="Quintus", last="Nero"},
{first="Zainab", last="Sultan"},
{first="Cassian", last="Corinth"},
{first="Valeria", last="Maximus", nick="Ferox"},
{first="Thorne", last="Noctis", nick="Mainomenos"},
{first="Temur", last="Qutlu", home="Ourid", roam={"Ourid","Bashkim","Tonar"}},
{first="Ayshe", last="Temirkhan", home="Notranskja", roam={"Notranskja","Krlsgorod","Hamarrfell","Skøda","Fanghorn","Eikbu","Galvø"}},
-- Straits Conventions detached commissions: distant Bassarid brokerage ports
{first="Hamilkar", last="Qorhan", nick="Aiji", home="Port of Corumia", roam={"Port of Corumia"}, mandate="straits_conventions"},
{first="Shirin", last="Vayelon", nick="Azadeh", home="Port of Euranidom", roam={"Port of Euranidom"}, mandate="straits_conventions"},
{first="Aelia", last="Witham", nick="Laceral", home="Port of Southern Keltiania", roam={"Port of Southern Keltiania"}, mandate="straits_conventions"}
}
local function privateerFlag(cap)
if type(cap) == "table" and cap.flag and cap.flag ~= "" then
return flagIcon(cap.flag, 20)
end
local last = type(cap) == "table" and cap.last or cap
return string.format("[[File:%s.png|20px]]", tostring(last or ""))
end
local function privateerPageName(c)
return string.format("Captain_%s_%s", c.first, c.last)
end
local function privateerNameRow(c)
local page = privateerPageName(c)
if c.nick then
return string.format("[[%s|Captain %s ''‘%s’'' %s]]", page, c.first, c.nick, c.last)
end
return string.format("[[%s|Captain %s %s]]", page, c.first, c.last)
end
--------------------------------------------------------------------------------
-- 2) 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 getCurrentYDOY()
local s = cal.getCurrentDate()
local y, doy, month = parseCalendarString(s)
if not y or not doy then
y, doy, month = 0, 1, ""
s = tostring(s or "")
end
return y, doy, month, s, extractEventText(s)
end
--------------------------------------------------------------------------------
-- 3) DETERMINISTIC HASH + RNG (NO bit32)
--------------------------------------------------------------------------------
local function hash31(str)
local h = 0
for i = 1, #str do
h = (h * 131 + str:byte(i)) % 2147483647
end
return h
end
local function lcg(seed)
local s = seed % 2147483647
if s <= 0 then s = s + 2147483646 end
return function()
s = (1103515245 * s + 12345) % 2147483647
return s
end
end
local function rand01(nextInt)
return nextInt() / 2147483647
end
local function pickFromList(nextInt, list, avoidValue)
if #list == 0 then return nil end
if #list == 1 then return list[1] end
local tries = 0
while tries < 12 do
local idx = (nextInt() % #list) + 1
local v = list[idx]
if v ~= avoidValue then return v end
tries = tries + 1
end
for _, v in ipairs(list) do
if v ~= avoidValue then return v end
end
return list[1]
end
--------------------------------------------------------------------------------
-- 4) REGIONAL CLUSTERS (expanded for TME)
--------------------------------------------------------------------------------
local clusters = {
South = {"Lykopolis","Lewisburg","Keybir-Aviv","Tel-Amin","Jogi","Diamandis","Thermosalem","Akróstadium","Sufriya"},
Central = {"Somniumpolis","Odiferia","Vaeringheim","Symphonara","Delphica","Lunalis Sancta","Luminaria","Serena","Aurelia","Sylvapolis","Catonis Atrium","Ferrum Citadel"},
Interior= {"Erythros","Pyralis","Nexa","Saluria","Acheron","Koinonía","Aureum","Aetherium","Kaledonija"},
North = {"Riddersborg","Ardclach","Aegirheim","Bjornopolis","Norsolyra","Ephyra","Thorsalon","Myrene","Thyrea","Halicarn","Sjøsborg","Sårensby","Storesund","Slevik"},
Jangsong= {"Skýrophos","Pelagia"},
Ouriana = {"Ourid","Bashkim","Tonar"},
Caledonia = {"Notranskja","Krlsgorod","Hamarrfell","Skøda","Fanghorn","Eikbu","Galvø"},
External = {"Port of Corumia","Port of Euranidom","Port of Southern Keltiania"}
}
local function clusterForCity(city)
for cname, list in pairs(clusters) do
for _, v in ipairs(list) do
if v == city then return cname end
end
end
return "Central"
end
--------------------------------------------------------------------------------
-- 4.1) HAIFAN BASSARIDIA HOME-PORT LOCK
--
-- Captains whose home port is in Haifan Bassaridia remain tied to that home port.
-- Their Current Station may sometimes display as a wider Southern Straits
-- Deployment, but weather, home-port logic, and enforcement authority remain
-- keyed to the actual home city.
--------------------------------------------------------------------------------
local haifanBassaridiaPorts = {
["Lykopolis"] = true,
["Lewisburg"] = true,
["Keybir-Aviv"] = true,
["Tel-Amin"] = true,
["Jogi"] = true,
["Diamandis"] = true,
["Thermosalem"] = true,
["Akróstadium"] = true,
["Sufriya"] = true
}
local function isHaifanBassaridiaPort(city)
return haifanBassaridiaPorts[tostring(city or "")] == true
end
--------------------------------------------------------------------------------
-- 4.5) DOMAIN SELECTION (Sea Privateering vs TME Rail Watch)
--------------------------------------------------------------------------------
local externalCompliancePorts = {
["Port of Corumia"]=true,
["Port of Euranidom"]=true,
["Port of Southern Keltiania"]=true
}
local seaPorts = {
["Lykopolis"]=true,["Lewisburg"]=true,["Keybir-Aviv"]=true,["Tel-Amin"]=true,["Jogi"]=true,["Diamandis"]=true,
["Thermosalem"]=true,["Akróstadium"]=true,["Sufriya"]=true,
["Somniumpolis"]=true,["Odiferia"]=true,["Vaeringheim"]=true,["Symphonara"]=true,
["Skýrophos"]=true,["Norsolyra"]=true,["Pelagia"]=true,["Bjornopolis"]=true,["Aegirheim"]=true,
["Thorsalon"]=true,["Myrene"]=true,["Ephyra"]=true,["Thyrea"]=true,["Halicarn"]=true,
["Riddersborg"]=true,["Ardclach"]=true,
["Sjøsborg"]=true,["Sårensby"]=true,["Storesund"]=true,["Slevik"]=true,
["Port of Corumia"]=true,["Port of Euranidom"]=true,["Port of Southern Keltiania"]=true
}
local function pickDomain(cap, absDay, station)
if cap and cap.mandate == "straits_conventions" then
return "sea"
end
if externalCompliancePorts[station] then
return "sea"
end
local key = string.format("domain|%s|%s|%d|%s", cap.last or "", cap.first or "", absDay, station or "")
local nextInt = lcg(hash31(key))
local r = rand01(nextInt)
if seaPorts[station] then
-- Haifan Bassaridia captains are Southern Strait home-port captains.
-- They mostly conduct sea/privateering work, but still perform inland
-- Straits Conventions enforcement through rail, road, canal, and caravan routes.
if isHaifanBassaridiaPort(station) then
return (r < 0.72) and "sea" or "rail"
end
-- Intermodal reality: ports often include rail yards; bias slightly toward sea.
return (r < 0.60) and "sea" or "rail"
end
-- Inland stations: overwhelmingly rail, with rare “river/ferry” security flavor.
return (r < 0.08) and "sea" or "rail"
end
--------------------------------------------------------------------------------
-- 5) COLOR CODING (row background reflects urgency)
--------------------------------------------------------------------------------
local function sevStyle(sev)
if sev == "red" then
return ' style="background-color:#f8d7da;"'
elseif sev == "yellow" then
return ' style="background-color:#fff3cd;"'
else
return ' style="background-color:#d4edda;"'
end
end
local function bumpSev(sev)
if sev == "green" then return "yellow" end
if sev == "yellow" then return "red" end
return "red"
end
--------------------------------------------------------------------------------
-- 6) TASK LIBRARY (Sea pools retained) + Rail Watch pools + interdiction pools
--------------------------------------------------------------------------------
local function T(text, sev) return {text=text, sev=sev} end
--------------------------------------------------------------------------------
-- 6.1) SOUTHERN STRAIT MISSIONARY ESCORTS
--
-- These tasks are used only for Haifan Bassaridia captains. They create
-- cult-specific escort missions that match the kind of work performed by
-- Reformed Stripping Path missionary units in the Southern Strait and former
-- Maritime Markets corridor.
--------------------------------------------------------------------------------
local missionaryUnits = {
"Hetairoi (12)",
"Dodekade (18)",
"Kleisthenes (25)",
"Thiasos (60)"
}
local haifanMissionaryDefaultWeights = {
{key="AZS", w=3}, -- Azure Sentinel Sect: waterfront, canal, harbor movement
{key="GGS", w=3}, -- Guild of Golden Shadows: forged documents, permits, smuggling
{key="TAP", w=3}, -- Temple Aprobelle: rumor control, panic prevention
{key="HSA", w=2}, -- Harmony Sanctum: crowd mediation, local concord
{key="SVI", w=2}, -- Sanctum Vitalis: clinic and medical escort
{key="AUM", w=1}, -- Order of Aurora Mystica: dawn rites and public reassurance
{key="SDE", w=1} -- Sanctum Delphica: doctrine, registries, quarantine language
}
local haifanMissionaryCityWeights = {
["Keybir-Aviv"] = {
{key="AZS", w=4}, {key="GGS", w=3}, {key="TAP", w=3}, {key="HSA", w=1}
},
["Tel-Amin"] = {
{key="GGS", w=4}, {key="AZS", w=3}, {key="VDE", w=2}, {key="HSA", w=2}, {key="TAP", w=2}
},
["Jogi"] = {
{key="GGS", w=4}, {key="TAP", w=3}, {key="AZS", w=2}, {key="MIR", w=2}, {key="SVI", w=2}
},
["Diamandis"] = {
{key="TAP", w=3}, {key="SVI", w=3}, {key="RNB", w=2}, {key="AUM", w=2}, {key="MIR", w=1}
},
["Lewisburg"] = {
{key="GGS", w=3}, {key="HSA", w=3}, {key="TAP", w=2}, {key="SDE", w=2}, {key="AZS", w=2}
},
["Thermosalem"] = {
{key="SVI", w=4}, {key="TAP", w=3}, {key="AZS", w=2}, {key="HSA", w=2}, {key="ALB", w=2}
},
["Akróstadium"] = {
{key="HSA", w=3}, {key="TAP", w=3}, {key="MIR", w=2}, {key="GGS", w=2}, {key="AZS", w=1}
},
["Sufriya"] = {
{key="VDE", w=3}, {key="AZS", w=3}, {key="SVI", w=2}, {key="TAP", w=2}, {key="GGS", w=2}
},
["Lykopolis"] = {
{key="SDE", w=3}, {key="TAP", w=3}, {key="GGS", w=2}, {key="HSA", w=2}, {key="AZS", w=2}
}
}
local southernStraitCultEscortData = {
AZS = {
link = "[[Thalassa (Reformed Stripping Path)#Azure Sentinel Sect|Azure Sentinel Sect]]",
sea = {
T("waterfront safety mission; missionaries inspect ferry queues, canal approaches, rope lanes, flood markers, and medical-supply crossings while Hatch Ministry patrol boats screen nearby craft.","yellow"),
T("harbor-pilgrim movement review; missionaries mark safe crossing lanes between shrine piers and quay markets while boarding teams check unregistered water taxis.","yellow"),
T("storm-lane guidance mission; missionaries post water-safety notices at canal mouths while privateers enforce no-loitering rules against suspicious skiffs.","yellow")
},
rail = {
T("inland water-route liaison; missionaries inspect bridge approaches and canal-adjacent freight yards while Rail Watch teams screen baggage, tool carts, and cargo stamps.","yellow"),
T("medical-supply crossing escort; missionaries move between port clinic stores and inland relief posts while Hatch Ministry guards protect manifests and route papers.","yellow")
}
},
GGS = {
link = "[[Chrysos (Reformed Stripping Path)#The Guild of Golden Shadows|Guild of Golden Shadows]]",
sea = {
T("anti-smuggling permit audit; missionaries trace forged shrine seals and counterfeit port vouchers while privateers search broker skiffs and warehouse runners.","yellow"),
T("dockside fraud investigation; missionaries interview money-changers, scribes, and freight brokers while Hatch Ministry crews preserve false manifests as evidence.","yellow"),
T("covert market observation; missionaries follow a forged-credential chain through quay stalls while armed privateers maintain a low-visibility extraction route.","yellow")
},
rail = {
T("inland ledger-trail escort; missionaries compare port vouchers against rail freight books while Hatch Ministry guards secure clerks, witnesses, and seized stamps.","yellow"),
T("counterfeit permit interdiction; missionaries identify false Temple seals moving by baggage courier while Rail Watch teams isolate suspect passengers at the station.","yellow")
}
},
TAP = {
link = "[[Temple Aprobelle]]",
sea = {
T("rumor-control waterfront mission; missionaries calm panic about piracy, smuggling raids, or cult infiltration while privateers keep agitators away from ferry queues.","yellow"),
T("dockside social-harmony audit; missionaries interview families, sailors, and market stewards while Hatch Ministry teams screen rooftops and alley mouths for provocateurs.","yellow"),
T("panic-suppression patrol; missionaries separate verified danger from rumor while privateers prevent crowd surges near prize-cargo seizures.","yellow")
},
rail = {
T("station rumor-control escort; missionaries calm passenger panic after smuggling arrests while Rail Watch teams enforce queue discipline and protect witnesses.","yellow"),
T("inland unrest-prevention mission; missionaries visit freight offices and shrine lanes while Hatch Ministry guards deter harassment, courier intimidation, and false-alarm crowds.","yellow")
}
},
HSA = {
link = "[[Micras (Reformed Stripping Path)#Harmony Sanctum|Harmony Sanctum]]",
sea = {
T("dockworker mediation mission; missionaries settle a dispute between stevedores, ferry crews, and shrine porters while privateers hold a neutral security line.","green"),
T("market-lane concord escort; missionaries restore queue discipline between pilgrims, merchants, and cargo handlers while Hatch Ministry crews prevent retaliation.","yellow"),
T("family reconciliation visit near the harbor quarter; missionaries de-escalate a household dispute linked to smuggling accusations while privateers secure the street approaches.","green")
},
rail = {
T("station mediation escort; missionaries settle a freight-handler dispute before it becomes a strike while Rail Watch teams protect clerks and prevent platform crowding.","yellow"),
T("inland concord patrol; missionaries move between caravan yards and rail offices to calm trade-route complaints while Hatch Ministry personnel enforce separation lanes.","green")
}
},
SVI = {
link = "[[Plateau (Reformed Stripping Path)#Sanctum Vitalis|Sanctum Vitalis]]",
sea = {
T("clinic-route escort; missionaries move medical notes, triage tags, and relief supplies between harbor clinics while privateers inspect vessels suspected of carrying contaminated or illicit cargo.","yellow"),
T("pilgrim-health screening support; missionaries identify fever, exhaustion, and exposure cases near the quay while Hatch Ministry crews protect clinic intake lanes.","yellow"),
T("Noctic-related health escort; missionaries transport registered patients or medical supplies through dockside crowds while privateers prevent vigilante interference.","red")
},
rail = {
T("medical-transfer escort; missionaries accompany patients and sealed clinic packets from the port district to inland care posts while Rail Watch teams secure compartments.","yellow"),
T("station clinic support; missionaries establish a calm triage lane for sick travelers while Hatch Ministry guards protect supplies and prevent rumor-driven crowding.","yellow")
}
},
AUM = {
link = "[[Eos (Reformed Stripping Path)#Order of Aurora Mystica|Order of Aurora Mystica]]",
sea = {
T("dawn reassurance circuit; missionaries conduct first-light rites at harbor shrines while privateers keep the pier clear and screen boats arriving before inspection hours.","green"),
T("sunrise complaint intake escort; missionaries gather civic grievances at the market edge while Hatch Ministry teams protect the booth from smugglers and intimidation crews.","yellow"),
T("beacon-and-lamp blessing route; missionaries visit harbor lights and shrine lamps while privateers verify signal discipline against false-lamp smuggling cues.","yellow")
},
rail = {
T("first-light station escort; missionaries conduct dawn reassurance rites for travelers while Rail Watch teams inspect early baggage transfers and courier satchels.","green"),
T("morning grievance route; missionaries move between station offices and shrine courtyards while Hatch Ministry personnel protect witnesses reporting forged documents.","yellow")
}
},
RNB = {
link = "[[Nephele (Reformed Stripping Path)#Reverie Nebulous|Reverie Nebulous]]",
sea = {
T("dream-disturbance intake near the harbor quarter; missionaries record nightmare claims among sailors while privateers screen suspected Noctic cargo and rumor brokers.","yellow"),
T("sleep-ward escort; missionaries guide affected families to supervised rest spaces while Hatch Ministry crews keep hostile onlookers away from the clinic lane.","yellow")
},
rail = {
T("station dream-panic escort; missionaries separate nightmare testimony from smuggling rumors while Rail Watch teams secure witnesses and baggage evidence.","yellow"),
T("inland rest-ward transfer; missionaries move distressed travelers to a supervised shelter while Hatch Ministry guards protect the route from agitators.","yellow")
}
},
VDE = {
link = "[[Opsithe (Reformed Stripping Path)#Mystery of the Verdant Embrace|Mystery of the Verdant Embrace]]",
sea = {
T("agricultural-offering escort; missionaries inspect fruit, herbs, and plant offerings entering the port while privateers search for hidden contraband beneath produce cargo.","green"),
T("wet-market safety mission; missionaries issue handling guidance for ritual plants while Hatch Ministry teams check vendor permits and cargo-origin tags.","yellow")
},
rail = {
T("inland produce-route escort; missionaries inspect plant cargo and field-origin tags while Rail Watch teams compare manifests against port intake ledgers.","yellow"),
T("farm-to-shrine custody escort; missionaries accompany ritual produce toward inland chapels while Hatch Ministry guards check caravan seals and freight transfers.","green")
}
},
ALB = {
link = "[[Ivory (Reformed Stripping Path)#Temple Alabaster|Temple Alabaster]]",
sea = {
T("medicine-purity escort; missionaries inspect sealed tinctures, balms, and clinic stores while privateers search for counterfeit jars in dockside cargo.","yellow"),
T("therapeutic-cargo custody mission; missionaries verify Temple Bank seals on medical consignments while Hatch Ministry crews secure the handoff pier.","yellow")
},
rail = {
T("sealed medicine transfer escort; missionaries carry clinic packets inland while Rail Watch teams protect storage crates and verify chain-of-custody signatures.","yellow"),
T("counterfeit medicine interdiction; missionaries identify suspect jars in station freight while Hatch Ministry guards preserve evidence and isolate handlers.","red")
}
},
SDE = {
link = "[[Sisera (Reformed Stripping Path)#Sanctum Delphica|Sanctum Delphica]]",
sea = {
T("doctrine-registry escort; missionaries verify shrine credentials, restricted rite permits, and port-side teaching licenses while privateers protect the document table.","yellow"),
T("quarantine-language review; missionaries correct unsafe public claims about rites and interdictions while Hatch Ministry crews hold the crowd line.","yellow")
},
rail = {
T("inland registry audit escort; missionaries compare shrine permits with rail-courier papers while Rail Watch teams secure clerks and document bundles.","yellow"),
T("doctrinal compliance route; missionaries visit inland chapels and registry offices while Hatch Ministry personnel screen courier bags for forged seals.","yellow")
}
},
MIR = {
link = "[[Silenus (Reformed Stripping Path)#Mystery of Red Mirth|Mystery of Red Mirth]]",
sea = {
T("festival-crowd escort; missionaries manage reveler lanes and intoxication risks near the harbor while privateers keep smugglers from using the crowd as cover.","yellow"),
T("dockside merriment-control mission; missionaries separate licensed celebration from disorder while Hatch Ministry crews search unattended crates and side boats.","yellow")
},
rail = {
T("festival rail-arrival escort; missionaries guide celebrants away from freight yards while Rail Watch teams inspect baggage for concealed contraband.","yellow"),
T("reveler-route control; missionaries maintain safe lanes between station, shrine, and market while Hatch Ministry guards deter pickpockets and courier drops.","yellow")
}
}
}
local function pickWeightedMissionaryKey(nextInt, station)
local weighted = haifanMissionaryCityWeights[station] or haifanMissionaryDefaultWeights
local total = 0
for _, item in ipairs(weighted) do
total = total + (tonumber(item.w) or 0)
end
if total <= 0 then
return "TAP"
end
local r = nextInt() % total
local acc = 0
for _, item in ipairs(weighted) do
acc = acc + (tonumber(item.w) or 0)
if r < acc then
return item.key
end
end
return weighted[1].key or "TAP"
end
local function makeSouthernStraitMissionaryEscort(nextInt, station, domain)
local key = pickWeightedMissionaryKey(nextInt, station)
local data = southernStraitCultEscortData[key] or southernStraitCultEscortData.TAP
local unit = pickFromList(nextInt, missionaryUnits, nil) or "Hetairoi (12)"
local taskPool = (domain == "rail") and data.rail or data.sea
if not taskPool or #taskPool == 0 then
taskPool = data.sea or data.rail or {}
end
local chosen = pickFromList(nextInt, taskPool, nil) or T("missionary escort and route-security support along the Southern Strait.","yellow")
local prefix
if domain == "rail" then
prefix = "Southern Strait missionary escort / inland route enforcement"
else
prefix = "Southern Strait missionary escort / privateering screen"
end
return T(
prefix .. ": " .. data.link .. " " .. unit .. " — " .. chosen.text ..
" Mission staged from " .. cityLink(station) ..
"; captain remains home-port based while extending Bassaridian control over former Maritime Markets routes.",
chosen.sev or "yellow"
)
end
-- SEA / MARITIME (existing pools preserved)
local tasks_general = {
T("Convoy escort and corridor clearing for registered merchant traffic, enforcing registry sequence and predictable passage.","green"),
T("VBSS surge and manifest verification, with heightened scrutiny for cargo mislabeling, forged papers, and transshipment laundering.","yellow"),
T("Interdiction patrol against small-craft logistics supporting anti-corridor insurgent networks, with evidence handling and detainee processing discipline.","yellow"),
T("Lane assurance work in the approaches and harbor margins, including buoy checks, beacon verification, and channel safety enforcement.","green"),
T("Counter-smuggling sweep focused on contraband arms, unregistered ritual cargo, and false-cleared Temple consignments.","yellow"),
T("Rapid-response security support to local authorities for port unrest, quarantine enforcement, and rumor-driven crowd surges.","red"),
T("Search-and-rescue readiness patrol with debris clearance coordination and temporary berth control during incidents.","red"),
T("Liaison rotation with Temple and civic screening teams for morale discipline, rumor containment, and compliance reinforcement.","yellow"),
T("Night interception detail targeting unlit runners and coastal-shadowing craft attempting to bypass inspection windows.","yellow"),
T("Port-entry queue control and traffic metering to prevent berth chaos, including stand-off enforcement for noncompliant hulls.","green"),
T("Evidence convoy to a War League intake point, transporting seized cargo under chain-of-custody doctrine.","yellow"),
T("Inspection of fishery convoys and cold-chain cargo for hidden compartments, false ice loads, and counterfeit seals.","yellow"),
T("Anti-piracy deterrence sweep against unaffiliated raiders operating outside the registry, with warning demonstrations and capture mandates.","yellow"),
T("Boarding-team readiness cycle and small-arms drill day, with emphasis on non-lethal deck control and restraint discipline.","green"),
T("Escort for diplomatic or Temple couriers transiting the corridor, with counter-ambush screening at chokepoints.","yellow"),
T("Counterfeit voucher and registry-stamp crackdown, coordinating with port clerks to identify forged paperwork patterns.","yellow"),
T("Harbor-mouth overwatch with spotters to track suspicious loitering and false distress signaling near approach lanes.","green"),
T("Quarantine cordon support for a suspected outbreak vessel, enforcing isolation lanes and controlled disembarkation sequencing.","red"),
T("Canal approach patrol to prevent sabotage against locks, piers, and water infrastructure serving major quay districts.","red"),
T("Training cruise with junior crews to certify boarding compliance, medical triage procedure, and detainee handling standards.","green")
}
local tasks_straits_conventions = {
T("Straits Conventions enforcement patrol: verify Maritime Identity Beacon broadcasts, cargo declarations, and White-Lane corridor credentials for traffic linked to distant Bassarid brokerage ports.","yellow"),
T("Confederacy of the Dispossessed interdiction watch: monitor suspected front companies, false relief consignments, and small-craft courier routes operating near the assigned brokerage port.","yellow"),
T("Port-state control liaison: coordinate with General Port auditors, HCE clerks, and local brokerage officers to reconcile manifests, placards, and disputed cargo classifications.","green"),
T("Counter-piracy sweep against Confederacy-linked runners attempting to exploit brokerage traffic, with boarding teams prepared for evidence seizure and detainee transfer.","red"),
T("White-Lane convoy assurance: escort humanitarian and essential-goods traffic while screening for dual-use equipment, encrypted radios, illicit UAV components, and forged observer tokens.","yellow"),
T("Compliance register action: document repeat violator hulls, preserve MIB challenge-response records, and prepare sanction packets for SCT or SAC review.","yellow"),
T("Brokerage-port security rotation: protect stipend, voucher, and essential-goods shipments moving through distant Bassarid commercial networks.","yellow"),
T("Harbor-mouth intimidation patrol: visible privateer presence against Confederacy recruiters, black-market brokers, and pirate-supply tenders operating outside lawful registry.","yellow"),
T("Emergency exclusion-zone drill: establish temporary stand-off waters around a threatened convoy or hazardous cargo transfer under Straits Conventions authority.","red"),
T("Routine detached commission watch: maintain predictable passage for lawful merchants while keeping Confederacy-linked irregulars under continuous observation.","green")
}
local tasks_north = {
T("Cold-water frontier patrol emphasizing raider deterrence, route denial, and winter-route landing site reconnaissance.","yellow"),
T("Joint boarding drills and seizure-readiness for difficult-weather VBSS operations near northern chokepoints.","green"),
T("Coastal reconnaissance for hidden coves, improvised piers, and suspected insurgent resupply anchorages.","yellow"),
T("Ice-margin escort for timber and ore traffic, enforcing safe-lane passage through fog and drift hazards.","green"),
T("Night watch against ghost-ship imitation tactics, verifying hull identity and rejecting falsified light codes.","yellow"),
T("Shoreline sweep for signal cairns and illicit beacon fires used to guide runners into protected inlets.","yellow"),
T("Interdiction of smugglers using river-mouth approaches to bypass port control, with shallow-draft pursuit elements.","yellow"),
T("Storm-surge response patrol supporting stranded craft, hauling wreckage clear and securing debris fields.","red"),
T("Border-proximity deterrence demonstration near contested approaches, emphasizing corridor sovereignty by presence.","yellow"),
T("Dockyard compliance audit for unregistered repairs and hidden compartments used for contraband staging.","yellow"),
T("Winterized escort for mail and courier traffic, enforcing strict timing windows through fog-prone waters.","green"),
T("Interception of runner craft using fjord-shadow routes to evade inspection, with pursuit and boarding discipline.","yellow"),
T("Harbor-mouth search pattern for missing fisher crews after sudden squall; SAR handoff to local authorities.","red"),
T("Cold-weather medical readiness drill; hypothermia triage training paired with evacuation rehearsals.","yellow"),
T("Counter-smuggling sweep focused on high-value furs, ore samples, and forged tax seals.","yellow"),
T("Coastal watchtower liaison: beacon confirmation, signal discipline, and false-alarm suppression.","green"),
T("Night shoreline patrol to prevent sabotage against lock-gates and fuel depots during high-wind periods.","red"),
T("Escort for War League inspectors transiting northern ports; identity verification and berth control enforced.","yellow"),
T("Evidence transfer run under snow/fog conditions; custody chain maintained despite degraded visibility.","yellow"),
T("Deterrence cruise through rough seas to signal sovereignty; safe-distance demonstration only.","green")
}
local tasks_south = {
T("Haifan corridor compliance sweep: routing verification, escort-buffer reinforcement, and random boarding windows.","yellow"),
T("Harbor-entry metering and berth assignment enforcement during peak merchant throughput.","green"),
T("Contraband focus patrol targeting shrine-counterfeit cargo and unregistered ritual items.","yellow"),
T("VBSS series against suspect manifests tied to minor routing variances, emphasizing correction over disruption.","yellow"),
T("Shoreline patrol to deter small-craft runner networks staging from beach coves and canal mouths.","yellow"),
T("Escort for high-value diplomatic traffic; counter-ambush and surveillance checks at chokepoints.","yellow"),
T("Rapid response to port unrest and rumor-driven crowd surges, coordinating with civic stewards.","red"),
T("Quarantine enforcement support for incoming vessel under inspection; controlled disembarkation and isolation lanes.","red"),
T("Night interception of unlit craft attempting to shadow lawful hulls into berth lanes.","yellow"),
T("Inspection of fishery convoys for hidden compartments and false seals; evidence packaged for clerks.","yellow"),
T("Counterfeit voucher crackdown: registry stamp verification and vendor interviews.","yellow"),
T("Harbor-mouth overwatch; spotters track loitering craft and false distress signals.","green"),
T("Canal-approach patrol to prevent sabotage against locks and piers.","red"),
T("Search-and-rescue readiness patrol in heavy rain periods; debris clearance standby.","red"),
T("Lane assurance: buoy and beacon checks to ensure safe passage and lawful routing.","green"),
T("Interdiction against small-craft logistics supporting insurgent networks.","yellow"),
T("Seizure-and-transfer convoy for detained contraband under chain-of-custody doctrine.","yellow"),
T("Escort buffer widening during heightened risk periods; predictable passage enforced.","yellow"),
T("Training cruise: boarding compliance, medical triage procedure, detainee handling standards.","green"),
T("Dockside liaison with Temple and civic screening teams; rumor containment and compliance reinforcement.","yellow")
}
local tasks_southern_straits = {
T("Southern Strait privateering watch: lawful prize-taking patrol against non-aligned runners using former Maritime Markets coves, false distress signals, and night-transfer craft.","yellow"),
T("Southern Strait control sweep: visible Hatch Ministry patrol from the home port to nearby inland route mouths, reinforcing Bassaridian claim over the former Maritime Markets corridor.","yellow"),
T("Straits Conventions enforcement: inspect merchant hulls, ferry tenders, and bonded cargo lighters for route papers, registry seals, and lawful corridor credentials.","yellow"),
T("Anti-smuggling patrol: search for arms, forged Temple permits, illicit ritual cargo, hidden coin chests, and dual-use signal gear moving through quay warehouses.","yellow"),
T("Former Maritime Markets registry action: compare old brokerage marks, new Bassaridian manifests, and General Port records to detect identity laundering by legacy merchant houses.","green"),
T("Hatch Ministry harbor intimidation cruise: make a deliberate show of flags, boarding teams, and signal lamps to discourage pirate brokers from testing Bassaridian control.","yellow"),
T("Counter-piracy watch: monitor crews claiming old Maritime Markets privileges while quietly mapping their patrons, debts, and supply contacts.","yellow"),
T("Quay-side seizure drill: rehearse fast isolation of suspicious cargo stacks, detainee lanes, and evidence handoff points for sudden privateering actions.","green"),
T("Smuggler decoy operation: allow a suspect lighter to proceed under surveillance before closing the route at the inland transfer point.","red"),
T("Southern Strait compliance interview: question pilots, dock stewards, ferry captains, and warehouse scribes about unauthorized side-channel traffic.","green"),
T("Privateering intelligence exchange: compare Hatch Ministry reports with Temple Aprobelle rumor bulletins and port-clerk anomalies to identify repeat violators.","yellow"),
T("Blore Heath–Jogi–Mylecia legacy traffic review: flag vessels using old Maritime Markets routing habits to conceal modern smuggling or anti-Bassaridian coordination.","yellow"),
T("Strait shrine-security patrol: inspect small waterfront shrines, offering houses, and pilgrimage piers for coercion, false cult stamps, or hidden courier drops.","yellow"),
T("Home-port sovereignty watch: remain stationed from the captain's assigned Haifan port while extending enforcement by patrol boat, rail liaison, and inland-route detachments.","green")
}
local tasks_central = {
T("Canal-city inner-basin patrol: lock-gate oversight, pier security, and approach-lane order enforcement.","green"),
T("VBSS rotation focused on paperwork integrity and counterfeit seal detection at high-throughput quays.","yellow"),
T("Interdiction against canal runner craft attempting to bypass inspection windows using side channels.","yellow"),
T("Harbor traffic control: queue metering, berth discipline, and stand-off enforcement for noncompliant hulls.","green"),
T("Anti-smuggling sweep targeting disguised ritual cargo and forged Temple consignment manifests.","yellow"),
T("Rapid response support to civic authorities during unrest near the docks; dispersal corridors opened.","red"),
T("Search-and-rescue standby near canal choke points; debris fields secured and cleared.","red"),
T("Liaison with Temple auditors and port clerks: compliance reinforcement and morale discipline checks.","yellow"),
T("Night interception detail targeting unlit barges and covert tender rendezvous.","yellow"),
T("Evidence convoy run to War League intake point; chain-of-custody doctrine enforced.","yellow"),
T("Inspection of cold-chain and fishery cargo for hidden compartments and counterfeit seals.","yellow"),
T("Anti-piracy deterrence sweep against unaffiliated raiders operating outside registry rules.","yellow"),
T("Boarding-team readiness cycle emphasizing non-lethal deck control and restraint discipline.","green"),
T("Escort for Temple couriers through canal junctions; counter-ambush screening at bridges.","yellow"),
T("Counterfeit voucher and registry-stamp crackdown in dockside markets.","yellow"),
T("Harbor-mouth overwatch with spotters to track suspicious loitering near approach lanes.","green"),
T("Quarantine cordon support for suspected outbreak vessel; isolation lanes enforced.","red"),
T("Canal infrastructure protection: lock-gates and waterworks sabotage prevention.","red"),
T("Training cruise certifying boarding compliance and med-triage procedure for new crews.","green"),
T("Routine patrol and compliance enforcement across primary canal approaches.","green")
}
local tasks_privateering = {
T("Privateering sortie against non-aligned vessels: inspection and seizure of contraband goods concealed in false bilge compartments.","yellow"),
T("Privateering sortie against non-aligned vessels: boarding action against a suspected pirate-supply barque, confiscating arms and fuel.","red"),
T("Privateering sortie against non-aligned vessels: capture of a runner dhow attempting corridor bypass under black sail.","yellow"),
T("Privateering sortie against non-aligned vessels: interdiction of a false-distress decoy ship used to lure lawful traffic.","yellow"),
T("Privateering sortie against non-aligned vessels: seizure of illicit currency and forged vouchers intended for dockside laundering.","yellow"),
T("Privateering sortie against non-aligned vessels: raid on a coastal cache pier, dismantling ladders, crates, and mooring points.","red"),
T("Privateering sortie against non-aligned vessels: pursuit and boarding under cover of fog, enforcing forfeiture for evasion.","red"),
T("Privateering sortie against non-aligned vessels: escort of a captured prize through the harbor mouth under guard.","yellow"),
T("Privateering sortie against non-aligned vessels: boarding and confiscation of unregistered ritual cargo transported under counterfeit shrine seals.","red"),
T("Privateering sortie against non-aligned vessels: interdiction of a night-transfer rendezvous, dispersing tenders and capturing broker craft.","red"),
T("Privateering sortie against non-aligned vessels: targeted inspection of hulls linked to prior paper anomalies, enforcing forfeiture for repeat fraud.","yellow"),
T("Privateering sortie against non-aligned vessels: punitive raid on suspected resupply pier and fuel caches.","red"),
T("Privateering sortie against non-aligned vessels: seizure of contraband medicines and counterfeit labels; evidence packaged for clerks.","yellow"),
T("Privateering sortie against non-aligned vessels: dismantle an illegal buoy line guiding ships off-lane; confiscate charts and signal codes.","yellow"),
T("Privateering sortie against non-aligned vessels: detain a pirate recruiter crew; transport under restraint doctrine.","red"),
T("Privateering sortie against non-aligned vessels: sweep of shadowed inlets for hidden tenders; tow seized craft to inspection berth.","yellow"),
T("Privateering sortie against non-aligned vessels: strike deterrence demonstration and warning shots at standoff distance.","yellow"),
T("Privateering sortie against non-aligned vessels: capture of a smuggling skiff convoy and destruction of caches.","red"),
T("Privateering sortie against non-aligned vessels: enforce forfeiture on repeat-violator hull; escort to custody quay.","yellow"),
T("Privateering sortie against non-aligned vessels: raid on ritual contraband broker and seizure of ledgers.","red")
}
local tasks_southern_straits_privateering = {
T("Southern Strait privateering sortie: seize a non-aligned runner carrying forged Straits Conventions papers and hidden arms under legitimate fishery cargo.","red"),
T("Southern Strait privateering sortie: board a former Maritime Markets broker craft suspected of laundering cargo identity through old port ledgers.","yellow"),
T("Southern Strait privateering sortie: capture an unlit lighter shadowing missionary traffic and escort the prize back to the home port.","red"),
T("Southern Strait privateering sortie: raid a covert shoreline cache used to move ritual contraband, signal flags, and counterfeit Temple seals inland.","red"),
T("Southern Strait privateering sortie: enforce forfeiture against a repeat-violator hull using false distress lamps to bypass Bassaridian inspection windows.","yellow"),
T("Southern Strait privateering sortie: intercept a smuggler convoy at the harbor-to-road transfer point and preserve evidence for Straits Convention review.","red"),
T("Southern Strait privateering sortie: detain pirate recruiters posing as ferry brokers near a pilgrimage landing used by Reformed Stripping Path missionaries.","red"),
T("Southern Strait privateering sortie: escort a captured prize through the home-port quay while clerks reconcile manifests, beacon records, and witness statements.","yellow")
}
-- RAIL WATCH (TME / Rail Watch Corps pools)
local tasks_rail_general = {
T("TME Rail Watch: station perimeter and platform safety sweep; deter pickpocket rings and rumor agitation in concourse lines.","green"),
T("TME Rail Watch: signal-box audit and timetable integrity check; confirm block clearances and prevent spoofed movement orders.","green"),
T("TME Rail Watch: rolling-stock inspection for tamper marks, hidden voids, and counterfeit seals on high-value freight consists.","yellow"),
T("TME Rail Watch: cargo manifest verification against yard ledgers; identify false routing, phantom transfers, and laundering patterns.","yellow"),
T("TME Rail Watch: bridge-and-culvert patrol along a priority segment; sabotage indicators documented and handed to engineers.","yellow"),
T("TME Rail Watch: escort for a secured freight movement (armored cars / sealed registry cargo) through a high-risk corridor.","yellow"),
T("TME Rail Watch: trackside surveillance for illicit spur usage and night unloads; seize tools and detain handlers when lawful.","yellow"),
T("TME Rail Watch: incident response drill—passenger triage, evacuation routing, and station lock-down sequencing.","green"),
T("TME Rail Watch: counter-smuggling sweep at intermodal transfer points (rail ↔ ferry/road), focusing on weapons, ritual contraband, and forged vouchers.","yellow"),
T("TME Rail Watch: ticket-fraud and identity-screening surge for suspect travel patterns tied to insurgent facilitators.","yellow"),
T("TME Rail Watch: depot access-control enforcement; verify staff badges, contractor rosters, and guard against insider compromise.","yellow"),
T("TME Rail Watch: hazardous cargo protocol verification (placards, segregation, firebreak placement) with fire brigade liaison.","green"),
T("TME Rail Watch: anti-theft patrol in yard stacks; intercept cargo pilferage crews and document chain-of-custody breaches.","yellow"),
T("TME Rail Watch: crowd-control support during delays; manage queues, prevent stampede risk, and suppress panic narratives.","red"),
T("TME Rail Watch: emergency stop event investigation—inspect brake lines, couplers, and switch throws for sabotage.","red"),
T("TME Rail Watch: tunnel/covered-cut inspection escort for maintenance teams; secure both portals and communications relays.","yellow"),
T("TME Rail Watch: escort War League / Temple auditors on a compliance run through priority stations and depots.","yellow"),
T("TME Rail Watch: communications security sweep—radio discipline, repeater checks, and interception countermeasures.","green"),
T("TME Rail Watch: secure transfer of seized evidence and detainees to intake authority under custody doctrine.","yellow"),
T("TME Rail Watch: rapid-response support to local authorities for rail-linked unrest, strikes, or depot occupation attempts.","red")
}
local tasks_rail_north = {
T("TME Rail Watch (North): snow/ice speed-restriction enforcement and switch-heater protection; prevent sabotage under low visibility.","yellow"),
T("TME Rail Watch (North): plow-train escort and drift clearance security; deter raids against immobilized consists.","yellow"),
T("TME Rail Watch (North): cold-chain cargo integrity checks (fish, furs, ore samples) and counterfeit tax-seal detection.","yellow"),
T("TME Rail Watch (North): ferry-terminal interface security (where applicable); prevent diversion of passengers and registry cargo.","yellow"),
T("TME Rail Watch (North): trackside watch against signal fires / illicit beacons used to cue night unloads.","yellow")
}
local tasks_rail_south = {
T("TME Rail Watch (South): corridor compliance sweep—customs coordination, manifest correction, and targeted interdictions for false routing.","yellow"),
T("TME Rail Watch (South): station surge during peak throughput; platform discipline and baggage screening intensified.","yellow"),
T("TME Rail Watch (South): sabotage prevention along canal/lock-adjacent segments; protect power feeds and switch yards.","red"),
T("TME Rail Watch (South): hazmat standby for refinery/industrial cargo; rapid isolation lanes prepared at the yard.","red"),
T("TME Rail Watch (South): escort for diplomatic / Temple couriers using express service; counter-surveillance checks at stops.","yellow")
}
local tasks_rail_southern_straits = {
T("TME Rail Watch / Southern Strait: inspect inland cargo transfers leaving the port district, focusing on forged registry seals and false destination ledgers.","yellow"),
T("TME Rail Watch / Southern Strait: escort Reformed Stripping Path missionaries along rail-linked shrine routes where dockside unrest may spill inland.","yellow"),
T("TME Rail Watch / Southern Strait: coordinate with road wardens and canal clerks to close smuggling gaps between harbor warehouses and inland caravan yards.","yellow"),
T("TME Rail Watch / Southern Strait: station-platform enforcement against courier teams moving counterfeit Straits Conventions documents in passenger baggage.","yellow"),
T("TME Rail Watch / Southern Strait: protect missionary medical stores, shrine records, and Temple Bank vouchers during a tense inland transfer.","yellow"),
T("TME Rail Watch / Southern Strait: rapid-response patrol for suspected sabotage against switches, signal boxes, or canal-adjacent freight yards.","red"),
T("TME Rail Watch / Southern Strait: review old Maritime Markets brokerage codes against Bassaridian cargo ledgers to identify laundering patterns.","green"),
T("TME Rail Watch / Southern Strait: shadow a suspect freight consist from the home port to its inland interchange before conducting a controlled stop-search.","red")
}
local tasks_rail_central = {
T("TME Rail Watch (Central): intermodal yard patrol—rail ↔ canal logistics verified; prevent shadow transfers and counterfeit seals.","yellow"),
T("TME Rail Watch (Central): station crowd-control augmentation for civic observances; manage arrivals to avoid crush risk.","yellow"),
T("TME Rail Watch (Central): registry office liaison—voucher fraud detection and ledger reconciliation for repeated anomalies.","yellow"),
T("TME Rail Watch (Central): canal-bridge rail span inspection and pier-adjacent fence line security; sabotage deterrence.","yellow"),
T("TME Rail Watch (Central): courier-train protection detail—secure dispatch of high-priority parcels and sealed documents.","green")
}
local tasks_rail_interior = {
T("TME Rail Watch (Interior): tunnel portal security and rockfall/route integrity checks; escort engineers through constrained segments.","yellow"),
T("TME Rail Watch (Interior): remote depot inspection—fuel stores, spare parts, and communications kit audited for theft and tampering.","yellow"),
T("TME Rail Watch (Interior): volcanic/thermal corridor precautions (where applicable); enforce restricted access and guard maintenance crews.","yellow"),
T("TME Rail Watch (Interior): anti-bandit sweep along sparsely monitored branch lines; detain raiders and recover stolen cargo.","red"),
T("TME Rail Watch (Interior): passenger evacuation rehearsal for stranded consists; establish perimeter and medical handoff plan.","green")
}
local tasks_rail_ouriana = {
T("TME Rail Watch (Ouriana): grain/livestock freight escort and yard security; prevent rustling-style cargo diversion.","yellow"),
T("TME Rail Watch (Ouriana): bridge patrol over river crossings; floodwatch coordination and sabotage interdiction.","yellow"),
T("TME Rail Watch (Ouriana): railhead security for War League logistics movements; access control and schedule secrecy enforced.","yellow"),
T("TME Rail Watch (Ouriana): rural station patrol—ticket fraud suppression and contraband checks on feeder lines.","green"),
T("TME Rail Watch (Ouriana): emergency response to track washout / derailment risk; establish cordon and protect recovery crews.","red")
}
local tasks_rail_privateering = {
T("Rail interdiction under Hatch Ministry authority: seize contraband railcars flagged by ledger anomalies; detain handlers and preserve evidence.","red"),
T("Rail interdiction under Hatch Ministry authority: intercept a night offload at a branch depot; confiscate arms and fuel caches.","red"),
T("Rail interdiction under Hatch Ministry authority: stop-and-search of an express consist for counterfeit vouchers and forged cargo seals.","yellow"),
T("Rail interdiction under Hatch Ministry authority: capture a rogue courier team moving ritual contraband under counterfeit shrine documentation.","red"),
T("Rail interdiction under Hatch Ministry authority: escort seized rolling stock to a secured yard; enforce chain-of-custody doctrine.","yellow"),
T("Rail interdiction under Hatch Ministry authority: raid an illicit spur-line cache and dismantle transfer ramps and signal markers.","red"),
T("Rail interdiction under Hatch Ministry authority: detain a repeat-violator freight broker; seize ledgers and routing codes.","red"),
T("Rail interdiction under Hatch Ministry authority: intermodal ferry-rail inspection—confiscate hidden cargo in false compartments.","yellow")
}
--------------------------------------------------------------------------------
-- 6.5) NOCTIC-RABREV / NOCTIC VAMPIRISM OPS INSERTS
--
-- These task pools add Noctic-Rabrev cargo, raw-leaf custody, Noctic Vampirism
-- outbreak, Temple Bank seal, Noctic Fleet, and nocticine-related interdiction
-- events to both sea/privateering and TME Rail Watch outputs.
--------------------------------------------------------------------------------
local nocticStationRisk = {
["Somniumpolis"] = 8,
["Odiferia"] = 8,
["Erythros"] = 7,
["Acheron"] = 6,
["Norsolyra"] = 6,
["Skøda"] = 6,
["Nexa"] = 4,
["Pyralis"] = 4,
["Vaeringheim"] = 3,
["Jogi"] = 3,
["Aurelia"] = 3,
["Saluria"] = 3,
["Koinonía"] = 2,
["Aureum"] = 2,
["Aetherium"] = 2,
["Port of Corumia"] = 1,
["Port of Euranidom"] = 1,
["Port of Southern Keltiania"] = 1
}
local function nocticRepeatCount(station)
local risk = nocticStationRisk[station] or 0
if risk >= 8 then return 4 end
if risk >= 6 then return 3 end
if risk >= 4 then return 2 end
if risk >= 1 then return 1 end
return 0
end
local tasks_noctic_general = {
T("[[Noctic-Rabrev]] custody verification: raw-leaf crates, flower extract, tincture jars, and nocticine concentrates checked against [[Temple Bank of the Reformed Stripping Path|Temple Bank]] seals and [[General Port of Lake Morovia]] manifests.","yellow"),
T("[[Noctic Vampirism]] incident support: escort cult-health evaluators to a registered household while protecting patient privacy, witness safety, and clinic access.","yellow"),
T("Noctic product compliance sweep: distinguish safe flower-based cargo from restricted raw leaves; suspect containers isolated for Temple and port-clerk inspection.","yellow"),
T("Noctic rumor-control escort: secure the approach to a clinic or shrine after reports of blood-hunger panic, forged warnings, or unsafe raw-leaf handling.","yellow"),
T("Licensed nocticine transfer escort: verify custody packets for refined therapeutic tinctures used in post-traumatic care; prevent substitution with unrefined leaf resin.","green"),
T("Noctic vendor paperwork review: compare dosage stamps, field-origin tags, and Temple Bank countersigns before release into market lanes.","green"),
T("Restricted-leaf holding action: keep seized [[Noctic-Rabrev]] material under guard until cult-health officers and auditors complete chain-of-custody review.","yellow"),
T("Emergency cordon for suspected raw-leaf exposure cluster; keep crowd lanes open for medical intake while blocking retaliation against registered Noctic patients.","red")
}
local tasks_noctic_sea = {
T("Canal and wetland quay patrol for Noctic Fleet consignments; inspect sealed leaf chests, flower bales, and tincture crates moving through marsh-side piers.","yellow"),
T("Small-craft inspection for hidden raw [[Noctic-Rabrev]] leaves packed beneath legitimate flower cargo; detain handlers and preserve crate seals.","yellow"),
T("Noctic Fleet escort through fogged canal approaches; prevent runner craft from shadowing licensed Noctic cargo toward unauthorized shrine landings.","yellow"),
T("Blood Orchard export check: verify field-origin tags, wetland harvest stamps, and Temple Bank countersigns before Noctic material enters General Port routing.","green"),
T("Harbor quarantine lane support after suspected Noctic Vampirism exposure aboard a cargo skiff; controlled disembarkation and clinic transfer enforced.","red"),
T("Night-water interdiction against unlit boats carrying raw-leaf bundles toward informal blood rites; evidence and detainees transferred under seal.","red")
}
local tasks_noctic_rail = {
T("TME Rail Watch Noctic cargo screen: inspect sealed compartments carrying [[Noctic-Rabrev]] flower extract, tinctures, and restricted raw-leaf packets.","yellow"),
T("Station-platform protection for registered Noctic patients traveling under cult-health escort; prevent crowd harassment and unauthorized press of pilgrims.","yellow"),
T("Rail manifest reconciliation for Noctic Fleet freight: field stamps, Temple Bank seals, and General Port routing numbers compared before departure.","green"),
T("Branch-line inspection for counterfeit Noctic permits; stop-and-search freight handlers moving ritual leaf cargo under false shrine documentation.","yellow"),
T("Night depot watch after reports of blood-hunger panic among passengers; separate medical intake from rumor-driven crowd movement.","red"),
T("Emergency railcar cordon after raw-leaf residue discovered in a passenger compartment; secure witnesses, quarantine luggage, and summon cult-health officers.","red")
}
local tasks_noctic_skoda = {
T("[[List of cities in Bassaridia Vaeringheim#Skøda|Skøda]] mirror-vigil security: escort Temple witnesses through shuttered terrace lanes after Noctic fever and false-revelation reports.","yellow"),
T("Skødan hidden-name custody check: protect registered Noctic households from crowd intrusion while clerks verify permits for preserved raw-leaf material.","yellow"),
T("Covered-mirror district patrol after Noctic Vampirism rumors; prevent vigilante searches, secure cistern courts, and route patients to cult-health review.","red"),
T("Highland rail stop inspection near Skøda: search baggage and ritual textiles for concealed Noctic leaves, counterfeit dream-mapping permits, and forged Temple seals.","yellow")
}
local tasks_noctic_privateering = {
T("Privateering sortie against Noctic smugglers: board a runner craft carrying raw [[Noctic-Rabrev]] leaves beneath legal flower extract cargo; seize manifests and detain handlers.","red"),
T("Privateering sortie against counterfeit Noctic brokers: raid a pier cache containing forged Temple Bank seals, nocticine tincture labels, and unregistered raw-leaf packets.","red"),
T("Privateering sortie against non-aligned vessels: intercept a night-transfer of restricted Noctic cargo intended for unauthorized blood rites.","red"),
T("Privateering sortie against nocticine traffickers: capture broker craft moving high-potency tinctures outside General Port custody channels.","yellow"),
T("Privateering sortie against false medical consignments: seize crates misdeclared as therapeutic nocticine concentrate but packed with unprocessed raw leaves.","red"),
T("Privateering sortie against shrine-document fraud: detain couriers carrying counterfeit induction permits and restricted-leaf vouchers.","yellow")
}
local tasks_noctic_rail_privateering = {
T("Rail interdiction under Hatch Ministry authority: seize a freight car carrying undeclared raw [[Noctic-Rabrev]] leaves behind false flower-extract placards.","red"),
T("Rail interdiction under Hatch Ministry authority: intercept a branch-depot offload of Noctic cargo intended for unauthorized induction rites.","red"),
T("Rail interdiction under Hatch Ministry authority: detain a broker team moving forged Temple Bank Noctic permits through passenger luggage.","yellow"),
T("Rail interdiction under Hatch Ministry authority: isolate a contaminated railcar after raw-leaf residue triggers Noctic Vampirism symptoms among travelers.","red"),
T("Rail interdiction under Hatch Ministry authority: escort seized Noctic Fleet rolling stock to a secured yard while preserving field-origin tags and seal evidence.","yellow"),
T("Rail interdiction under Hatch Ministry authority: raid an illicit spur-line cache of nocticine tinctures, raw-leaf packets, and counterfeit shrine documentation.","red")
}
local tasks_noctic_external = {
T("Straits Conventions Noctic cargo review: inspect distant brokerage-port consignments for raw [[Noctic-Rabrev]] leaves hidden inside flower extract, incense, or medical tincture crates.","yellow"),
T("External brokerage compliance action: verify Noctic Fleet export papers, Temple Bank seals, and White-Lane cargo declarations before onward movement.","green"),
T("Counter-smuggling watch for Noctic cargo routed through distant Bassarid brokerage ports; suspect brokers logged for SCT or SAC review.","yellow")
}
local function appendPool(dst, src)
for _, t in ipairs(src) do
table.insert(dst, t)
end
end
local function addNocticTasksToPool(pool, station, domain)
local reps = nocticRepeatCount(station)
if reps <= 0 then return end
for _ = 1, reps do
appendPool(pool, tasks_noctic_general)
if domain == "rail" then
appendPool(pool, tasks_noctic_rail)
else
appendPool(pool, tasks_noctic_sea)
end
if station == "Skøda" then
appendPool(pool, tasks_noctic_skoda)
end
end
end
local function buildPrivateeringPool(domain, station, cap)
local pool = {}
if cap and cap.mandate == "straits_conventions" then
appendPool(pool, tasks_straits_conventions)
appendPool(pool, tasks_noctic_external)
return pool
end
if domain == "rail" then
appendPool(pool, tasks_rail_privateering)
else
appendPool(pool, tasks_privateering)
end
if isHaifanBassaridiaPort(station) then
appendPool(pool, tasks_southern_straits_privateering)
end
local reps = nocticRepeatCount(station)
for _ = 1, reps do
if domain == "rail" then
appendPool(pool, tasks_noctic_rail_privateering)
else
appendPool(pool, tasks_noctic_privateering)
end
end
return pool
end
local function pickTask(nextInt, station)
local pool = {}
for _, t in ipairs(tasks_general) do table.insert(pool, t) end
local cl = clusterForCity(station)
if cl == "External" then
for _, t in ipairs(tasks_straits_conventions) do table.insert(pool, t) end
elseif cl == "North" or cl == "Jangsong" then
for _, t in ipairs(tasks_north) do table.insert(pool, t) end
elseif cl == "South" then
for _, t in ipairs(tasks_south) do table.insert(pool, t) end
for _, t in ipairs(tasks_southern_straits) do table.insert(pool, t) end
-- Add several cult-specific missionary escorts so they appear often enough
-- to matter, but do not fully replace ordinary privateering / enforcement.
if isHaifanBassaridiaPort(station) then
for _ = 1, 4 do
table.insert(pool, makeSouthernStraitMissionaryEscort(nextInt, station, "sea"))
end
end
else
for _, t in ipairs(tasks_central) do table.insert(pool, t) end
end
addNocticTasksToPool(pool, station, "sea")
return pickFromList(nextInt, pool, nil) or T("Standard patrol and compliance enforcement.","green")
end
local function pickTaskRail(nextInt, station)
local pool = {}
for _, t in ipairs(tasks_rail_general) do table.insert(pool, t) end
local cl = clusterForCity(station)
if cl == "North" or cl == "Jangsong" then
for _, t in ipairs(tasks_rail_north) do table.insert(pool, t) end
elseif cl == "South" then
for _, t in ipairs(tasks_rail_south) do table.insert(pool, t) end
for _, t in ipairs(tasks_rail_southern_straits) do table.insert(pool, t) end
-- Cult-specific missionary escorts for inland routes, rail stations,
-- caravan yards, canal roads, and port-to-interior transfers.
if isHaifanBassaridiaPort(station) then
for _ = 1, 4 do
table.insert(pool, makeSouthernStraitMissionaryEscort(nextInt, station, "rail"))
end
end
elseif cl == "Ouriana" then
for _, t in ipairs(tasks_rail_ouriana) do table.insert(pool, t) end
elseif cl == "Interior" then
for _, t in ipairs(tasks_rail_interior) do table.insert(pool, t) end
else
for _, t in ipairs(tasks_rail_central) do table.insert(pool, t) end
end
addNocticTasksToPool(pool, station, "rail")
return pickFromList(nextInt, pool, nil) or T("TME Rail Watch: standard patrol and compliance enforcement.","green")
end
--------------------------------------------------------------------------------
-- 7) ASSIGNMENT MODEL (station blocks of 7–14 days)
--------------------------------------------------------------------------------
local function homeCityForCaptain(cap)
-- Optional override: fixed home city
if cap and cap.home and cap.home ~= "" then
return cap.home
end
local key = (cap.last or "") .. "|" .. (cap.first or "")
local idx = (hash31(key) % #cities) + 1
return cities[idx]
end
local function blockLenForCaptain(cap)
local key = "len|" .. (cap.last or "") .. "|" .. (cap.first or "")
return 7 + (hash31(key) % 8) -- 7..14
end
local function stationForBlock(cap, blockIndex)
local home = homeCityForCaptain(cap)
-- Haifan Bassaridia captains do not rotate away from their assigned home ports.
-- Their wider Southern Strait activity is represented by task text and the
-- Current Station display label, not by moving them to a different city.
if isHaifanBassaridiaPort(home) then
return home
end
local key = string.format("station|%s|%s|%d", cap.last or "", cap.first or "", blockIndex)
local nextInt = lcg(hash31(key))
-- Optional override: restrict station choice to a specific list
if cap and cap.roam and type(cap.roam) == "table" and #cap.roam > 0 then
return pickFromList(nextInt, cap.roam, nil) or cap.roam[1]
end
local cl = clusterForCity(home)
local localList = clusters[cl] or clusters.Central
local r = rand01(nextInt)
if r < 0.65 then
return home
elseif r < 0.92 then
return pickFromList(nextInt, localList, home) or home
else
return pickFromList(nextInt, cities, home) or home
end
end
local function stationDisplayForAssignment(cap, absDay, station)
local home = homeCityForCaptain(cap)
if not isHaifanBassaridiaPort(home) then return nil end
local key = string.format("sstation|%s|%s|%d", cap.last or "", cap.first or "", absDay)
local nextInt = lcg(hash31(key))
-- Most days display the actual home port. Some days display the wider
-- Southern Strait posture while still computing weather and tasking from home.
if rand01(nextInt) < 0.52 then
return nil
end
local labels = {
"''Southern Straits Deployment''",
"''Southern Strait Privateering Screen''",
"''Southern Strait Anti-Smuggling Screen''",
"''Southern Straits Missionary Escort Watch''",
"''Southern Strait Inland Enforcement Line''",
"''Former Maritime Markets Compliance Station''",
"''Southern Strait Prize-Court Support Watch''",
"''Southern Strait Pilgrim-Route Security Line''"
}
local label = pickFromList(nextInt, labels, nil) or "''Southern Straits Deployment''"
return label .. "<br/><span style='font-size:90%;'>staged from " .. cityLink(home) .. "</span>"
end
local function maybePrivateering(cap, absDay)
local key = string.format("priv|%s|%s|%d", cap.last or "", cap.first or "", absDay)
local nextInt = lcg(hash31(key))
return (rand01(nextInt) < 0.10), nextInt
end
local function todaysAssignment(cap, year, dayOfYear, eventText)
local absDay = year * DAYS_IN_YEAR + dayOfYear
local blockLen = blockLenForCaptain(cap)
local offset = hash31("off|" .. (cap.last or "") .. "|" .. (cap.first or "")) % blockLen
local blockIndex = math.floor((absDay + offset) / blockLen)
local dayInBlock = ((absDay + offset) % blockLen) + 1
local station = stationForBlock(cap, blockIndex)
local stationDisplay = stationDisplayForAssignment(cap, absDay, station)
local domain = pickDomain(cap, absDay, station)
-- baseline task (deterministic)
local nextInt = lcg(hash31(string.format("task|%s|%s|%d|%s", cap.last or "", cap.first or "", absDay, domain)))
local taskEntry = (domain == "rail") and pickTaskRail(nextInt, station) or pickTask(nextInt, station)
local text = taskEntry.text
local sev = taskEntry.sev or "green"
-- 10% chance: opportunistic interdiction (sea: privateering sortie; rail: rail interdiction)
local priv, privRng = maybePrivateering(cap, absDay)
if priv then
local pool = buildPrivateeringPool(domain, station, cap)
local pe = pickFromList(privRng, pool, nil) or pool[1]
text = pe.text
sev = pe.sev or "yellow"
if cap.last == "Urbanus" and cap.first == "Cornelia" then
text = "Minister-Captain sanction: " .. text
if sev == "green" then sev = "yellow" end
end
end
-- movement flavor on block edges
if isHaifanBassaridiaPort(homeCityForCaptain(cap)) then
if dayInBlock == 1 then
text = "Home-port sortie initiation: " .. text
elseif dayInBlock == blockLen then
text = "Home-port watch reset: " .. text
end
else
if dayInBlock == 1 then
text = "Redeployment and patrol initiation: " .. text
elseif dayInBlock == blockLen then
text = "Handover and corridor reset: " .. text
end
end
-- If today’s calendar event tags the station in square brackets, bump urgency and add augmentation note
if eventText and eventText ~= "" then
local tag = "%[" .. station:gsub("(%W)","%%%1") .. "%]"
if eventText:match(tag) then
text = text .. " Festival and crowd-control augmentation in support of scheduled civic observances."
sev = bumpSev(sev)
end
end
-- Straits Conventions detached commission overlay
if cap.mandate == "straits_conventions" then
text = "Straits Conventions detached commission: " .. text ..
" Confederacy of the Dispossessed-linked vessels, brokers, and courier networks are treated as priority targets under corridor compliance rules."
if sev == "green" then sev = "yellow" end
end
-- Minister-Captain overlay (only if not already framed as sanction)
if cap.last == "Urbanus" and cap.first == "Cornelia" then
if not text:match("^Minister%-Captain") then
local prefix = (domain == "rail")
and "Minister-Captain coordination: liaison with station clerks and Temple auditors; "
or "Minister-Captain coordination: liaison with port clerks and Temple auditors; "
text = prefix .. text
if sev == "green" then sev = "yellow" end
end
end
-- WX overlay (Orange/Red only): weather posture + severity bump (domain-aware)
text, sev = applyWeatherOverlay(text, sev, station, year, dayOfYear, domain)
return {
home = homeCityForCaptain(cap),
station = station,
stationDisplay = stationDisplay,
blockLen = blockLen,
dayInBlock = dayInBlock,
task = text,
sev = sev,
domain = domain
}
end
--------------------------------------------------------------------------------
-- 8) OUTPUT
--------------------------------------------------------------------------------
function p.dailyReport(frame)
local args = (frame and frame.args) or {}
local y, doy, month, calStr, eventText = getCurrentYDOY()
-- Optional overrides:
-- {{#invoke:HatchMinistryDailyOps|dailyReport|year=52|day=118}}
if args.year and args.day then
local yy = tonumber(args.year)
local dd = tonumber(args.day)
if yy and dd and dd >= 1 and dd <= DAYS_IN_YEAR then
y, doy = yy, dd
end
end
local showLegend = true
if args.legend ~= nil then
local v = tostring(args.legend):lower()
showLegend = not (v == "0" or v == "no" or v == "false")
end
-- Optional captain filter:
-- {{#invoke:HatchMinistryDailyOps|dailyReport|captain=Ayshe Temirkhan}}
local captainFilter = nil
if args.captain and tostring(args.captain):match("%S") then
captainFilter = mw.text.trim(tostring(args.captain))
end
local southernStraitOnly = false
do
local z = mw.ustring.lower(tostring(args.zone or args.scope or args.region or args.theater or ""))
southernStraitOnly = (
z == "southernstrait"
or z == "southern strait"
or z == "southern straits"
or z == "haifan"
or z == "haifan bassaridia"
or z == "former maritime markets"
)
end
local function captainMatches(cap, filter)
if not filter then return true end
local full = string.format("%s %s", cap.first or "", cap.last or "")
local fullLower = mw.ustring.lower(full)
local filterLower = mw.ustring.lower(filter)
return fullLower == filterLower
end
local out = {}
table.insert(out, string.format("'''Hatch Ministry Daily Operations – Day %d, Year %d PSSC'''", doy, y))
if calStr and calStr ~= "" then
table.insert(out, "''Calendar:'' " .. calStr)
end
table.insert(out, '{| class="wikitable sortable"')
table.insert(out, '! Privateer Flag !! Captain !! Home City !! Current Station !! Ops Alert !! Rotation !! Today\'s Activity')
local rendered = 0
for _, cap in ipairs(privateers) do
local capHome = homeCityForCaptain(cap)
if captainMatches(cap, captainFilter)
and (not southernStraitOnly or isHaifanBassaridiaPort(capHome)) then
local a = todaysAssignment(cap, y, doy, eventText)
local rowStyle = sevStyle(a.sev)
local pFlag = privateerFlag(cap)
local name = privateerNameRow(cap)
local homeCell = cityFlagIcon(a.home, 20) .. " " .. cityLink(a.home)
local stationCell
if a.stationDisplay and a.stationDisplay ~= "" then
stationCell = cityFlagIcon(a.home, 20) .. " " .. a.stationDisplay
else
stationCell = cityFlagIcon(a.station, 20) .. " " .. cityLink(a.station)
end
local rot = string.format("%d/%d", a.dayInBlock, a.blockLen)
local opsCell = "n/a"
if wx and type(wx.getOpsData) == "function" then
local op = getWx(a.station, y, doy)
if op and op.alert then
opsCell = wxBadge(op.alert.level, op.alert.label, op.condition)
end
end
table.insert(out, "|-" .. rowStyle)
table.insert(out, string.format('| %s || %s || %s || %s || %s || %s || %s',
pFlag, name, homeCell, stationCell, opsCell, rot, a.task
))
rendered = rendered + 1
end
end
if rendered == 0 then
table.insert(out, '|-')
if captainFilter then
table.insert(out, string.format('| colspan="7" style="text-align:center;" | No Southern Strait captain matched "%s".', captainFilter))
elseif southernStraitOnly then
table.insert(out, '| colspan="7" style="text-align:center;" | No Southern Strait / Haifan Bassaridian privateer entries generated.')
else
table.insert(out, '| colspan="7" style="text-align:center;" | No privateer entries generated.')
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:#e8f5e9;padding:2px 6px;border:1px solid #c8e6c9;'>Green</span> routine / low concern; ")
table.insert(out, "<span style='background:#fff8e1;padding:2px 6px;border:1px solid #ffecb3;'>Yellow</span> elevated concern; ")
table.insert(out, "<span style='background:#ffebee;padding:2px 6px;border:1px solid #ffcdd2;'>Red</span> high concern / heavy enforcement.")
table.insert(out, "</div>")
end
return table.concat(out, "\n")
end
-- ============================================================================
-- PUBLIC HELPER: one computed captain assignment, reusable by narrative modules
-- ============================================================================
local function findCaptainByName(name)
if not name or tostring(name):match("^%s*$") then return nil end
local needle = mw.ustring.lower(mw.text.trim(tostring(name)))
for _, cap in ipairs(privateers) do
local full = string.format("%s %s", cap.first or "", cap.last or "")
if mw.ustring.lower(full) == needle then
return cap
end
end
return nil
end
function p.getCaptainAssignmentData(captainName, year, day)
local y, doy, month, calStr, eventText = getCurrentYDOY()
if tonumber(year) and tonumber(day) then
local yy = tonumber(year)
local dd = tonumber(day)
if yy and dd and dd >= 1 and dd <= DAYS_IN_YEAR then
y, doy = yy, dd
end
end
local cap = findCaptainByName(captainName)
if not cap then
return nil
end
local a = todaysAssignment(cap, y, doy, eventText)
local op = getWx(a.station, y, doy)
return {
captain = {
first = cap.first,
last = cap.last,
nick = cap.nick,
full = string.format("%s %s", cap.first or "", cap.last or "")
},
year = y,
day = doy,
calendar = calStr,
home = a.home,
station = a.station,
stationDisplay = a.stationDisplay,
task = a.task,
severity = a.sev,
dayInBlock = a.dayInBlock,
blockLen = a.blockLen,
domain = a.domain,
weather = op
}
end
function p.getCaptainAssignment(frame)
local args = (frame and frame.args) or {}
local data = p.getCaptainAssignmentData(args.captain, args.year, args.day)
if not data then
return ""
end
return mw.text.jsonEncode(data)
end
return p