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 15: Line 15:
-- Trade agreements generate ONLY under Temple Bank / missionary rows.
-- Trade agreements generate ONLY under Temple Bank / missionary rows.
-- War League and Hatch Ministry rows show "-" in the Trade agreement column.
-- War League and Hatch Ministry rows show "-" in the Trade agreement column.
--
-- Date behavior:
-- If year/day are omitted, this wrapper calculates the current PSSC date
-- and passes that same year/day to the source modules and to the added
-- Trade agreement / Rebellion alert columns. This keeps all generated
-- columns synchronized.
--
--
-- Economic design note:
-- Economic design note:
Line 37: Line 43:
local function makeFrame(args)
local function makeFrame(args)
return { args = args or {} }
return { args = args or {} }
end
local function getCurrentPSSCDate()
local startDate = os.time({ year = 1999, month = 8, day = 6, hour = 0 })
local secondsInDay = 86400
local daysPerYear = 183
local currentDate = os.time()
local totalDaysElapsed = math.floor((currentDate - startDate) / secondsInDay)
local psscYear = math.floor(totalDaysElapsed / daysPerYear)
local dayOfYear = (totalDaysElapsed % daysPerYear) + 1
if dayOfYear < 1 then dayOfYear = 1 end
if dayOfYear > 183 then dayOfYear = 183 end
return psscYear, dayOfYear
end
local function normalizeArgsDate(args)
local out = {}
for k, v in pairs(args or {}) do
out[k] = v
end
local y = tonumber(out.year or out.psscyear)
local d = tonumber(out.day or out.dayofyear)
if not y or not d then
y, d = getCurrentPSSCDate()
end
if d < 1 then d = 1 end
if d > 183 then d = 183 end
out.year = tostring(y)
out.day = tostring(d)
out.psscyear = tostring(y)
out.dayofyear = tostring(d)
return out
end
end


Line 91: Line 139:
end
end


return "current PSSC date"
local cy, cd = getCurrentPSSCDate()
local cm, cdom = doyToMonthDom(cd)
return string.format("%d/%d/%d PSSC", cdom, cm, cy)
end
end


Line 542: Line 592:


function p.makeTable(frame)
function p.makeTable(frame)
local args = (frame and frame.args) or {}
local args = normalizeArgsDate((frame and frame.args) or {})


local showIntro  = yn(args.intro, false)
local showIntro  = yn(args.intro, false)
Line 669: Line 719:
"* '''Trade sign-off''' is the key phrase indicating that a Temple Bank row generated a cult-backed trade agreement.",
"* '''Trade sign-off''' is the key phrase indicating that a Temple Bank row generated a cult-backed 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.",
"* If '''year''' and '''day''' are omitted, this wrapper calculates the current PSSC date and uses it consistently for the source rows and added columns.",
"* Use '''tradechance=100''' and '''rebellionchance=100''' for testing; return to low values such as '''tradechance=3''' and '''rebellionchance=6''' for ordinary use."
"* Use '''tradechance=100''' and '''rebellionchance=100''' for testing; return to low values such as '''tradechance=3''' and '''rebellionchance=6''' for ordinary use."
}, "\n")
}, "\n")

Revision as of 21:19, 4 June 2026

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


-- Module:SouthernStraitCorridorOps
-- Southern Strait-only daily operations wrapper.
-- Shows only Southern Strait / Haifan Bassaridian corridor entries from:
-- 1) WarLeagueDivisionOpsBVR
-- 2) HatchMinistryDailyOps
-- 3) MissionaryDailyOpsBVR
--
-- Adds row-level columns:
-- 4) Locality focus
-- 5) Trade agreement
-- 6) Rebellion alert
--
-- Trade-agreement behavior:
-- Trade agreements generate ONLY under Temple Bank / missionary rows.
-- War League and Hatch Ministry rows show "-" in the Trade agreement column.
--
-- Date behavior:
-- If year/day are omitted, this wrapper calculates the current PSSC date
-- and passes that same year/day to the source modules and to the added
-- Trade agreement / Rebellion alert columns. This keeps all generated
-- columns synchronized.
--
-- Economic design note:
-- Southern Strait and Eastern Mykonos localities are written as grounded
-- successor/satellite economies influenced by the historic port economies of
-- Blore Heath, Jogi, and Mylecia, without copying those ports directly.

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 getCurrentPSSCDate()
	local startDate = os.time({ year = 1999, month = 8, day = 6, hour = 0 })
	local secondsInDay = 86400
	local daysPerYear = 183

	local currentDate = os.time()
	local totalDaysElapsed = math.floor((currentDate - startDate) / secondsInDay)

	local psscYear = math.floor(totalDaysElapsed / daysPerYear)
	local dayOfYear = (totalDaysElapsed % daysPerYear) + 1

	if dayOfYear < 1 then dayOfYear = 1 end
	if dayOfYear > 183 then dayOfYear = 183 end

	return psscYear, dayOfYear
end

local function normalizeArgsDate(args)
	local out = {}

	for k, v in pairs(args or {}) do
		out[k] = v
	end

	local y = tonumber(out.year or out.psscyear)
	local d = tonumber(out.day or out.dayofyear)

	if not y or not d then
		y, d = getCurrentPSSCDate()
	end

	if d < 1 then d = 1 end
	if d > 183 then d = 183 end

	out.year = tostring(y)
	out.day = tostring(d)
	out.psscyear = tostring(y)
	out.dayofyear = tostring(d)

	return out
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

	local cy, cd = getCurrentPSSCDate()
	local cm, cdom = doyToMonthDom(cd)
	return string.format("%d/%d/%d PSSC", cdom, cm, cy)
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

--------------------------------------------------------------------------------
-- 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)
	-- Trade agreements belong only under Temple Bank / missionary rows.
	-- War League and Hatch Ministry rows keep the column but show a dash.
	if tostring(channel or "") ~= "missionary" then
		return "-"
	end

	if not loc then
		return "-"
	end

	-- Low default likelihood. Use tradechance=100 for testing.
	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"
}

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 showRebellionColumn = yn(args.rebellioncolumn or args.rebellioncolumns or args.unrestcolumn or args.showrebellioncolumn, true)

	if not showLocalityColumn and not showTradeColumn 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 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 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 = normalizeArgsDate((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 Haifan Bassaridian / former Maritime Markets 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, and row-level rebellion-type unrest alerts while excluding unrelated regional rows.")
	end

	----------------------------------------------------------------------------
	-- War League: Southern Strait-only
	----------------------------------------------------------------------------

	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",

			-- Southern Strait Corridor wrapper shows only Council of Kings
			-- Division rows for War League deployments.
			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 = addCorridorColumns(body, args, "war")

		table.insert(out, hr())
		table.insert(out, section("Council of Kings Southern Strait deployments", body, collapseSections, startCollapsed))
	end

	----------------------------------------------------------------------------
	-- Hatch Ministry: Southern Strait-only
	----------------------------------------------------------------------------

	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 = addCorridorColumns(body, args, "hatch")

		table.insert(out, hr())
		table.insert(out, section("Hatch Ministry Southern Strait privateer operations", body, collapseSections, startCollapsed))
	end

	----------------------------------------------------------------------------
	-- Temple Bank / Missionary Operations: Haifan only
	----------------------------------------------------------------------------

	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 = addCorridorColumns(body, args, "missionary")

		table.insert(out, hr())
		table.insert(out, section("Temple Bank Southern Strait missionary operations", body, collapseSections, startCollapsed))
	end

	----------------------------------------------------------------------------
	-- Combined legend
	----------------------------------------------------------------------------

	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 Haifan home-port privateers only.",
			"* '''Temple Bank Southern Strait missionary operations''' show Haifan Bassaridian missionary rows only.",
			"* '''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 sign-off''' is the key phrase indicating that a Temple Bank row generated a cult-backed 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.",
			"* If '''year''' and '''day''' are omitted, this wrapper calculates the current PSSC date and uses it consistently for the source rows and added columns.",
			"* Use '''tradechance=100''' and '''rebellionchance=100''' for testing; return to low values such as '''tradechance=3''' and '''rebellionchance=6''' for ordinary use."
		}, "\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