Module:HatchMinistryDailyOps
From MicrasWiki
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",
-- 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",
-- 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" }
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)
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"}},
}
local function privateerFlag(last)
return string.format("[[File:%s.png|20px]]", last)
end
local function privateerNameRow(c)
if c.nick then
return string.format("Captain %s ''‘%s’'' %s", c.first, c.nick, c.last)
end
return string.format("Captain %s %s", 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","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"}
}
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.5) DOMAIN SELECTION (Sea Privateering vs TME Rail Watch)
--------------------------------------------------------------------------------
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,["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
}
local function pickDomain(cap, absDay, station)
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
-- 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
-- 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_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_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")
}
-- 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_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")
}
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 == "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
else
for _, t in ipairs(tasks_central) do table.insert(pool, t) end
end
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
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
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: allow a captain to have a 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 key = string.format("station|%s|%s|%d", cap.last or "", cap.first or "", blockIndex)
local nextInt = lcg(hash31(key))
-- Optional override: restrict a captain to a specific station list (exclusive roaming)
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 home = homeCityForCaptain(cap)
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 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 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 = (domain == "rail") and tasks_rail_privateering or tasks_privateering
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 dayInBlock == 1 then
text = "Redeployment and patrol initiation: " .. text
elseif dayInBlock == blockLen then
text = "Handover and corridor reset: " .. text
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
-- 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,
blockLen = blockLen,
dayInBlock = dayInBlock,
task = text,
sev = sev
}
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
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')
for _, cap in ipairs(privateers) do
local a = todaysAssignment(cap, y, doy, eventText)
local rowStyle = sevStyle(a.sev)
local pFlag = privateerFlag(cap.last)
local name = privateerNameRow(cap)
local homeCell = cityFlagIcon(a.home, 20) .. " " .. cityLink(a.home)
local stationCell = cityFlagIcon(a.station, 20) .. " " .. cityLink(a.station)
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
))
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 / Standard ")
table.insert(out, "<span style='background:#fff3cd;padding:2px 6px;border:1px solid #ffeeba;'>Yellow</span> = Elevated / Enforcement ")
table.insert(out, "<span style='background:#f8d7da;padding:2px 6px;border:1px solid #f5c6cb;'>Red</span> = Urgent / High-Risk / Incident")
table.insert(out, "<br/>Ops Alert badge reflects forecast-driven operational risk (Green/Yellow/Orange/Red) and aligns with War League / Missionary posture.")
table.insert(out, "</div>")
end
return table.concat(out, "\n")
end
return p