Module:SouthernStraitCorridorOps: Difference between revisions
From MicrasWiki
Jump to navigationJump to search
NewZimiaGov (talk | contribs) No edit summary |
NewZimiaGov (talk | contribs) No edit summary |
||
| Line 1: | Line 1: | ||
-- Module:SouthernStraitCorridorOps | -- Module:SouthernStraitCorridorOps | ||
local p = {} | local p = {} | ||
| Line 83: | Line 68: | ||
return string.format("%d/%d/%d PSSC", dom, m, y) | return string.format("%d/%d/%d PSSC", dom, m, y) | ||
end | end | ||
return "current PSSC date" | return "current PSSC date" | ||
end | end | ||
| Line 91: | Line 75: | ||
local safeTitle = tostring(title or "Operations") | local safeTitle = tostring(title or "Operations") | ||
local content = tostring(body or "''No output.''") | local content = tostring(body or "''No output.''") | ||
if not collapsible then | if not collapsible then | ||
table.insert(out, "===" .. safeTitle .. "===") | table.insert(out, "===" .. safeTitle .. "===") | ||
| Line 228: | Line 213: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- Forced | -- Forced City/Node display | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local forcedCityNodeText = "[[File:MyleciaFlabB.jpeg|20px]] [[Mylecia]]" | |||
local function cleanHeaderLabel(line) | local function cleanHeaderLabel(line) | ||
| Line 243: | Line 230: | ||
s = s:gsub("%[%[[^%]|]+|([^%]]+)%]%]", "%1") | s = s:gsub("%[%[[^%]|]+|([^%]]+)%]%]", "%1") | ||
s = s:gsub("%[%[([^%]]+)%]%]", "%1") | s = s:gsub("%[%[([^%]]+)%]%]", "%1") | ||
s = s:gsub(" ", " ") | |||
s = s:gsub("%s*/%s*", "/") | |||
s = s:gsub("^%s+", ""):gsub("%s+$", "") | s = s:gsub("^%s+", ""):gsub("%s+$", "") | ||
return s:lower() | return s:lower() | ||
end | end | ||
local | local cityNodeHeaderLabels = { | ||
["city"] = true, | ["city"] = true, | ||
["node"] = true, | |||
["city/node"] = true, | |||
["city / node"] = true, | |||
["city or node"] = true, | |||
["home port"] = true, | ["home port"] = true, | ||
["homeport"] = true, | ["homeport"] = true, | ||
| Line 256: | Line 250: | ||
} | } | ||
local function | local function forceMyleciaCityNodeColumn(body, args) | ||
body = tostring(body or "") | body = tostring(body or "") | ||
| Line 267: | Line 261: | ||
end | end | ||
local | local forcedText = args.forcecitytext or args.forceCityText or forcedCityNodeText | ||
local lines = mw.text.split(body, "\n", true) | local lines = mw.text.split(body, "\n", true) | ||
local out = {} | local out = {} | ||
| Line 283: | Line 277: | ||
elseif inTable and line:match("^%s*!") then | elseif inTable and line:match("^%s*!") then | ||
col = col + 1 | local raw = line:gsub("^%s*!+%s*", "") | ||
local headers = mw.text.split(raw, "!!", true) | |||
if | |||
if #headers > 1 then | |||
for _, h in ipairs(headers) do | |||
col = col + 1 | |||
if cityNodeHeaderLabels[cleanHeaderLabel("! " .. h)] then | |||
cityCol = col | |||
end | |||
end | |||
else | |||
col = col + 1 | |||
if cityNodeHeaderLabels[cleanHeaderLabel(line)] then | |||
cityCol = col | |||
end | |||
end | end | ||
table.insert(out, line) | table.insert(out, line) | ||
| Line 295: | Line 301: | ||
elseif inTable and cityCol and line:match("^%s*|[^}%-%+]") then | elseif inTable and cityCol and line:match("^%s*|[^}%-%+]") then | ||
local raw = line:gsub("^%s*|%s*", "") | |||
local cells = mw.text.split(raw, "||", true) | |||
table.insert(out, "| " .. | |||
if #cells > 1 then | |||
for i = 1, #cells do | |||
if i == cityCol then | |||
cells[i] = " " .. forcedText .. " " | |||
end | |||
end | |||
table.insert(out, "| " .. table.concat(cells, " || ")) | |||
else | else | ||
table.insert(out, line) | col = col + 1 | ||
if col == cityCol then | |||
table.insert(out, "| " .. forcedText) | |||
else | |||
table.insert(out, line) | |||
end | |||
end | end | ||
| Line 319: | Line 337: | ||
local tradeSigningCults = { | local tradeSigningCults = { | ||
{ | { code = "UMB", label = "[[Noctis (Reformed Stripping Path)#Order of the Umbral Oracle|Order of the Umbral Oracle]]" }, | ||
{ code = "GGS", label = "[[Chrysos (Reformed Stripping Path)#Guild of Golden Shadows|Guild of Golden Shadows]]" }, | |||
{ code = "AZS", label = "[[Thalassa (Reformed Stripping Path)#Azure Sentinel Sect|Azure Sentinel Sect]]" }, | |||
{ code = "HAR", label = "[[Micras (Reformed Stripping Path)#Harmony Sanctum|Harmony Sanctum]]" }, | |||
{ | { code = "VIT", label = "[[Plateau (Reformed Stripping Path)#Sanctum Vitalis|Sanctum Vitalis]]" }, | ||
{ code = "AUR", label = "[[Eos (Reformed Stripping Path)#Order of Aurora Mystica|Order of Aurora Mystica]]" }, | |||
{ code = "DEL", label = "[[Sisera (Reformed Stripping Path)#Sanctum Delphica|Sanctum Delphica]]" }, | |||
{ code = "CAT", label = "[[Cato (Reformed Stripping Path)#Rex Catonis|Rex Catonis]]" }, | |||
{ | { code = "CON", label = "[[Lukedu (Reformed Stripping Path)#Accord Concordia|Accord Concordia]]" }, | ||
{ code = "STY", label = "[[Styx (Reformed Stripping Path)#Mystery of the Stygian Veil|Mystery of the Stygian Veil]]" } | |||
{ | |||
{ | |||
{ | |||
{ | |||
{ | |||
{ | |||
{ | |||
} | } | ||
| Line 425: | Line 413: | ||
local rebellionProfiles = { | local rebellionProfiles = { | ||
{ | { label = "Confederacy-adjacent courier screen", description = "False-route couriers testing inspection gaps." }, | ||
{ label = "Former Maritime Markets broker faction", description = "Old broker-house retainers resisting General Port paperwork." }, | |||
{ label = "Anti-registry dock syndicate", description = "Warehouse intermediaries opposing voucher and ledger discipline." }, | |||
{ label = "False-proclamation circle", description = "Pamphlet readers circulating forged civic orders." }, | |||
{ | { label = "Route-barricade committee", description = "Agitators obstructing lawful inspection lanes." }, | ||
{ label = "Dispossessed-sympathetic quay cell", description = "Waterfront cell copying Confederacy-style courier habits." } | |||
{ | |||
{ | |||
{ | |||
{ | |||
} | } | ||
| Line 490: | Line 460: | ||
local complianceProfiles = { | local complianceProfiles = { | ||
{ | { label = "Inspection cooperation", description = "Local carriers complied with inspection sequencing without requiring escalation." }, | ||
{ label = "Ledger correction", description = "Route clerks corrected incomplete or inconsistent paperwork before formal penalty." }, | |||
{ label = "Crowd dispersal", description = "Local stewards dispersed a gathering before it became an obstruction." }, | |||
{ label = "Guide assistance", description = "Licensed guides helped separate lawful traffic from suspect courier movement." }, | |||
{ | { label = "Cargo separation", description = "Bonded cargo was voluntarily separated for review without disrupting ordinary passage." }, | ||
{ label = "Rumor-control compliance", description = "Temple notices were accepted locally and hostile rumors lost momentum." }, | |||
{ label = "Privateer signal compliance", description = "Vessels accepted stand-off or hold-and-release signals without resistance." }, | |||
{ label = "Registry cooperation", description = "Local registrars preserved names, seals, and witness records for later review." } | |||
{ | |||
{ | |||
{ | |||
{ | |||
{ | |||
{ | |||
} | } | ||
| Line 616: | Line 562: | ||
local seq = (pctFromSeed(seed, "seq") % 20) + 1 | local seq = (pctFromSeed(seed, "seq") % 20) + 1 | ||
local eventId | local eventId | ||
if year > 0 and day > 0 then | if year > 0 and day > 0 then | ||
eventId = string.format("SS-RB-%03d.%03d-%s-%02d", year, day, loc.code or "GEN", seq) | eventId = string.format("SS-RB-%03d.%03d-%s-%02d", year, day, loc.code or "GEN", seq) | ||
| Line 745: | Line 690: | ||
if showIntro then | if showIntro then | ||
table.insert(out, string.format( | table.insert(out, string.format( | ||
"''Southern Strait-only operational display for %s, limited to | "''Southern Strait-only operational display for %s, limited to Mylecian Southern Strait corridor entries.''", | ||
label | label | ||
)) | )) | ||
table.insert(out, "") | table.insert(out, "") | ||
table.insert(out, "This display combines Council of Kings deployments, Hatch Ministry privateer activity, Temple Bank missionary operations, row-level Southern Strait locality focus columns, Temple Bank-only trade-agreement sign-offs, row-level compliance observations, and row-level rebellion-type unrest alerts while | table.insert(out, "This display combines Council of Kings deployments, Hatch Ministry privateer activity, Temple Bank missionary operations, row-level Southern Strait locality focus columns, Temple Bank-only trade-agreement sign-offs, row-level compliance observations, and row-level rebellion-type unrest alerts while displaying the operational City/Node as Mylecia.") | ||
end | end | ||
if includeWar then | if includeWar then | ||
| Line 765: | Line 706: | ||
flagsize = args.flagsize or "20", | flagsize = args.flagsize or "20", | ||
zone = "SouthernStrait", | zone = "SouthernStrait", | ||
division = "Council of Kings Division", | division = "Council of Kings Division", | ||
} | } | ||
| Line 776: | Line 714: | ||
local ok, body = safeCall("Module:WarLeagueDivisionOpsBVR", "makeTable", warArgs) | local ok, body = safeCall("Module:WarLeagueDivisionOpsBVR", "makeTable", warArgs) | ||
body = | body = forceMyleciaCityNodeColumn(body, args) | ||
body = addCorridorColumns(body, args, "war") | body = addCorridorColumns(body, args, "war") | ||
| Line 782: | Line 720: | ||
table.insert(out, section("Council of Kings Southern Strait deployments", body, collapseSections, startCollapsed)) | table.insert(out, section("Council of Kings Southern Strait deployments", body, collapseSections, startCollapsed)) | ||
end | end | ||
if includeHatch then | if includeHatch then | ||
| Line 800: | Line 734: | ||
local ok, body = safeCall("Module:HatchMinistryDailyOps", "dailyReport", hatchArgs) | local ok, body = safeCall("Module:HatchMinistryDailyOps", "dailyReport", hatchArgs) | ||
body = | body = forceMyleciaCityNodeColumn(body, args) | ||
body = addCorridorColumns(body, args, "hatch") | body = addCorridorColumns(body, args, "hatch") | ||
| Line 806: | Line 740: | ||
table.insert(out, section("Hatch Ministry Southern Strait privateer operations", body, collapseSections, startCollapsed)) | table.insert(out, section("Hatch Ministry Southern Strait privateer operations", body, collapseSections, startCollapsed)) | ||
end | end | ||
if includeMission then | if includeMission then | ||
| Line 828: | Line 758: | ||
local ok, body = safeCall("Module:MissionaryDailyOpsBVR", "makeTable", missionaryArgs) | local ok, body = safeCall("Module:MissionaryDailyOpsBVR", "makeTable", missionaryArgs) | ||
body = | body = forceMyleciaCityNodeColumn(body, args) | ||
body = addCorridorColumns(body, args, "missionary") | body = addCorridorColumns(body, args, "missionary") | ||
| Line 834: | Line 764: | ||
table.insert(out, section("Temple Bank Southern Strait missionary operations", body, collapseSections, startCollapsed)) | table.insert(out, section("Temple Bank Southern Strait missionary operations", body, collapseSections, startCollapsed)) | ||
end | end | ||
if showLegend then | if showLegend then | ||
local legendBody = table.concat({ | local legendBody = table.concat({ | ||
"* '''Council of Kings Southern Strait deployments''' show only Council of Kings Division War League rows for the Southern Strait corridor.", | "* '''Council of Kings Southern Strait deployments''' show only Council of Kings Division War League rows for the Southern Strait corridor.", | ||
"* '''Hatch Ministry Southern Strait privateer operations''' show | "* '''Hatch Ministry Southern Strait privateer operations''' show Southern Strait privateer rows.", | ||
"* '''Temple Bank Southern Strait missionary operations''' show Haifan Bassaridian missionary rows | "* '''Temple Bank Southern Strait missionary operations''' show Haifan Bassaridian missionary rows, but the displayed City/Node is forced to Mylecia in this wrapper.", | ||
"* '''City display''' is forced to [[File:MyleciaFlabB.jpeg|20px]] [[Mylecia]] for War League, Hatch Ministry, and Temple Bank rows | "* '''City/Node display''' is forced to [[File:MyleciaFlabB.jpeg|20px]] [[Mylecia]] for War League, Hatch Ministry, and Temple Bank rows.", | ||
"* '''Locality focus''' columns assign one extended Southern Strait, Eastern Mykonos Desert, or eastern Snowholme foothill settlement to each generated row when row-level localities are enabled.", | "* '''Locality focus''' columns assign one extended Southern Strait, Eastern Mykonos Desert, or eastern Snowholme foothill settlement to each generated row when row-level localities are enabled.", | ||
"* '''Trade agreement''' columns generate actual trade sign-offs only in the Temple Bank section; War League and Hatch Ministry rows display '''-''' in that column.", | "* '''Trade agreement''' columns generate actual trade sign-offs only in the Temple Bank section; War League and Hatch Ministry rows display '''-''' in that column.", | ||
"* '''Compliance observed''' columns show minor positive local behavior below the level of a formal trade agreement.", | |||
"* '''Compliance observed''' columns show minor positive local behavior below the level of a formal trade agreement | |||
"* '''Rebellion alert''' columns show rare row-level contained unrest events such as forged proclamations, broker-cell activity, route barricades, false orders, or Dispossessed-adjacent courier behavior.", | "* '''Rebellion alert''' columns show rare row-level contained unrest events such as forged proclamations, broker-cell activity, route barricades, false orders, or Dispossessed-adjacent courier behavior.", | ||
"* Use '''tradechance=100''', '''compliancechance=100''', and '''rebellionchance=100''' for testing | "* Use '''tradechance=100''', '''compliancechance=100''', and '''rebellionchance=100''' for testing.", | ||
"* Use '''forcemylecia=no''' only if the wrapper should temporarily show the original source-module | "* Use '''forcemylecia=no''' only if the wrapper should temporarily show the original source-module City/Node values." | ||
}, "\n") | }, "\n") | ||
Revision as of 20:13, 5 June 2026
Documentation for this module may be created at Module:SouthernStraitCorridorOps/doc
-- Module:SouthernStraitCorridorOps
local p = {}
--------------------------------------------------------------------------------
-- Helpers
--------------------------------------------------------------------------------
local function yn(value, default)
local v = tostring(value or ""):lower()
if v == "" then return default end
if v == "no" or v == "n" or v == "0" or v == "false" then return false end
if v == "yes" or v == "y" or v == "1" or v == "true" then return true end
return default
end
local function makeFrame(args)
return { args = args or {} }
end
local function safeRequire(moduleName)
local ok, mod = pcall(require, moduleName)
if ok and mod then
return mod, nil
end
return nil, "Could not load " .. tostring(moduleName)
end
local function safeCall(moduleName, funcName, args)
local mod, err = safeRequire(moduleName)
if not mod then
return false, "''" .. err .. ".''"
end
if type(mod[funcName]) ~= "function" then
return false, "''" .. tostring(moduleName) .. " has no public function named " .. tostring(funcName) .. ".''"
end
local ok, result = pcall(function()
return mod[funcName](makeFrame(args))
end)
if not ok then
return false, "''Error while invoking " .. tostring(moduleName) .. "." .. tostring(funcName) .. ": " .. tostring(result) .. "''"
end
return true, tostring(result or "")
end
local function doyToMonthDom(doy)
doy = tonumber(doy) or 1
if doy < 1 then doy = 1 end
if doy > 183 then doy = 183 end
if doy <= 61 then
return 1, doy
elseif doy <= 122 then
return 2, doy - 61
else
return 3, doy - 122
end
end
local function dateLabel(args)
local y = tonumber(args.year or args.psscyear)
local d = tonumber(args.day or args.dayofyear)
if y and d then
local m, dom = doyToMonthDom(d)
return string.format("%d/%d/%d PSSC", dom, m, y)
end
return "current PSSC date"
end
local function section(title, body, collapsible, collapsed)
local out = {}
local safeTitle = tostring(title or "Operations")
local content = tostring(body or "''No output.''")
if not collapsible then
table.insert(out, "===" .. safeTitle .. "===")
table.insert(out, content)
return table.concat(out, "\n")
end
local collapseClass = collapsed and "mw-collapsible mw-collapsed" or "mw-collapsible"
table.insert(out, '<div class="' .. collapseClass .. '" style="border:1px solid #a2a9b1; margin:0.6em 0; background:#f8f9fa;">')
table.insert(out, '<div style="font-weight:bold; padding:0.35em 0.6em; background:#eaecf0;">' .. safeTitle .. '</div>')
table.insert(out, '<div style="padding:0.5em 0.6em;">')
table.insert(out, content)
table.insert(out, '</div>')
table.insert(out, '</div>')
return table.concat(out, "\n")
end
local function hr()
return "\n----\n"
end
local function hashString(s)
s = tostring(s or "")
local h = 0
for i = 1, #s do
h = (h * 31 + string.byte(s, i)) % 1000003
end
return h
end
local function pick(list, seed, salt)
if not list or #list == 0 then return nil end
local idx = (hashString(tostring(seed or "") .. ":" .. tostring(salt or "")) % #list) + 1
return list[idx]
end
local function pctFromSeed(seed, salt)
return hashString(tostring(seed or "") .. ":" .. tostring(salt or "")) % 100
end
--------------------------------------------------------------------------------
-- Extended Southern Strait / Eastern Mykonos / Snowholme locality list
--------------------------------------------------------------------------------
local southernTradeCities = {
{
code = "MIR",
city = "[[Mirovia]]",
region = "[[Eastern Mykonos]] Desert",
category = "Agriculture",
company = "[[Mirovian Cistern Growers' Syndicate]]",
resource = "cistern-grown melons, dryland cucumbers, preserved desert fruit, seed oil, packed caravan rations, and water-accounted produce consignments",
lineage = "Desert provisioning economy influenced by [[Jogi]]'s food-market role and [[Mylecia]]'s caravan-facing trade networks; adapted to cistern agriculture, water claims, and dry-route supply rather than beer, grain, or ritual crops"
},
{
code = "SAR",
city = "[[Saruq Thyrsa]]",
region = "Southern Strait coast",
category = "Agriculture",
company = "[[Saruq Thyrsa Reedcutters' Guild]]",
resource = "coastal reed, cane fiber, packing straw, ferry mats, cargo-padding material, rope backing, warehouse thatch, and crate-lining bundles",
lineage = "Harbor-materials economy influenced by [[Blore Heath]]'s shipping brokerage and [[Jogi]]'s quay-market supply system; focused on practical cargo handling, ferry shelters, and packing materials rather than piracy or cult goods"
},
{
code = "AYS",
city = "[[Ayllu Nysara]]",
region = "Eastern foothills of the [[Snowholme Range]]",
category = "Agriculture",
company = "[[Nysaran Terrace Farmers' Ayllu]]",
resource = "terrace beans, cold-weather tubers, sheep wool, highland fodder, pack-animal feed, preserved root stores, and mule-train provisions",
lineage = "Foothill supply economy influenced only indirectly by the Maritime Markets; tied more closely to Snowholme terrace farming, upland food storage, wool traffic, and pass-road provisioning"
},
{
code = "THQ",
city = "[[Thal Qencha]]",
region = "Eastern foothills of the [[Snowholme Range]]",
category = "Agriculture",
company = "[[Thal Qencha Springkeepers' Cooperative]]",
resource = "spring-fed watercress, trout ponds, smoked fish, cold-storage greens, inspected mountain food shipments, packed ice-box produce, and road-market fish consignments",
lineage = "Foothill water-and-food economy tied to Snowholme springs and pass roads; comparable to older Maritime Markets food distribution only in its role as a regulated supplier to travelers, markets, and route offices"
},
{
code = "RUK",
city = "[[Rumi-Kefaya]]",
region = "[[Eastern Mykonos]] Desert",
category = "Mining / Manufacturing / Energy",
company = "[[Rumi-Kefaya Stone and Lime Works]]",
resource = "basalt, limestone, paving stone, railway ballast, drainage tile, lime mortar, culvert blocks, harbor repair stone, quay coping, and storm-route masonry",
lineage = "Infrastructure economy influenced by [[Mylecia]]'s coal-and-works tradition and Southern Strait coastal preservation activity; focused on roads, quays, culverts, retaining walls, and storm-damage repair rather than extractive wealth"
},
{
code = "HAY",
city = "[[Hayr Chaskal]]",
region = "Southern Strait coast",
category = "Mining / Manufacturing / Energy",
company = "[[Hayr Chaskal Glass and Signal Works]]",
resource = "beacon glass, telegraph insulators, lighthouse lenses, lamp glass, storm panes, signal mirrors, buoy lenses, and weatherproof signal housings",
lineage = "Signal-and-navigation economy influenced by [[Blore Heath]]'s shipping control and [[Mylecia]]'s coastal works; supports ferry warnings, lighthouse maintenance, telegraph lines, and Hatch Ministry watch posts without copying older pirate or brokerage firms"
},
{
code = "QSE",
city = "[[Qhapaq Selen]]",
region = "Eastern foothills of the [[Snowholme Range]]",
category = "Services",
company = "[[Qhapaq Selen Route Registry Office]]",
resource = "night-road registry, caravan lodging, pass permits, route insurance, guide licensing, cargo-route papers, missing-traveler filings, and bonded transit records",
lineage = "Foothill route-service economy influenced by [[Mylecia]]'s caravan-facing commerce and [[Jogi]]'s intelligence-and-paperwork traditions; adapted to Snowholme pass roads, guide houses, lodging records, and insurance claims"
}
}
--------------------------------------------------------------------------------
-- Row-level locality focus helper
--------------------------------------------------------------------------------
local function rowLocalityData(args, channel, rowNumber)
if not southernTradeCities or #southernTradeCities == 0 then
return nil
end
local year = tonumber(args.year or args.psscyear) or 0
local day = tonumber(args.day or args.dayofyear) or 0
local seed = tostring(args.seed or "") .. ":SS-ROW-LOCALITY:" .. tostring(channel or "") .. ":" .. tostring(year) .. ":" .. tostring(day)
local offset = hashString(seed) % #southernTradeCities
local idx = ((tonumber(rowNumber) or 1) - 1 + offset) % #southernTradeCities + 1
return southernTradeCities[idx]
end
local function rowLocalityText(loc)
if not loc then
return "Southern Strait corridor"
end
return loc.city .. "<br><small>" .. loc.region .. "</small>"
end
--------------------------------------------------------------------------------
-- Forced City/Node display
--------------------------------------------------------------------------------
local forcedCityNodeText = "[[File:MyleciaFlabB.jpeg|20px]] [[Mylecia]]"
local function cleanHeaderLabel(line)
local s = tostring(line or "")
s = s:gsub("^%s*!+%s*", "")
if s:find("|", 1, true) then
local parts = mw.text.split(s, "|", true)
s = parts[#parts] or s
end
s = s:gsub("<.->", "")
s = s:gsub("%[%[[^%]|]+|([^%]]+)%]%]", "%1")
s = s:gsub("%[%[([^%]]+)%]%]", "%1")
s = s:gsub(" ", " ")
s = s:gsub("%s*/%s*", "/")
s = s:gsub("^%s+", ""):gsub("%s+$", "")
return s:lower()
end
local cityNodeHeaderLabels = {
["city"] = true,
["node"] = true,
["city/node"] = true,
["city / node"] = true,
["city or node"] = true,
["home port"] = true,
["homeport"] = true,
["home city"] = true,
["base city"] = true,
["staging city"] = true
}
local function forceMyleciaCityNodeColumn(body, args)
body = tostring(body or "")
if yn(args.forcemylecia or args.forceMylecia or args.myleciacity or args.myleciaCity, true) == false then
return body
end
if not body:find("{|", 1, true) then
return body
end
local forcedText = args.forcecitytext or args.forceCityText or forcedCityNodeText
local lines = mw.text.split(body, "\n", true)
local out = {}
local inTable = false
local cityCol = nil
local col = 0
for _, line in ipairs(lines) do
if line:match("^%s*{|") then
inTable = true
cityCol = nil
col = 0
table.insert(out, line)
elseif inTable and line:match("^%s*!") then
local raw = line:gsub("^%s*!+%s*", "")
local headers = mw.text.split(raw, "!!", true)
if #headers > 1 then
for _, h in ipairs(headers) do
col = col + 1
if cityNodeHeaderLabels[cleanHeaderLabel("! " .. h)] then
cityCol = col
end
end
else
col = col + 1
if cityNodeHeaderLabels[cleanHeaderLabel(line)] then
cityCol = col
end
end
table.insert(out, line)
elseif inTable and line:match("^%s*|%-") then
col = 0
table.insert(out, line)
elseif inTable and cityCol and line:match("^%s*|[^}%-%+]") then
local raw = line:gsub("^%s*|%s*", "")
local cells = mw.text.split(raw, "||", true)
if #cells > 1 then
for i = 1, #cells do
if i == cityCol then
cells[i] = " " .. forcedText .. " "
end
end
table.insert(out, "| " .. table.concat(cells, " || "))
else
col = col + 1
if col == cityCol then
table.insert(out, "| " .. forcedText)
else
table.insert(out, line)
end
end
elseif inTable and line:match("^%s*|}") then
inTable = false
table.insert(out, line)
else
table.insert(out, line)
end
end
return table.concat(out, "\n")
end
--------------------------------------------------------------------------------
-- Cult sign-off list for Temple Bank trade agreements
--------------------------------------------------------------------------------
local tradeSigningCults = {
{ code = "UMB", label = "[[Noctis (Reformed Stripping Path)#Order of the Umbral Oracle|Order of the Umbral Oracle]]" },
{ code = "GGS", label = "[[Chrysos (Reformed Stripping Path)#Guild of Golden Shadows|Guild of Golden Shadows]]" },
{ code = "AZS", label = "[[Thalassa (Reformed Stripping Path)#Azure Sentinel Sect|Azure Sentinel Sect]]" },
{ code = "HAR", label = "[[Micras (Reformed Stripping Path)#Harmony Sanctum|Harmony Sanctum]]" },
{ code = "VIT", label = "[[Plateau (Reformed Stripping Path)#Sanctum Vitalis|Sanctum Vitalis]]" },
{ code = "AUR", label = "[[Eos (Reformed Stripping Path)#Order of Aurora Mystica|Order of Aurora Mystica]]" },
{ code = "DEL", label = "[[Sisera (Reformed Stripping Path)#Sanctum Delphica|Sanctum Delphica]]" },
{ code = "CAT", label = "[[Cato (Reformed Stripping Path)#Rex Catonis|Rex Catonis]]" },
{ code = "CON", label = "[[Lukedu (Reformed Stripping Path)#Accord Concordia|Accord Concordia]]" },
{ code = "STY", label = "[[Styx (Reformed Stripping Path)#Mystery of the Stygian Veil|Mystery of the Stygian Veil]]" }
}
local tradeOutcomes = {
"Cleared for General Port ledger review.",
"Provisional admission recommended.",
"Conditional trade window approved.",
"Company charter accepted for corridor listing.",
"Trade agreement filed through ordinary registry channels."
}
local function tradeAgreementCell(args, channel, rowNumber, loc)
if tostring(channel or "") ~= "missionary" then
return "-"
end
if not loc then
return "-"
end
local chance = tonumber(args.tradechance or args.tradeChance) or 3
if chance < 0 then chance = 0 end
if chance > 100 then chance = 100 end
local year = tonumber(args.year or args.psscyear) or 0
local day = tonumber(args.day or args.dayofyear) or 0
local seed = tostring(args.seed or "") ..
":SS-TEMPLE-TRADE-COL:" .. tostring(rowNumber or 0) ..
":" .. tostring(loc.code or "GEN") ..
":" .. tostring(year) ..
":" .. tostring(day)
local roll = pctFromSeed(seed, "roll") + 1
if roll > chance then
if yn(args.showTradeNoEvent or args.showtradenoevent, false) then
return "No sign-off"
end
return "-"
end
local cult = pick(tradeSigningCults, seed, "cult")
local outcome = pick(tradeOutcomes, seed, "outcome")
if not cult then
return "-"
end
local seq = (pctFromSeed(seed, "seq") % 20) + 1
local tradeId
if year > 0 and day > 0 then
tradeId = string.format("GP-SS-%03d.%03d-%s-%02d", year, day, loc.code or "GEN", seq)
else
tradeId = string.format("GP-SS-%s-%02d", loc.code or "GEN", seq)
end
local signCode = string.format("%s-TA%02d", cult.code or "CUL", seq)
return "'''Trade sign-off'''<br>" ..
"<small>" .. tradeId .. "</small><br>" ..
(cult.label or "Cult sign-off recorded") .. "<br>" ..
"<small>Code: '''" .. signCode .. "'''<br>" .. outcome .. "</small>"
end
--------------------------------------------------------------------------------
-- Rebellion / corridor unrest row column
--------------------------------------------------------------------------------
local rebellionProfiles = {
{ label = "Confederacy-adjacent courier screen", description = "False-route couriers testing inspection gaps." },
{ label = "Former Maritime Markets broker faction", description = "Old broker-house retainers resisting General Port paperwork." },
{ label = "Anti-registry dock syndicate", description = "Warehouse intermediaries opposing voucher and ledger discipline." },
{ label = "False-proclamation circle", description = "Pamphlet readers circulating forged civic orders." },
{ label = "Route-barricade committee", description = "Agitators obstructing lawful inspection lanes." },
{ label = "Dispossessed-sympathetic quay cell", description = "Waterfront cell copying Confederacy-style courier habits." }
}
local rebellionTriggers = {
"Forged levy notice.",
"Masked speakers redirected traffic.",
"Missing seals and altered registry slips.",
"Crowd pressure over bonded cargo.",
"Tampered signal schedule.",
"Conflicting route orders.",
"Coordinated paper strike.",
"Anti-Bassaridian chant movement.",
"Repeated courier handoffs.",
"Matched false manifests."
}
local rebellionActions = {
"CKD cordon and evidence hold.",
"Temple rumor-control desk opened.",
"Ledgers and copied seals sealed.",
"Privateer stand-off posture.",
"Pass papers reissued.",
"Lawful cargo separated.",
"Missionary mediation opened.",
"Corrected signal notice issued.",
"Temporary General Port audit lane opened.",
"Incident classified as contained unrest."
}
local rebellionRisks = {
"Low",
"Moderate",
"High",
"Contained",
"Escalating"
}
--------------------------------------------------------------------------------
-- Compliance observed / minor positive row column
--------------------------------------------------------------------------------
local complianceProfiles = {
{ label = "Inspection cooperation", description = "Local carriers complied with inspection sequencing without requiring escalation." },
{ label = "Ledger correction", description = "Route clerks corrected incomplete or inconsistent paperwork before formal penalty." },
{ label = "Crowd dispersal", description = "Local stewards dispersed a gathering before it became an obstruction." },
{ label = "Guide assistance", description = "Licensed guides helped separate lawful traffic from suspect courier movement." },
{ label = "Cargo separation", description = "Bonded cargo was voluntarily separated for review without disrupting ordinary passage." },
{ label = "Rumor-control compliance", description = "Temple notices were accepted locally and hostile rumors lost momentum." },
{ label = "Privateer signal compliance", description = "Vessels accepted stand-off or hold-and-release signals without resistance." },
{ label = "Registry cooperation", description = "Local registrars preserved names, seals, and witness records for later review." }
}
local complianceOutcomes = {
"Minor positive compliance noted.",
"Recorded as a stabilizing local action.",
"Accepted as offsetting evidence against wider noncompliance.",
"Filed as a documented good-faith response.",
"Preserved for future compliance comparison.",
"Marked as below trade-agreement threshold but operationally helpful."
}
local function complianceObservedCell(args, channel, rowNumber, loc)
if not loc then
return "-"
end
local chance = tonumber(args.compliancechance or args.complianceChance or args.observedchance or args.observedChance) or 8
if chance < 0 then chance = 0 end
if chance > 100 then chance = 100 end
local year = tonumber(args.year or args.psscyear) or 0
local day = tonumber(args.day or args.dayofyear) or 0
local seed = tostring(args.seed or "") ..
":SS-COMPLIANCE-COL:" .. tostring(channel or "") ..
":" .. tostring(rowNumber or 0) ..
":" .. tostring(loc.code or "GEN") ..
":" .. tostring(year) ..
":" .. tostring(day)
local roll = pctFromSeed(seed, "roll") + 1
if roll > chance then
if yn(args.showComplianceNoEvent or args.showcompliancenoevent, false) then
return "No observation"
end
return "-"
end
local profile = pick(complianceProfiles, seed, "profile")
local outcome = pick(complianceOutcomes, seed, "outcome")
if not profile then
return "-"
end
local seq = (pctFromSeed(seed, "seq") % 20) + 1
local eventId
if year > 0 and day > 0 then
eventId = string.format("SS-CO-%03d.%03d-%s-%02d", year, day, loc.code or "GEN", seq)
else
eventId = string.format("SS-CO-%s-%02d", loc.code or "GEN", seq)
end
return "'''Compliance observed'''<br>" ..
"<small>" .. eventId .. "</small><br>" ..
"'''Profile:''' " .. profile.label .. "<br>" ..
"<small>" .. profile.description .. "<br>" ..
"'''Outcome:''' " .. outcome .. "</small>"
end
local function rebellionAlertCell(args, channel, rowNumber, loc)
if not loc then
return "-"
end
local chance = tonumber(args.rebellionchance or args.rebellionChance or args.unrestchance or args.unrestChance) or 6
if chance < 0 then chance = 0 end
if chance > 100 then chance = 100 end
local year = tonumber(args.year or args.psscyear) or 0
local day = tonumber(args.day or args.dayofyear) or 0
local seed = tostring(args.seed or "") ..
":SS-REBELLION-COL:" .. tostring(channel or "") ..
":" .. tostring(rowNumber or 0) ..
":" .. tostring(loc.code or "GEN") ..
":" .. tostring(year) ..
":" .. tostring(day)
local roll = pctFromSeed(seed, "roll") + 1
if roll > chance then
if yn(args.showRebellionNoEvent or args.showrebellionnoevent or args.showUnrestNoEvent or args.showunrestnoevent, false) then
return "No unrest"
end
return "-"
end
local profile = pick(rebellionProfiles, seed, "profile")
local trigger = pick(rebellionTriggers, seed, "trigger")
local action = pick(rebellionActions, seed, "action")
local risk = pick(rebellionRisks, seed, "risk")
if not profile then
return "-"
end
local seq = (pctFromSeed(seed, "seq") % 20) + 1
local eventId
if year > 0 and day > 0 then
eventId = string.format("SS-RB-%03d.%03d-%s-%02d", year, day, loc.code or "GEN", seq)
else
eventId = string.format("SS-RB-%s-%02d", loc.code or "GEN", seq)
end
return "'''Unrest alert'''<br>" ..
"<small>" .. eventId .. "</small><br>" ..
"'''Profile:''' " .. profile.label .. "<br>" ..
"<small>" .. profile.description .. "<br>" ..
"'''Trigger:''' " .. trigger .. "<br>" ..
"'''Risk:''' " .. risk .. "<br>" ..
"'''Response:''' " .. action .. "</small>"
end
--------------------------------------------------------------------------------
-- Row-column insertion helper
--------------------------------------------------------------------------------
local function addCorridorColumns(body, args, channel)
body = tostring(body or "")
if not body:find("{|", 1, true) then
return body
end
local showLocalityColumn = yn(args.rowlocalities or args.localities or args.showlocalities, true)
local showTradeColumn = yn(args.tradecolumn or args.tradecolumns or args.showtradecolumn, true)
local showComplianceColumn = yn(args.compliancecolumn or args.compliancecolumns or args.observedcolumn or args.showcompliancecolumn, true)
local showRebellionColumn = yn(args.rebellioncolumn or args.rebellioncolumns or args.unrestcolumn or args.showrebellioncolumn, true)
if not showLocalityColumn and not showTradeColumn and not showComplianceColumn and not showRebellionColumn then
return body
end
local lines = mw.text.split(body, "\n", true)
local out = {}
local inTable = false
local headerInserted = false
local rowNumber = 0
for _, line in ipairs(lines) do
if line:match("^%s*{|") then
inTable = true
headerInserted = false
table.insert(out, line)
elseif inTable and not headerInserted and line:match("^%s*!") then
if showLocalityColumn then
table.insert(out, "! Locality focus")
end
if showTradeColumn then
table.insert(out, "! Trade agreement")
end
if showComplianceColumn then
table.insert(out, "! Compliance observed")
end
if showRebellionColumn then
table.insert(out, "! Rebellion alert")
end
headerInserted = true
table.insert(out, line)
elseif inTable and line:match("^%s*|%-") then
rowNumber = rowNumber + 1
local loc = rowLocalityData(args, channel, rowNumber)
table.insert(out, line)
if showLocalityColumn then
table.insert(out, "| " .. rowLocalityText(loc))
end
if showTradeColumn then
table.insert(out, "| " .. tradeAgreementCell(args, channel, rowNumber, loc))
end
if showComplianceColumn then
table.insert(out, "| " .. complianceObservedCell(args, channel, rowNumber, loc))
end
if showRebellionColumn then
table.insert(out, "| " .. rebellionAlertCell(args, channel, rowNumber, loc))
end
elseif inTable and line:match("^%s*|}") then
inTable = false
table.insert(out, line)
else
table.insert(out, line)
end
end
return table.concat(out, "\n")
end
--------------------------------------------------------------------------------
-- Main Southern Strait-only view
--------------------------------------------------------------------------------
function p.makeTable(frame)
local args = (frame and frame.args) or {}
local showIntro = yn(args.intro, false)
local showTitle = yn(args.showtitle or args.titleline, false)
local showLegend = yn(args.legend or args.showlegend, false)
local collapseSections = yn(args.collapsible, true)
local startCollapsed = yn(args.collapsed, true)
local includeWar = yn(args.war or args.warleague, true)
local includeHatch = yn(args.hatch or args.hatchministry, true)
local includeMission = yn(args.missionary or args.templebank, true)
local year = args.year or args.psscyear
local day = args.day or args.dayofyear
local title = args.title or "Southern Strait Corridor Daily Operations"
local label = dateLabel(args)
local out = {}
if showTitle then
table.insert(out, "==" .. title .. "==")
end
if showIntro then
table.insert(out, string.format(
"''Southern Strait-only operational display for %s, limited to Mylecian Southern Strait corridor entries.''",
label
))
table.insert(out, "")
table.insert(out, "This display combines Council of Kings deployments, Hatch Ministry privateer activity, Temple Bank missionary operations, row-level Southern Strait locality focus columns, Temple Bank-only trade-agreement sign-offs, row-level compliance observations, and row-level rebellion-type unrest alerts while displaying the operational City/Node as Mylecia.")
end
if includeWar then
local warArgs = {
year = year,
day = day,
rows = args.warrows or args.rows or "24",
scanrows = args.warscanrows or args.scanrows or "160",
showlegend = showLegend and "yes" or "no",
flagsize = args.flagsize or "20",
zone = "SouthernStrait",
division = "Council of Kings Division",
}
if args.seed and tostring(args.seed) ~= "" then
warArgs.seed = args.seed
end
local ok, body = safeCall("Module:WarLeagueDivisionOpsBVR", "makeTable", warArgs)
body = forceMyleciaCityNodeColumn(body, args)
body = addCorridorColumns(body, args, "war")
table.insert(out, hr())
table.insert(out, section("Council of Kings Southern Strait deployments", body, collapseSections, startCollapsed))
end
if includeHatch then
local hatchArgs = {
year = year,
day = day,
legend = showLegend and "yes" or "no",
zone = "SouthernStrait",
}
if args.captain and tostring(args.captain) ~= "" then
hatchArgs.captain = args.captain
end
local ok, body = safeCall("Module:HatchMinistryDailyOps", "dailyReport", hatchArgs)
body = forceMyleciaCityNodeColumn(body, args)
body = addCorridorColumns(body, args, "hatch")
table.insert(out, hr())
table.insert(out, section("Hatch Ministry Southern Strait privateer operations", body, collapseSections, startCollapsed))
end
if includeMission then
local missionaryArgs = {
year = year,
day = day,
region = "Haifan",
showlegend = showLegend and "yes" or "no",
includeThreat = args.includeThreat or args.includethreat or "no",
showEcon = args.showEcon or args.showecon or "no",
}
if args.missionarylimit and tostring(args.missionarylimit) ~= "" then
missionaryArgs.limit = args.missionarylimit
elseif args.limit and tostring(args.limit) ~= "" then
missionaryArgs.limit = args.limit
end
local ok, body = safeCall("Module:MissionaryDailyOpsBVR", "makeTable", missionaryArgs)
body = forceMyleciaCityNodeColumn(body, args)
body = addCorridorColumns(body, args, "missionary")
table.insert(out, hr())
table.insert(out, section("Temple Bank Southern Strait missionary operations", body, collapseSections, startCollapsed))
end
if showLegend then
local legendBody = table.concat({
"* '''Council of Kings Southern Strait deployments''' show only Council of Kings Division War League rows for the Southern Strait corridor.",
"* '''Hatch Ministry Southern Strait privateer operations''' show Southern Strait privateer rows.",
"* '''Temple Bank Southern Strait missionary operations''' show Haifan Bassaridian missionary rows, but the displayed City/Node is forced to Mylecia in this wrapper.",
"* '''City/Node display''' is forced to [[File:MyleciaFlabB.jpeg|20px]] [[Mylecia]] for War League, Hatch Ministry, and Temple Bank rows.",
"* '''Locality focus''' columns assign one extended Southern Strait, Eastern Mykonos Desert, or eastern Snowholme foothill settlement to each generated row when row-level localities are enabled.",
"* '''Trade agreement''' columns generate actual trade sign-offs only in the Temple Bank section; War League and Hatch Ministry rows display '''-''' in that column.",
"* '''Compliance observed''' columns show minor positive local behavior below the level of a formal trade agreement.",
"* '''Rebellion alert''' columns show rare row-level contained unrest events such as forged proclamations, broker-cell activity, route barricades, false orders, or Dispossessed-adjacent courier behavior.",
"* Use '''tradechance=100''', '''compliancechance=100''', and '''rebellionchance=100''' for testing.",
"* Use '''forcemylecia=no''' only if the wrapper should temporarily show the original source-module City/Node values."
}, "\n")
table.insert(out, hr())
table.insert(out, section("Combined legend", legendBody, collapseSections, startCollapsed))
end
return table.concat(out, "\n")
end
--------------------------------------------------------------------------------
-- Shortcut views
--------------------------------------------------------------------------------
function p.southernStrait(frame)
local args = (frame and frame.args) or {}
args.title = args.title or "Southern Strait Corridor Daily Operations"
return p.makeTable({ args = args })
end
function p.warOnly(frame)
local args = (frame and frame.args) or {}
args.hatch = "no"
args.missionary = "no"
args.war = "yes"
args.title = args.title or "War League Southern Strait Daily Operations"
return p.makeTable({ args = args })
end
function p.hatchOnly(frame)
local args = (frame and frame.args) or {}
args.war = "no"
args.missionary = "no"
args.hatch = "yes"
args.title = args.title or "Hatch Ministry Southern Strait Daily Operations"
return p.makeTable({ args = args })
end
function p.missionaryOnly(frame)
local args = (frame and frame.args) or {}
args.war = "no"
args.hatch = "no"
args.missionary = "yes"
args.title = args.title or "Temple Bank Southern Strait Missionary Daily Operations"
return p.makeTable({ args = args })
end
return p