Module:WarLeagueDivisionOpsBVR: Difference between revisions
From MicrasWiki
Jump to navigationJump to search
NewZimiaGov (talk | contribs) Created page with "-- Module:WarLeagueDivisionOpsBVR -- Daily, in-world War League divisional operations log. -- Integrates Module:BassaridianDailyEvents by parsing today’s city-event/urgency table output. :contentReference[oaicite:2]{index=2} local p = {} -------------------------------------------------------------------------------- -- (A) PSSC DATE (same logic used by Module:BassaridianDailyEvents) :contentReference[oaicite:3]{index=3} ----------------------------------------------..." |
NewZimiaGov (talk | contribs) No edit summary |
||
| Line 1: | Line 1: | ||
-- Module:WarLeagueDivisionOpsBVR | -- Module:WarLeagueDivisionOpsBVR | ||
-- Daily, in-world War League divisional operations log. | -- Daily, in-world War League divisional operations log. | ||
-- Integrates Module:BassaridianDailyEvents by parsing today’s city-event/urgency table output. | -- Integrates Module:BassaridianDailyEvents by parsing today’s city-event/urgency table output. | ||
local p = {} | local p = {} | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- (A) PSSC DATE (same logic used by Module:BassaridianDailyEvents) | -- (A) PSSC DATE (same logic used by Module:BassaridianDailyEvents) | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function getPSSCDate() | local function getPSSCDate() | ||
| Line 63: | Line 63: | ||
local anchor = anchorOverride or cityName | local anchor = anchorOverride or cityName | ||
return string.format("[[List_of_cities_in_Bassaridia_Vaeringheim#%s|%s]]", anchor, cityName) | return string.format("[[List_of_cities_in_Bassaridia_Vaeringheim#%s|%s]]", anchor, cityName) | ||
end | end | ||
local function opId(di, divCode, idx) | local function opId(di, divCode, idx) | ||
return string.format("WL-%03d.%03d-%s-%02d", di.year, di.dayOfYear, divCode, idx) | return string.format("WL-%03d.%03d-%s-%02d", di.year, di.dayOfYear, divCode, idx) | ||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- (C) INTEGRATE DAILY EVENTS (parse Module:BassaridianDailyEvents.makeTable()) | -- (C) INTEGRATE DAILY EVENTS (parse Module:BassaridianDailyEvents.makeTable()) | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function buildEventMapForToday() | local function buildEventMapForToday() | ||
| Line 91: | Line 78: | ||
end | end | ||
local tbl = daily.makeTable() -- returns wikitable text with rows: "| date || city || event || urgency" | |||
local tbl = daily.makeTable() | |||
local eventMap = {} | local eventMap = {} | ||
for line in tostring(tbl):gmatch("[^\n]+") do | for line in tostring(tbl):gmatch("[^\n]+") do | ||
if line:match("^|%s") and not line:match("^|%-%s*$") then | if line:match("^|%s") and not line:match("^|%-%s*$") then | ||
local parts = mw.text.split(line:gsub("^|%s*", ""), "%s*||%s*") | local parts = mw.text.split(line:gsub("^|%s*", ""), "%s*||%s*") | ||
if #parts >= 4 then | if #parts >= 4 then | ||
local city = parts[2] | local city = parts[2] | ||
local ev = parts[3] | local ev = parts[3] | ||
local urg = parts[4] | local urg = parts[4] | ||
if city and city ~= "" and not eventMap[city] then | if city and city ~= "" and not eventMap[city] then | ||
eventMap[city] = { | eventMap[city] = { desc = ev, urgency = urg } | ||
end | end | ||
end | end | ||
| Line 117: | Line 99: | ||
local function urgencyScore(urg) | local function urgencyScore(urg) | ||
if urg == "CRITICAL" then return 4 end | if urg == "CRITICAL" then return 4 end | ||
if urg == "HIGH" then return 3 end | if urg == "HIGH" then return 3 end | ||
| Line 150: | Line 131: | ||
local function pickEchelonForUrgency(echelons, uScore) | local function pickEchelonForUrgency(echelons, uScore) | ||
local tuned = {} | local tuned = {} | ||
for _, e in ipairs(echelons) do | for _, e in ipairs(echelons) do | ||
local w = e.weight or 1 | local w = e.weight or 1 | ||
if uScore >= 3 then | if uScore >= 3 then | ||
if e.name:match("Cohortes") or e.name:match("Ordu %(") or e.name:match("Moira") then | if e.name:match("Cohortes") or e.name:match("Ordu %(") or e.name:match("Moira") then | ||
| Line 172: | Line 151: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- (E) DIVISIONS + CITY POOLS | -- (E) DIVISIONS + CITY POOLS | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local DIVISIONS = { | local DIVISIONS = { | ||
{ name="Vaeringheim Division", | { name="Vaeringheim Division", link="[[Vaeringheim Division]]", code="VAE", weight=5, | ||
cities={"Vaeringheim","Luminaria","Serena","Symphonara","Aurelia","Somniumpolis","Nexa","Lunalis Sancta","Sylvapolis","Delphica","Catonis Atrium","Ferrum Citadel"} }, | cities={"Vaeringheim","Luminaria","Serena","Symphonara","Aurelia","Somniumpolis","Nexa","Lunalis Sancta","Sylvapolis","Delphica","Catonis Atrium","Ferrum Citadel"} }, | ||
{ name="Alpazkigz Division", | { name="Alpazkigz Division", link="[[Alpazkigz Division]]", code="ALP", weight=4, | ||
cities={"The Alpazkigz","Pyralis","Acheron","Erythros","Aureum","Koinonía","Saluria","Aetherium","Fanghorn","Eikbu","Galvø","Sårensby","Slevik","Sjøsborg","Storesund","Notranskja","Kaledonija"} }, | cities={"The Alpazkigz","Pyralis","Acheron","Erythros","Aureum","Koinonía","Saluria","Aetherium","Fanghorn","Eikbu","Galvø","Sårensby","Slevik","Sjøsborg","Storesund","Notranskja","Kaledonija"} }, | ||
{ name="Odiferia Division", | { name="Odiferia Division", link="[[Odiferia Division]]", code="ODF", weight=3, | ||
cities={"Odiferia","Somniumpolis","Aetherium","Ferrum Citadel","Aurelia","Symphonara"} }, | cities={"Odiferia","Somniumpolis","Aetherium","Ferrum Citadel","Aurelia","Symphonara"} }, | ||
{ name="Hatch Ministry Division", | { name="Hatch Ministry Division", link="[[Hatch Ministry Division]]", code="HAT", weight=3, | ||
cities={"Lewisburg","Lykopolis","Jogi","Keybir-Aviv","Tel-Amin","Diamandis","Thermosalem","Akróstadium","Sufriya"} }, | cities={"Lewisburg","Lykopolis","Jogi","Keybir-Aviv","Tel-Amin","Diamandis","Thermosalem","Akróstadium","Sufriya"} }, | ||
{ name="New South Jangsong Division",link="[[New South Jangsong Division]]",code="NSJ", weight=3, | { name="New South Jangsong Division", link="[[New South Jangsong Division]]", code="NSJ", weight=3, | ||
cities={"Skýrophos","Bjornopolis","Aegirheim","Norsolyra","Thorsalon","Pelagia","Myrene","Thyrea","Ephyra","Halicarn"} }, | cities={"Skýrophos","Bjornopolis","Aegirheim","Norsolyra","Thorsalon","Pelagia","Myrene","Thyrea","Ephyra","Halicarn"} }, | ||
{ name="Haifan Bassaridia Division", link="[[Haifan Bassaridia Division]]", code="HFA", weight=3, | { name="Haifan Bassaridia Division", link="[[Haifan Bassaridia Division]]", code="HFA", weight=3, | ||
cities={"Keybir-Aviv","Tel-Amin","Diamandis","Jogi","Lewisburg","Thermosalem","Akróstadium","Sufriya","Lykopolis"} }, | cities={"Keybir-Aviv","Tel-Amin","Diamandis","Jogi","Lewisburg","Thermosalem","Akróstadium","Sufriya","Lykopolis"} }, | ||
{ name="Bassaridian Normark Division",link="[[Bassaridian Normark Division]]",code="BNO",weight=2, | { name="Bassaridian Normark Division",link="[[Bassaridian Normark Division]]",code="BNO",weight=2, | ||
cities={"Ardclach","Riddersborg"} }, | cities={"Ardclach","Riddersborg"} }, | ||
{ name="Ouriana Division", | { name="Ouriana Division", link="[[Ouriana Division]]", code="OUR", weight=2, | ||
cities={"Bashkim","Ourid","Tonar"} }, | cities={"Bashkim","Ourid","Tonar"} }, | ||
{ name="Council of Kings Division", | { name="Council of Kings Division", link="[[Council of Kings Division]]", code="CKD", weight=1, | ||
cities={"Vaeringheim","Bashkim","Ourid","Tonar"} }, | cities={"Vaeringheim","Bashkim","Ourid","Tonar"} }, | ||
} | } | ||
| Line 235: | Line 195: | ||
local OP_AIR = { | local OP_AIR = { | ||
{ name="ISR overwatch / route scanning | { name="ISR overwatch / route scanning", weight=4 }, | ||
{ name="rapid extraction / medevac lift", weight=2 }, | { name="rapid extraction / medevac lift", weight=2 }, | ||
{ name="strike deterrence package (show of force)", weight=1 }, | { name="strike deterrence package (show of force)", weight=1 }, | ||
| Line 241: | Line 201: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- (G) | -- (G) OVERSIGHT + COMPLIANCE | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local OVERSIGHT = { | local OVERSIGHT = { | ||
{ label="Senate of Elders liaison", weight=3 }, | { label="Senate of Elders liaison", weight=3 }, | ||
| Line 263: | Line 212: | ||
local COMPLIANCE = { | local COMPLIANCE = { | ||
{ label=" | { label="Deployment verified; corridor routing approved; ledger notes filed same-day", weight=6 }, | ||
{ label=" | { label="Minor variance corrected; revised routing issued with escort buffer", weight=2 }, | ||
{ label=" | { label="Weather delay; holding pattern under local command until visibility improves", weight=1 }, | ||
{ label="Mission deferred (no-go); export-control check triggered by false manifest", | { label="Mission deferred (no-go); export-control check triggered by false manifest", weight=1 }, | ||
{ label="Export request refused (constitutional prohibition); | { label="Export request refused (constitutional prohibition); material retained under Council custody", weight=1 }, | ||
} | } | ||
| Line 276: | Line 225: | ||
return ' style="background-color:#fff3cd;"' | return ' style="background-color:#fff3cd;"' | ||
end | end | ||
if c:match("minor variance | if c:match("minor variance") then | ||
return ' style="background-color:#fff3cd;"' | return ' style="background-color:#fff3cd;"' | ||
end | end | ||
| Line 283: | Line 232: | ||
end | end | ||
if uScore >= 3 then | if uScore >= 3 then | ||
return ' style="background-color:#f8d7da;"' | return ' style="background-color:#f8d7da;"' | ||
end | end | ||
return ' style="background-color:#d4edda;"' | return ' style="background-color:#d4edda;"' | ||
| Line 289: | Line 238: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- (H) MAIN TABLE | -- (H) MAIN TABLE (NO systems/platforms column) | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
function p.makeTable(frame) | function p.makeTable(frame) | ||
local di = getPSSCDate() | local di = getPSSCDate() | ||
local args = frame and frame.args or {} | |||
local rows = tonumber(args.rows) or 36 | local rows = tonumber(args.rows) or 36 | ||
local showLegend = tostring(args.showlegend or "yes"):lower() ~= "no" | local showLegend = tostring(args.showlegend or "yes"):lower() ~= "no" | ||
-- Build daily events mapping first ( | -- Build daily events mapping first (DailyEvents may reseed RNG internally) | ||
local eventMap, hasEvents = buildEventMapForToday() | local eventMap, hasEvents = buildEventMapForToday() | ||
-- | -- Seed our RNG deterministically for the day (or override for testing) | ||
local seed = tonumber(args.seed) or (di.year * 1000 + di.dayOfYear) | local seed = tonumber(args.seed) or (di.year * 1000 + di.dayOfYear) | ||
math.randomseed(seed) | math.randomseed(seed) | ||
| Line 330: | Line 279: | ||
" !! Naval Forces" .. | " !! Naval Forces" .. | ||
" !! Aerial Forces" .. | " !! Aerial Forces" .. | ||
" !! Oversight" .. | " !! Oversight" .. | ||
" !! Compliance / Notes" | " !! Compliance / Notes" | ||
| Line 339: | Line 287: | ||
local cityName = pick(div.cities) | local cityName = pick(div.cities) | ||
local cityWikilink = cityLink(cityName, cityName) | local cityWikilink = cityLink(cityName, cityName) | ||
local ev = eventMap[cityName] | local ev = eventMap[cityName] | ||
local evDesc = ev and ev.desc or (hasEvents and "No reports" or "Events module unavailable") | local evDesc = ev and ev.desc or (hasEvents and "No reports" or "Events module unavailable") | ||
| Line 348: | Line 294: | ||
local uScore = urgencyScore(urg) | local uScore = urgencyScore(urg) | ||
-- Branch selection: Hatch/Haifan | -- Branch selection: Hatch/Haifan bias naval; higher urgency bias air support | ||
local branchWeighted = { | local branchWeighted = { | ||
{ name="ground", weight = 6 }, | { name="ground", weight = 6 }, | ||
| Line 356: | Line 302: | ||
local primary = pickWeighted(branchWeighted).name | local primary = pickWeighted(branchWeighted).name | ||
-- Choose | -- Choose operation pool | ||
local op | local op | ||
if primary == "naval" then | if primary == "naval" then | ||
| Line 366: | Line 312: | ||
end | end | ||
-- | -- Formations | ||
local ground = "" | local ground = "—" | ||
local naval = "" | local naval = "—" | ||
local air = "" | local air = "—" | ||
-- | -- Keep a ground footprint unless it's a pure air ISR day and low urgency | ||
if primary | if not (primary == "air" and uScore <= 1 and math.random() < 0.35) then | ||
local gE = pickEchelonForUrgency(GROUND_ECHELONS, uScore) | local gE = pickEchelonForUrgency(GROUND_ECHELONS, uScore) | ||
ground = string.format("%s (%s-%02d)", gE, div.code, math.random(1, 24)) | |||
end | end | ||
| Line 387: | Line 332: | ||
air = string.format("%s (%s-A%02d)", aE, div.code, math.random(1, 18)) | air = string.format("%s (%s-A%02d)", aE, div.code, math.random(1, 18)) | ||
end | end | ||
local obs = pickWeighted(OVERSIGHT).label | local obs = pickWeighted(OVERSIGHT).label | ||
local comp = pickWeighted(COMPLIANCE).label | local comp = pickWeighted(COMPLIANCE).label | ||
local notes = bulletList({ | local notes = bulletList({ | ||
"Op-ID: " .. opId(di, div.code, i), | "Op-ID: " .. opId(di, div.code, i), | ||
"Doctrine: corridor integrity and civic stability maintenance | "Doctrine: corridor integrity and civic stability maintenance" | ||
}) | }) | ||
| Line 407: | Line 345: | ||
table.insert(out, "|-" .. style) | table.insert(out, "|-" .. style) | ||
table.insert(out, string.format( | table.insert(out, string.format( | ||
" | "| %s || %s || %s || %s || %s || %s || %s || %s || %s || %s || %s", | ||
di.text, | di.text, | ||
div.link, | div.link, | ||
| Line 414: | Line 352: | ||
urg, | urg, | ||
op.name, | op.name, | ||
ground, | |||
naval, | |||
air, | |||
obs, | obs, | ||
bulletList({ comp, notes }) | bulletList({ comp, notes }) | ||
| Line 431: | Line 368: | ||
table.insert(out, "<span style='background:#fff3cd;padding:2px 6px;border:1px solid #ffeeba;'>Yellow</span> = variance/adjustment/weather ") | table.insert(out, "<span style='background:#fff3cd;padding:2px 6px;border:1px solid #ffeeba;'>Yellow</span> = variance/adjustment/weather ") | ||
table.insert(out, "<span style='background:#f8d7da;padding:2px 6px;border:1px solid #f5c6cb;'>Red</span> = HIGH/CRITICAL or export-control/deferment") | table.insert(out, "<span style='background:#f8d7da;padding:2px 6px;border:1px solid #f5c6cb;'>Red</span> = HIGH/CRITICAL or export-control/deferment") | ||
table.insert(out, "</div>") | table.insert(out, "</div>") | ||
end | end | ||
| Line 439: | Line 375: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- (I) OPTIONAL: show org ladders | -- (I) OPTIONAL: show org ladders (for documentation / page footer) | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
function p.showOrganization() | function p.showOrganization() | ||
Revision as of 20:05, 31 December 2025
Documentation for this module may be created at Module:WarLeagueDivisionOpsBVR/doc
-- Module:WarLeagueDivisionOpsBVR
-- Daily, in-world War League divisional operations log.
-- Integrates Module:BassaridianDailyEvents by parsing today’s city-event/urgency table output.
local p = {}
--------------------------------------------------------------------------------
-- (A) PSSC DATE (same logic used by Module:BassaridianDailyEvents)
--------------------------------------------------------------------------------
local function getPSSCDate()
local startDate = os.time({year = 1999, month = 8, day = 6})
local secondsInDay = 86400
local daysPerYear = 183
local now = os.time()
local totalDays = math.floor((now - startDate) / secondsInDay)
local yearFraction = totalDays / daysPerYear
local psscYear = math.floor(yearFraction)
local dayOfYear = math.floor((yearFraction - psscYear) * daysPerYear) + 1
local month, dayOfMonth
if dayOfYear <= 61 then
month = 1; dayOfMonth = dayOfYear
elseif dayOfYear <= 122 then
month = 2; dayOfMonth = dayOfYear - 61
else
month = 3; dayOfMonth = dayOfYear - 122
end
local dateString = string.format("%d/%d/%d PSSC", dayOfMonth, month, psscYear)
return {
text = dateString,
year = psscYear,
dayOfYear = dayOfYear,
month = month,
dom = dayOfMonth
}
end
--------------------------------------------------------------------------------
-- (B) HELPERS
--------------------------------------------------------------------------------
local function pick(t) return t[math.random(#t)] end
local function pickWeighted(list)
local total = 0
for _, it in ipairs(list) do total = total + (it.weight or 1) end
local r = math.random() * total
local acc = 0
for _, it in ipairs(list) do
acc = acc + (it.weight or 1)
if r <= acc then return it end
end
return list[#list]
end
local function bulletList(items)
if not items or #items == 0 then return "" end
return "<ul><li>" .. table.concat(items, "</li><li>") .. "</li></ul>"
end
local function cityLink(cityName, anchorOverride)
local anchor = anchorOverride or cityName
return string.format("[[List_of_cities_in_Bassaridia_Vaeringheim#%s|%s]]", anchor, cityName)
end
local function opId(di, divCode, idx)
return string.format("WL-%03d.%03d-%s-%02d", di.year, di.dayOfYear, divCode, idx)
end
--------------------------------------------------------------------------------
-- (C) INTEGRATE DAILY EVENTS (parse Module:BassaridianDailyEvents.makeTable())
--------------------------------------------------------------------------------
local function buildEventMapForToday()
local ok, daily = pcall(require, "Module:BassaridianDailyEvents")
if not ok or not daily or type(daily.makeTable) ~= "function" then
return {}, false
end
local tbl = daily.makeTable() -- returns wikitable text with rows: "| date || city || event || urgency"
local eventMap = {}
for line in tostring(tbl):gmatch("[^\n]+") do
if line:match("^|%s") and not line:match("^|%-%s*$") then
local parts = mw.text.split(line:gsub("^|%s*", ""), "%s*||%s*")
if #parts >= 4 then
local city = parts[2]
local ev = parts[3]
local urg = parts[4]
if city and city ~= "" and not eventMap[city] then
eventMap[city] = { desc = ev, urgency = urg }
end
end
end
end
return eventMap, true
end
local function urgencyScore(urg)
if urg == "CRITICAL" then return 4 end
if urg == "HIGH" then return 3 end
if urg == "MEDIUM" then return 2 end
if urg == "LOW" then return 1 end
return 0
end
--------------------------------------------------------------------------------
-- (D) FORCE ORGANIZATION (as requested)
--------------------------------------------------------------------------------
local GROUND_ECHELONS = {
{ name="Turmae", weight=5 },
{ name="Contubernia", weight=5 },
{ name="Ordinis", weight=4 },
{ name="Manipuli", weight=3 },
{ name="Centuriae", weight=2 },
{ name="Cohortes", weight=1 },
}
local NAVAL_ECHELONS = {
{ name="Birlik-i Gemi (Patrol Element)", weight=5 },
{ name="Tabur-i Derya (Task Unit)", weight=3 },
{ name="Ordu (Task Group)", weight=1 },
}
local AIR_ECHELONS = {
{ name="Zeygi (Element)", weight=5 },
{ name="Ptisis (Flight)", weight=3 },
{ name="Moira (Squadron)", weight=1 },
}
local function pickEchelonForUrgency(echelons, uScore)
local tuned = {}
for _, e in ipairs(echelons) do
local w = e.weight or 1
if uScore >= 3 then
if e.name:match("Cohortes") or e.name:match("Ordu %(") or e.name:match("Moira") then
w = w + 3
elseif e.name:match("Centuriae") or e.name:match("Tabur%-i Derya") or e.name:match("Ptisis") then
w = w + 2
end
elseif uScore == 2 then
if e.name:match("Centuriae") or e.name:match("Tabur%-i Derya") or e.name:match("Ptisis") then
w = w + 1
end
end
table.insert(tuned, { name = e.name, weight = w })
end
return pickWeighted(tuned).name
end
--------------------------------------------------------------------------------
-- (E) DIVISIONS + CITY POOLS
--------------------------------------------------------------------------------
local DIVISIONS = {
{ name="Vaeringheim Division", link="[[Vaeringheim Division]]", code="VAE", weight=5,
cities={"Vaeringheim","Luminaria","Serena","Symphonara","Aurelia","Somniumpolis","Nexa","Lunalis Sancta","Sylvapolis","Delphica","Catonis Atrium","Ferrum Citadel"} },
{ name="Alpazkigz Division", link="[[Alpazkigz Division]]", code="ALP", weight=4,
cities={"The Alpazkigz","Pyralis","Acheron","Erythros","Aureum","Koinonía","Saluria","Aetherium","Fanghorn","Eikbu","Galvø","Sårensby","Slevik","Sjøsborg","Storesund","Notranskja","Kaledonija"} },
{ name="Odiferia Division", link="[[Odiferia Division]]", code="ODF", weight=3,
cities={"Odiferia","Somniumpolis","Aetherium","Ferrum Citadel","Aurelia","Symphonara"} },
{ name="Hatch Ministry Division", link="[[Hatch Ministry Division]]", code="HAT", weight=3,
cities={"Lewisburg","Lykopolis","Jogi","Keybir-Aviv","Tel-Amin","Diamandis","Thermosalem","Akróstadium","Sufriya"} },
{ name="New South Jangsong Division", link="[[New South Jangsong Division]]", code="NSJ", weight=3,
cities={"Skýrophos","Bjornopolis","Aegirheim","Norsolyra","Thorsalon","Pelagia","Myrene","Thyrea","Ephyra","Halicarn"} },
{ name="Haifan Bassaridia Division", link="[[Haifan Bassaridia Division]]", code="HFA", weight=3,
cities={"Keybir-Aviv","Tel-Amin","Diamandis","Jogi","Lewisburg","Thermosalem","Akróstadium","Sufriya","Lykopolis"} },
{ name="Bassaridian Normark Division",link="[[Bassaridian Normark Division]]",code="BNO",weight=2,
cities={"Ardclach","Riddersborg"} },
{ name="Ouriana Division", link="[[Ouriana Division]]", code="OUR", weight=2,
cities={"Bashkim","Ourid","Tonar"} },
{ name="Council of Kings Division", link="[[Council of Kings Division]]", code="CKD", weight=1,
cities={"Vaeringheim","Bashkim","Ourid","Tonar"} },
}
--------------------------------------------------------------------------------
-- (F) OPERATION TYPES (branch-aware)
--------------------------------------------------------------------------------
local OP_GROUND = {
{ name="corridor security (rail / canal / road checkpointing)", weight=4 },
{ name="urban stabilization (cordon / curfew enforcement)", weight=3 },
{ name="anti-smuggling interdiction / contraband seizure", weight=3 },
{ name="infrastructure protection (port/bridge/aqueduct)", weight=3 },
{ name="counter-insurgency sweep (cell disruption)", weight=2 },
{ name="hazard response (evacuation / debris clearance)", weight=2 },
{ name="convoy escort (supply / detainee / artifact transfer)", weight=2 },
}
local OP_NAVAL = {
{ name="maritime escort / lake patrol routing", weight=3 },
{ name="VBSS / interdiction / contraband boarding", weight=3 },
{ name="blockade enforcement / straits compliance sweep", weight=2 },
{ name="harbor security (lock-gates / inner basin patrol)", weight=2 },
}
local OP_AIR = {
{ name="ISR overwatch / route scanning", weight=4 },
{ name="rapid extraction / medevac lift", weight=2 },
{ name="strike deterrence package (show of force)", weight=1 },
}
--------------------------------------------------------------------------------
-- (G) OVERSIGHT + COMPLIANCE
--------------------------------------------------------------------------------
local OVERSIGHT = {
{ label="Senate of Elders liaison", weight=3 },
{ label="Port compliance inspector (HCE)", weight=4 },
{ label="Temple Aprobelle social-harmony auditor", weight=3 },
{ label="Straits Convention monitor (civilian)", weight=3 },
{ label="No observer (weather delay)", weight=1 },
}
local COMPLIANCE = {
{ label="Deployment verified; corridor routing approved; ledger notes filed same-day", weight=6 },
{ label="Minor variance corrected; revised routing issued with escort buffer", weight=2 },
{ label="Weather delay; holding pattern under local command until visibility improves", weight=1 },
{ label="Mission deferred (no-go); export-control check triggered by false manifest", weight=1 },
{ label="Export request refused (constitutional prohibition); material retained under Council custody", weight=1 },
}
local function rowColor(obs, comp, uScore)
local o = (obs or ""):lower()
local c = (comp or ""):lower()
if o:match("weather delay") then
return ' style="background-color:#fff3cd;"'
end
if c:match("minor variance") then
return ' style="background-color:#fff3cd;"'
end
if c:match("export request refused") or c:match("mission deferred") then
return ' style="background-color:#f8d7da;"'
end
if uScore >= 3 then
return ' style="background-color:#f8d7da;"'
end
return ' style="background-color:#d4edda;"'
end
--------------------------------------------------------------------------------
-- (H) MAIN TABLE (NO systems/platforms column)
--------------------------------------------------------------------------------
function p.makeTable(frame)
local di = getPSSCDate()
local args = frame and frame.args or {}
local rows = tonumber(args.rows) or 36
local showLegend = tostring(args.showlegend or "yes"):lower() ~= "no"
-- Build daily events mapping first (DailyEvents may reseed RNG internally)
local eventMap, hasEvents = buildEventMapForToday()
-- Seed our RNG deterministically for the day (or override for testing)
local seed = tonumber(args.seed) or (di.year * 1000 + di.dayOfYear)
math.randomseed(seed)
local divFilter = tostring(args.division or ""):lower()
local divPool = {}
if divFilter ~= "" then
for _, d in ipairs(DIVISIONS) do
if d.name:lower():find(divFilter, 1, true) or d.code:lower():find(divFilter, 1, true) then
table.insert(divPool, d)
end
end
if #divPool == 0 then divPool = DIVISIONS end
else
divPool = DIVISIONS
end
local out = {}
table.insert(out, "{| class='wikitable sortable'")
table.insert(out,
"! style='white-space:nowrap;' | Date" ..
" !! Division" ..
" !! City / Node" ..
" !! Daily event trigger" ..
" !! Urgency" ..
" !! Operation" ..
" !! Ground Forces" ..
" !! Naval Forces" ..
" !! Aerial Forces" ..
" !! Oversight" ..
" !! Compliance / Notes"
)
for i = 1, rows do
local div = pickWeighted(divPool)
local cityName = pick(div.cities)
local cityWikilink = cityLink(cityName, cityName)
local ev = eventMap[cityName]
local evDesc = ev and ev.desc or (hasEvents and "No reports" or "Events module unavailable")
local urg = ev and ev.urgency or "N/A"
local uScore = urgencyScore(urg)
-- Branch selection: Hatch/Haifan bias naval; higher urgency bias air support
local branchWeighted = {
{ name="ground", weight = 6 },
{ name="naval", weight = (div.code == "HAT" or div.code == "HFA") and 5 or 2 },
{ name="air", weight = 2 + uScore },
}
local primary = pickWeighted(branchWeighted).name
-- Choose operation pool
local op
if primary == "naval" then
op = pickWeighted(OP_NAVAL)
elseif primary == "air" then
op = pickWeighted(OP_AIR)
else
op = pickWeighted(OP_GROUND)
end
-- Formations
local ground = "—"
local naval = "—"
local air = "—"
-- Keep a ground footprint unless it's a pure air ISR day and low urgency
if not (primary == "air" and uScore <= 1 and math.random() < 0.35) then
local gE = pickEchelonForUrgency(GROUND_ECHELONS, uScore)
ground = string.format("%s (%s-%02d)", gE, div.code, math.random(1, 24))
end
if primary == "naval" or (div.code == "HAT" and math.random() < 0.55) or (uScore >= 3 and math.random() < 0.35) then
local nE = pickEchelonForUrgency(NAVAL_ECHELONS, uScore)
naval = string.format("%s (%s-N%02d)", nE, div.code, math.random(1, 12))
end
if primary == "air" or (uScore >= 2 and math.random() < 0.55) then
local aE = pickEchelonForUrgency(AIR_ECHELONS, uScore)
air = string.format("%s (%s-A%02d)", aE, div.code, math.random(1, 18))
end
local obs = pickWeighted(OVERSIGHT).label
local comp = pickWeighted(COMPLIANCE).label
local notes = bulletList({
"Op-ID: " .. opId(di, div.code, i),
"Doctrine: corridor integrity and civic stability maintenance"
})
local style = rowColor(obs, comp, uScore)
table.insert(out, "|-" .. style)
table.insert(out, string.format(
"| %s || %s || %s || %s || %s || %s || %s || %s || %s || %s || %s",
di.text,
div.link,
cityWikilink,
evDesc,
urg,
op.name,
ground,
naval,
air,
obs,
bulletList({ comp, notes })
))
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/compliant ")
table.insert(out, "<span style='background:#fff3cd;padding:2px 6px;border:1px solid #ffeeba;'>Yellow</span> = variance/adjustment/weather ")
table.insert(out, "<span style='background:#f8d7da;padding:2px 6px;border:1px solid #f5c6cb;'>Red</span> = HIGH/CRITICAL or export-control/deferment")
table.insert(out, "</div>")
end
return table.concat(out, "\n")
end
--------------------------------------------------------------------------------
-- (I) OPTIONAL: show org ladders (for documentation / page footer)
--------------------------------------------------------------------------------
function p.showOrganization()
return table.concat({
'{| class="wikitable" style="width:100%;"',
'! Branch !! Smallest → Largest',
'|-',
'| Ground || Turmae → Contubernia → Ordinis → Manipuli → Centuriae → Cohortes',
'|-',
'| Naval || Birlik-i Gemi (Patrol Element) → Tabur-i Derya (Task Unit) → Ordu (Task Group)',
'|-',
'| Aerial || Zeygi (Element) → Ptisis (Flight) → Moira (Squadron)',
'|}',
}, "\n")
end
return p