Module:MissionaryDeploymentCorum
From MicrasWiki
Jump to navigationJump to search
Documentation for this module may be created at Module:MissionaryDeploymentCorum/doc
-- Module: MissionaryDaily
-- Purpose: Daily, in-world report of missionary activities (pre-CRH), aligned with guardrails + WL expansion + transparency [[Baratar Scandal of 52 PSSC]]
local p = {}
-------------------------------------------------------------------------------
-- (A) PSSC DATE
-------------------------------------------------------------------------------
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
-- Health surge activates on/after 36/2/52 PSSC and remains active thereafter
local function surgeActive(di)
-- 36/2 => dayOfYear = 61 + 36 = 97
if di.year > 52 then return true end
if di.year < 52 then return false end
return di.dayOfYear >= 97
end
-------------------------------------------------------------------------------
-- (B) STATIC DATA
-------------------------------------------------------------------------------
-- Units (fixed baseline; headcount will display +50% under surge)
local units = {
{order="Celestial Harmony Sect", unit="Kleisthenes", headcount=3, code="CHS-K1"},
{order="Celestial Harmony Sect", unit="Kleisthenes", headcount=2, code="CHS-K2"},
{order="Celestial Harmony Sect", unit="Kleisthenes", headcount=3, code="CHS-K3"},
{order="Celestial Harmony Sect", unit="Kleisthenes", headcount=2, code="CHS-K4"},
{order="Celestial Harmony Sect", unit="Kleisthenes", headcount=3, code="CHS-K5"},
{order="Celestial Harmony Sect", unit="Kleisthenes", headcount=2, code="CHS-K6"},
{order="Celestial Harmony Sect", unit="Kleisthenes", headcount=5, code="CHS-K7"},
{order="Celestial Harmony Sect", unit="Kleisthenes", headcount=5, code="CHS-K8"},
{order="Reverie Nebulous", unit="Kleisthenes", headcount=3, code="REN-K1"},
{order="Reverie Nebulous", unit="Kleisthenes", headcount=2, code="REN-K2"},
{order="Reverie Nebulous", unit="Kleisthenes", headcount=3, code="REN-K3"},
{order="Reverie Nebulous", unit="Kleisthenes", headcount=2, code="REN-K4"},
{order="Reverie Nebulous", unit="Kleisthenes", headcount=3, code="REN-K5"},
{order="Reverie Nebulous", unit="Kleisthenes", headcount=2, code="REN-K6"},
{order="Reverie Nebulous", unit="Kleisthenes", headcount=3, code="REN-K7"},
{order="Reverie Nebulous", unit="Kleisthenes", headcount=2, code="REN-K8"},
{order="Reverie Nebulous", unit="Kleisthenes", headcount=5, code="REN-K9"},
{order="Temple Alabaster", unit="Kleisthenes", headcount=5, code="ALB-K1"},
{order="Temple Alabaster", unit="Kleisthenes", headcount=5, code="ALB-K2"},
{order="Temple Alabaster", unit="Kleisthenes", headcount=5, code="ALB-K3"},
{order="Temple Alabaster", unit="Kleisthenes", headcount=5, code="ALB-K4"},
{order="Temple Alabaster", unit="Kleisthenes", headcount=5, code="ALB-K5"},
{order="Sanctum Delphica", unit="Kleisthenes", headcount=3, code="DEL-K1"},
{order="Sanctum Delphica", unit="Kleisthenes", headcount=2, code="DEL-K2"},
{order="Sanctum Delphica", unit="Kleisthenes", headcount=3, code="DEL-K3"},
{order="Sanctum Delphica", unit="Kleisthenes", headcount=2, code="DEL-K4"},
{order="Sanctum Delphica", unit="Kleisthenes", headcount=5, code="DEL-K5"},
{order="Sanctum Delphica", unit="Kleisthenes", headcount=5, code="DEL-K6"},
{order="Sanctum Delphica", unit="Kleisthenes", headcount=5, code="DEL-K7"},
{order="Order of the Umbral Oracle", unit="Dodekade", headcount=12, code="UMO-D1"},
{order="Order of the Umbral Oracle", unit="Hetairos", headcount=6, code="UMO-H1"},
{order="Sanctum Vitalis", unit="Kleisthenes", headcount=3, code="SCV-K1"},
{order="Sanctum Vitalis", unit="Kleisthenes", headcount=2, code="SCV-K2"},
{order="Sanctum Vitalis", unit="Kleisthenes", headcount=5, code="SCV-K3"},
{order="Sanctum Vitalis", unit="Kleisthenes", headcount=5, code="SCV-K4"},
{order="Sanctum Vitalis", unit="Kleisthenes", headcount=10, code="SCV-K5"},
{order="Azure Sentinel Sect", unit="Kleisthenes", headcount=3, code="AZS-K1"},
{order="Azure Sentinel Sect", unit="Kleisthenes", headcount=2, code="AZS-K2"},
{order="Azure Sentinel Sect", unit="Kleisthenes", headcount=3, code="AZS-K3"},
{order="Azure Sentinel Sect", unit="Kleisthenes", headcount=2, code="AZS-K4"},
{order="Azure Sentinel Sect", unit="Kleisthenes", headcount=3, code="AZS-K5"},
{order="Azure Sentinel Sect", unit="Kleisthenes", headcount=2, code="AZS-K6"},
{order="Azure Sentinel Sect", unit="Kleisthenes", headcount=5, code="AZS-K7"},
{order="Azure Sentinel Sect", unit="Kleisthenes", headcount=5, code="AZS-K8"},
}
-- Districts (NZ = New Zimia heritage; GM = Bayen/Gamesman)
-- Give Nortonian & Braxian higher weight under respiratory-risk focus
local districts = {
{name="Lyvian Zones", kind="NZ", weight=1},
{name="Lucinarian Zones", kind="NZ", weight=1},
{name="Brentonian Zones", kind="NZ", weight=1},
{name="Ossyani Zones", kind="NZ", weight=1},
{name="Sinclarian Zones", kind="GM", weight=1},
{name="Nortonian Zones", kind="GM", weight=3}, -- focus
{name="Wallisian Zones", kind="GM", weight=1},
{name="Alindan Zones", kind="GM", weight=1},
{name="Aijiac Zones", kind="GM", weight=1},
{name="Braxian Zones", kind="GM", weight=3}, -- focus
}
-- Activities (add nutrition)
local activities = {
{name="relief"},
{name="education"},
{name="reconciliation"},
{name="nutrition"},
-- internal key used for logic; label used for display
{name="diaspora_outreach", label="diaspora outreach"},
}
-- Observers (neutral/independent)
local observerPool = {
{label="Neutral temple auditor", weight=4},
{label="Independent port compliance inspector", weight=3},
{label="Community elder (witness)", weight=3},
{label="Oportian observer", weight=4},
{label="Nouvelle Alexandrie observer", weight=4},
{label="Imperial Federation observer", weight=4},
{label="Matamoros observer", weight=4},
{label="Straits Convention monitor (civilian)", weight=4},
{label="DAC Delegate observer", weight=4},
{label="No observer (weather delay)", weight=1},
}
-- Compliance notes (HCE & kit checks)
local kitNotes = {
"HCE log filed; roster posted; kit verified (no encryption/UAV/sensors)",
"Kit verified; spot-check passed; HCE reference filed",
"HCE log filed; minor inventory variance (expiry) corrected same-day",
}
-- Expanded White-Lane corridors (kind-aware)
local wlCorridors = {
-- New Zimian (NZ)
{code="WL-NZ-1", name="New Zimian Humanitarian Belt", kind="NZ", window="0700–1400 (expanded)"},
{code="WL-NZ-2", name="East Zimia Service Loop", kind="NZ", window="1200–1900 (expanded)"},
-- Gamesman/Bayen (GM)
{code="WL-GM-1", name="Bayen Relief Spur", kind="GM", window="0900–1600 (expanded)"},
{code="WL-GM-2", name="Lake Norton South Chain", kind="GM", window="1300–2000 (expanded)"},
-- Mixed coastal shuttle usable by either
{code="WL-SEA-1", name="Corum Coastal Shuttle", kind="Mixed", window="0600–1800 (expanded)"},
}
-------------------------------------------------------------------------------
-- (C) HELPERS
-------------------------------------------------------------------------------
local function pick(list, seedRoll)
local idx = (seedRoll and ((seedRoll % #list) + 1)) or math.random(#list)
return list[idx]
end
local function pickWeighted(list)
local total = 0
for _, item in ipairs(list) do
total = total + (item.weight or 1)
end
local roll, cumul = math.random(total), 0
for _, item in ipairs(list) do
cumul = cumul + (item.weight or 1)
if roll <= cumul then
return item
end
end
return list[#list]
end
-- Render a simple HTML bullet list from an array of strings
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 outputsFor(activityName, districtName)
if activityName == "relief" then
local patients = math.random(22, 72)
local parcels = math.random(60, 180)
if districtName == "Nortonian Zones" or districtName == "Braxian Zones" then
patients = math.floor(patients * 1.15)
parcels = math.floor(parcels * 1.15)
end
return bulletList({
string.format("patients seen: %d", patients),
string.format("relief parcels: %d", parcels),
})
elseif activityName == "education" then
local classes = math.random(1, 3)
local students = math.random(28, 110)
return bulletList({
string.format("classes: %d", classes),
string.format("learners served: %d", students),
})
elseif activityName == "nutrition" then
local rations = math.random(120, 320)
local screened = math.random(35, 120)
local supplements = math.random(80, 220)
if districtName == "Nortonian Zones" or districtName == "Braxian Zones" then
rations = math.floor(rations * 1.20)
screened = math.floor(screened * 1.20)
supplements = math.floor(supplements* 1.20)
end
return bulletList({
string.format("fortified rations: %d", rations),
string.format("health screenings: %d", screened),
string.format("supplements issued: %d", supplements),
})
elseif activityName == "diaspora_outreach" then
local registrants = math.random(12, 60)
local consultations = math.random(4, 15)
local updates = math.random(2, 8)
return bulletList({
string.format("diaspora households registered: %d", registrants),
string.format("DAC consultations: %d", consultations),
string.format("registry updates: %d", updates),
})
elseif activityName == "trade_facilitation" then
local manifests = math.random(5, 25)
local inspections = math.random(3, 10)
local disputes = math.random(0, 3)
return bulletList({
string.format("manifests verified: %d", manifests),
string.format("shipments inspected: %d", inspections),
string.format("tariff disputes mediated: %d", disputes),
})
elseif activityName == "cultural_exchange" then
local events = math.random(1, 4)
local participants = math.random(40, 160)
local materials = math.random(60, 200)
return bulletList({
string.format("events hosted: %d", events),
string.format("participants engaged: %d", participants),
string.format("heritage materials distributed: %d", materials),
})
elseif activityName == "legal_aid" then
local cases = math.random(2, 10)
local certificates = math.random(1, 6)
local mediations = math.random(0, 4)
return bulletList({
string.format("legal cases assisted: %d", cases),
string.format("compliance certificates issued: %d", certificates),
string.format("mediations completed: %d", mediations),
})
else -- reconciliation (fallback/default)
local circles = math.random(1, 4)
local agreements = math.random(0, math.min(circles, 3))
return bulletList({
string.format("mediation circles: %d", circles),
string.format("agreements reached: %d", agreements),
})
end
end
local function observerLine()
local obs = pickWeighted(observerPool)
if obs.label == "No observer (weather delay)" then
return "Observer: n/a (weather delay); affidavits to follow"
else
return "Observer present: " .. obs.label
end
end
local function hceRef(dateInfo, unitCode)
return string.format("HCE-%03d.%03d-%s", dateInfo.year, dateInfo.dayOfYear, unitCode)
end
local function guardrailsStatus()
local roll = math.random()
if roll < 0.08 then
return "Geofence: route adjusted (+1.6 km buffer); Separation: ✓; No-contact log: signed"
elseif roll < 0.95 then
return "Geofence: ✓; Separation: ✓; No-contact log: signed"
else
return "Mission deferred (no-go buffer triggered); rescheduled next window"
end
end
-- Row color based on compliance outcomes
local function rowColor(hceLine, guards, obsLine)
if guards:match("deferred") or hceLine:match("pre%-mission") then
return ' style="background-color:#f8d7da;"' -- red
end
if hceLine:match("variance") or guards:match("adjusted") or obsLine:match("weather delay") then
return ' style="background-color:#fff3cd;"' -- yellow
end
return ' style="background-color:#d4edda;"' -- green
end
-- Pick a White-Lane corridor suitable for a given district (by kind)
local function pickWLCorridorForDistrict(district)
local kind = district.kind
local pool = {}
for _, wl in ipairs(wlCorridors) do
if wl.kind == kind or wl.kind == "Mixed" then
table.insert(pool, wl)
end
end
if #pool == 0 then
pool = wlCorridors
end
return pool[math.random(#pool)]
end
-- Generate a simple geofence profile ID by kind/day
local function geofenceProfileId(district, dateInfo)
local k = district.kind or "NZ"
return string.format("GF-%s-%03d", k, dateInfo.dayOfYear)
end
-- Build a compact transparency trigger line per row
local function buildTriggers(u, d, act, obsLine, hceLine, guards, wl, di, surge)
local t = {}
-- observer presence / delay
if obsLine:match("Observer present") then
local who = obsLine:gsub("Observer present: ","")
table.insert(t, "observer: " .. who)
elseif obsLine:match("weather delay") then
table.insert(t, "observer: weather delay")
else
table.insert(t, "observer: n/a")
end
-- kit status
if hceLine:match("minor inventory variance") then
table.insert(t, "kit: variance")
elseif hceLine:match("pre%-mission") then
table.insert(t, "kit: pre-mission only")
else
table.insert(t, "kit: verified")
end
-- guardrails
if guards:match("adjusted") then
table.insert(t, "geofence: adjusted")
elseif guards:match("deferred") then
table.insert(t, "geofence: deferred")
else
table.insert(t, "geofence: OK")
end
-- lane/district/activity
table.insert(t, "WL " .. wl.code)
table.insert(t, "district " .. (d.kind or "NZ"))
table.insert(t, "activity " .. act.name)
-- surge flags
if surge then
table.insert(t, "health surge: active")
if d.name == "Nortonian Zones" or d.name == "Braxian Zones" then
table.insert(t, "focus: " .. d.name)
end
end
return table.concat(t, "; ")
end
-------------------------------------------------------------------------------
-- (D) MAIN TABLE
-------------------------------------------------------------------------------
function p.makeTable(frame)
local dateInfo = getPSSCDate()
math.randomseed(dateInfo.year * 1000 + dateInfo.dayOfYear)
local rows = tonumber(frame and frame.args and frame.args.rows) or 48
local showLegend = (frame and frame.args and tostring(frame.args.showlegend) or "yes"):lower() ~= "no"
local healthSurge = surgeActive(dateInfo)
local out = {}
table.insert(out, "{| class='wikitable sortable'")
table.insert(out,
"! style='white-space:nowrap;' | Date" ..
" !! District" ..
" !! Order / Unit (Headcount)" ..
" !! Activity" ..
" !! Outputs" ..
" !! Observer Verification" ..
" !! HCE / Kit Compliance" ..
" !! Guardrails / Routing Notes"
)
for i = 1, rows do
-- Prefer weighted districts (Braxian/Nortonian focus)
local d = pickWeighted(districts)
-- Bias activities in focus zones toward relief/nutrition
local act
if (d.name == "Nortonian Zones" or d.name == "Braxian Zones") and math.random() < 0.65 then
act = ({ {name="relief"}, {name="nutrition"} })[math.random(2)]
else
act = pick(activities)
end
local u = pick(units)
-- Display +50% headcount during surge
local displayedHeadcount = u.headcount
if healthSurge then
displayedHeadcount = math.floor(u.headcount * 1.5 + 0.5)
end
local outputs = outputsFor(act.name, d.name)
local obsLine = observerLine()
local hceLine = bulletList({
hceRef(dateInfo, u.code),
pick(kitNotes),
})
local guards = guardrailsStatus()
-- WL annotation
local wl = pickWLCorridorForDistrict(d)
local gfId = geofenceProfileId(d, dateInfo)
if guards:match("deferred") then
outputs = "n/a (deferred)"
obsLine = "Observer: n/a (deferred)"
hceLine = bulletList({
hceRef(dateInfo, u.code),
"pre-mission check only",
})
end
local unitStr = string.format("%s — %s (%d) [%s]", u.order, u.unit, displayedHeadcount, u.code)
-- Color
local style = rowColor(hceLine, guards, obsLine)
table.insert(out, "|-" .. style)
-- Case & triggers
local caseId = string.format("MD-%03d.%03d-%02d", dateInfo.year, dateInfo.dayOfYear, i)
local triggers = buildTriggers(u, d, act, obsLine, hceLine, guards, wl, dateInfo, healthSurge)
-- Notes (append surge note when active)
local surgeNote = healthSurge and "Public health surge: respiratory risk mitigation & nutrition support" or nil
local guardrailsParts = {
guards,
string.format("WL %s (%s)", wl.code, wl.name),
"Window: " .. wl.window,
"Geofence Profile: " .. gfId,
"Case: " .. caseId,
"Triggers: " .. triggers,
}
if surgeNote then
table.insert(guardrailsParts, surgeNote)
end
local guardrailsNotes = bulletList(guardrailsParts)
local activityLabel = act.label or act.name
table.insert(out, string.format(
"| %s || %s || %s || %s || %s || %s || %s || %s",
dateInfo.text,
d.name,
unitStr,
activityLabel,
outputs,
obsLine,
hceLine,
guardrailsNotes
))
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> = 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> = Deferred/Pre-mission only")
table.insert(out, "</div>")
end
return table.concat(out, "\n")
end
return p