Module:MissionaryDeploymentCorum: Difference between revisions
From MicrasWiki
Jump to navigationJump to search
NewZimiaGov (talk | contribs) No edit summary |
NewZimiaGov (talk | contribs) No edit summary |
||
| Line 26: | Line 26: | ||
local dateString = string.format("%d/%d/%d PSSC", dayOfMonth, month, psscYear) | local dateString = string.format("%d/%d/%d PSSC", dayOfMonth, month, psscYear) | ||
return { text = dateString, year = psscYear, dayOfYear = dayOfYear, month = month, dom = dayOfMonth } | return { | ||
text = dateString, | |||
year = psscYear, | |||
dayOfYear = dayOfYear, | |||
month = month, | |||
dom = dayOfMonth | |||
} | |||
end | end | ||
| Line 42: | Line 48: | ||
-- Units (fixed baseline; headcount will display +50% under surge) | -- Units (fixed baseline; headcount will display +50% under surge) | ||
local units = { | local units = { | ||
{order="Celestial Harmony Sect", unit="Kleisthenes", headcount=3, code="CHS-K1"}, | {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=2, code="CHS-K2"}, | ||
{order="Celestial Harmony Sect", unit="Kleisthenes", headcount=3, code="CHS-K3"}, | {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=2, code="CHS-K4"}, | ||
{order="Celestial Harmony Sect", unit="Kleisthenes", headcount=3, code="CHS-K5"}, | {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=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-K7"}, | ||
{order="Celestial Harmony Sect", unit="Kleisthenes", headcount=5, code="CHS-K8"}, | {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-K1"}, | ||
{order="Reverie Nebulous", unit="Kleisthenes", headcount=3, code="REN-K3"}, | {order="Reverie Nebulous", unit="Kleisthenes", headcount=2, code="REN-K2"}, | ||
{order="Reverie Nebulous", unit="Kleisthenes", headcount=2, code="REN-K4"}, | {order="Reverie Nebulous", unit="Kleisthenes", headcount=3, code="REN-K3"}, | ||
{order="Reverie Nebulous", unit="Kleisthenes", headcount=3, code="REN-K5"}, | {order="Reverie Nebulous", unit="Kleisthenes", headcount=2, code="REN-K4"}, | ||
{order="Reverie Nebulous", unit="Kleisthenes", headcount=2, code="REN-K6"}, | {order="Reverie Nebulous", unit="Kleisthenes", headcount=3, code="REN-K5"}, | ||
{order="Reverie Nebulous", unit="Kleisthenes", headcount=3, code="REN-K7"}, | {order="Reverie Nebulous", unit="Kleisthenes", headcount=2, code="REN-K6"}, | ||
{order="Reverie Nebulous", unit="Kleisthenes", headcount=2, code="REN-K8"}, | {order="Reverie Nebulous", unit="Kleisthenes", headcount=3, code="REN-K7"}, | ||
{order="Reverie Nebulous", unit="Kleisthenes", headcount=5, code="REN-K9"}, | {order="Reverie Nebulous", unit="Kleisthenes", headcount=2, code="REN-K8"}, | ||
{order="Temple Alabaster", unit="Kleisthenes", headcount=5, code="ALB-K1"}, | {order="Reverie Nebulous", unit="Kleisthenes", headcount=5, code="REN-K9"}, | ||
{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-K1"}, | ||
{order="Temple Alabaster", unit="Kleisthenes", headcount=5, code="ALB-K4"}, | {order="Temple Alabaster", unit="Kleisthenes", headcount=5, code="ALB-K2"}, | ||
{order="Temple Alabaster", unit="Kleisthenes", headcount=5, code="ALB-K5"}, | {order="Temple Alabaster", unit="Kleisthenes", headcount=5, code="ALB-K3"}, | ||
{order="Sanctum Delphica", unit="Kleisthenes", headcount=3, code="DEL-K1"}, | {order="Temple Alabaster", unit="Kleisthenes", headcount=5, code="ALB-K4"}, | ||
{order="Sanctum Delphica", unit="Kleisthenes", headcount=2, code="DEL-K2"}, | {order="Temple Alabaster", unit="Kleisthenes", headcount=5, code="ALB-K5"}, | ||
{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=3, code="DEL-K1"}, | ||
{order="Sanctum Delphica", unit="Kleisthenes", headcount=5, code="DEL-K5"}, | {order="Sanctum Delphica", unit="Kleisthenes", headcount=2, code="DEL-K2"}, | ||
{order="Sanctum Delphica", unit="Kleisthenes", headcount=5, code="DEL-K6"}, | {order="Sanctum Delphica", unit="Kleisthenes", headcount=3, code="DEL-K3"}, | ||
{order="Sanctum Delphica", unit="Kleisthenes", headcount=5, code="DEL-K7"}, | {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="Dodekade", headcount=12, code="UMO-D1"}, | ||
{order="Order of the Umbral Oracle", unit="Hetairos", headcount=6, code="UMO-H1"}, | {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=3, code="SCV-K1"}, | ||
{order="Sanctum Vitalis", unit="Kleisthenes", headcount=2, code="SCV-K2"}, | {order="Sanctum Vitalis", unit="Kleisthenes", headcount=2, code="SCV-K2"}, | ||
| Line 78: | Line 89: | ||
{order="Sanctum Vitalis", unit="Kleisthenes", headcount=5, code="SCV-K4"}, | {order="Sanctum Vitalis", unit="Kleisthenes", headcount=5, code="SCV-K4"}, | ||
{order="Sanctum Vitalis", unit="Kleisthenes", headcount=10, code="SCV-K5"}, | {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=3, code="AZS-K1"}, | ||
{order="Azure Sentinel Sect", unit="Kleisthenes", headcount=2, code="AZS-K2"}, | {order="Azure Sentinel Sect", unit="Kleisthenes", headcount=2, code="AZS-K2"}, | ||
| Line 156: | Line 168: | ||
local function pickWeighted(list) | local function pickWeighted(list) | ||
local total = 0 | local total = 0 | ||
for _, item in ipairs(list) do total = total + (item.weight or 1) end | for _, item in ipairs(list) do | ||
total = total + (item.weight or 1) | |||
end | |||
local roll, cumul = math.random(total), 0 | local roll, cumul = math.random(total), 0 | ||
for _, item in ipairs(list) do | for _, item in ipairs(list) do | ||
cumul = cumul + (item.weight or 1) | cumul = cumul + (item.weight or 1) | ||
if roll <= cumul then return item end | if roll <= cumul then | ||
return item | |||
end | |||
end | end | ||
return list[#list] | 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 | end | ||
| Line 173: | Line 197: | ||
parcels = math.floor(parcels * 1.15) | parcels = math.floor(parcels * 1.15) | ||
end | end | ||
return string.format("patients seen: %d | return bulletList({ | ||
string.format("patients seen: %d", patients), | |||
string.format("relief parcels: %d", parcels), | |||
}) | |||
elseif activityName == "education" then | elseif activityName == "education" then | ||
local classes = math.random(1, 3) | local classes = math.random(1, 3) | ||
local students = math.random(28, 110) | local students = math.random(28, 110) | ||
return string.format("classes: %d | return bulletList({ | ||
string.format("classes: %d", classes), | |||
string.format("learners served: %d", students), | |||
}) | |||
elseif activityName == "nutrition" then | elseif activityName == "nutrition" then | ||
| Line 189: | Line 219: | ||
supplements = math.floor(supplements* 1.20) | supplements = math.floor(supplements* 1.20) | ||
end | end | ||
return string.format( | 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 | elseif activityName == "diaspora_outreach" then | ||
| Line 198: | Line 229: | ||
local consultations = math.random(4, 15) | local consultations = math.random(4, 15) | ||
local updates = math.random(2, 8) | local updates = math.random(2, 8) | ||
return string.format( | 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 | elseif activityName == "trade_facilitation" then | ||
| Line 207: | Line 239: | ||
local inspections = math.random(3, 10) | local inspections = math.random(3, 10) | ||
local disputes = math.random(0, 3) | local disputes = math.random(0, 3) | ||
return string.format( | 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 | elseif activityName == "cultural_exchange" then | ||
| Line 216: | Line 249: | ||
local participants = math.random(40, 160) | local participants = math.random(40, 160) | ||
local materials = math.random(60, 200) | local materials = math.random(60, 200) | ||
return string.format( | 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 | elseif activityName == "legal_aid" then | ||
| Line 225: | Line 259: | ||
local certificates = math.random(1, 6) | local certificates = math.random(1, 6) | ||
local mediations = math.random(0, 4) | local mediations = math.random(0, 4) | ||
return string.format( | 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) | else -- reconciliation (fallback/default) | ||
local circles = math.random(1, 4) | local circles = math.random(1, 4) | ||
local agreements = math.random(0, math.min(circles, 3)) | local agreements = math.random(0, math.min(circles, 3)) | ||
return string.format("mediation circles: %d | return bulletList({ | ||
string.format("mediation circles: %d", circles), | |||
string.format("agreements reached: %d", agreements), | |||
}) | |||
end | end | ||
end | end | ||
| Line 281: | Line 319: | ||
end | end | ||
end | end | ||
if #pool == 0 then pool = wlCorridors end | if #pool == 0 then | ||
pool = wlCorridors | |||
end | |||
return pool[math.random(#pool)] | return pool[math.random(#pool)] | ||
end | end | ||
| Line 320: | Line 360: | ||
end | end | ||
-- lane/district/activity | -- lane/district/activity | ||
table.insert(t, "WL "..wl.code) | table.insert(t, "WL " .. wl.code) | ||
table.insert(t, "district "..(d.kind or "NZ")) | table.insert(t, "district " .. (d.kind or "NZ")) | ||
table.insert(t, "activity "..act.name) | table.insert(t, "activity " .. act.name) | ||
-- surge flags | -- surge flags | ||
if surge then | if surge then | ||
table.insert(t, "health surge: active") | table.insert(t, "health surge: active") | ||
if d.name == "Nortonian Zones" or d.name == "Braxian Zones" then | if d.name == "Nortonian Zones" or d.name == "Braxian Zones" then | ||
table.insert(t, "focus: "..d.name) | table.insert(t, "focus: " .. d.name) | ||
end | end | ||
end | end | ||
| Line 340: | Line 380: | ||
math.randomseed(dateInfo.year * 1000 + dateInfo.dayOfYear) | math.randomseed(dateInfo.year * 1000 + dateInfo.dayOfYear) | ||
local rows = tonumber(frame and frame.args and frame.args.rows) or 48 | 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 showLegend = (frame and frame.args and tostring(frame.args.showlegend) or "yes"):lower() ~= "no" | ||
local healthSurge = surgeActive(dateInfo) | local healthSurge = surgeActive(dateInfo) | ||
| Line 359: | Line 399: | ||
for i = 1, rows do | for i = 1, rows do | ||
-- Prefer weighted districts (Braxian/Nortonian focus) | -- Prefer weighted districts (Braxian/Nortonian focus) | ||
local d | local d = pickWeighted(districts) | ||
-- Bias activities in focus zones toward relief/nutrition | -- Bias activities in focus zones toward relief/nutrition | ||
local act | local act | ||
| Line 368: | Line 409: | ||
end | end | ||
local u | local u = pick(units) | ||
-- Display +50% headcount during surge | -- Display +50% headcount during surge | ||
local displayedHeadcount = u.headcount | local displayedHeadcount = u.headcount | ||
| Line 377: | Line 419: | ||
local outputs = outputsFor(act.name, d.name) | local outputs = outputsFor(act.name, d.name) | ||
local obsLine = observerLine() | local obsLine = observerLine() | ||
local hceLine = | |||
local hceLine = bulletList({ | |||
hceRef(dateInfo, u.code), | |||
pick(kitNotes), | |||
}) | |||
local guards = guardrailsStatus() | local guards = guardrailsStatus() | ||
-- WL annotation | -- WL annotation | ||
local wl | local wl = pickWLCorridorForDistrict(d) | ||
local gfId | local gfId = geofenceProfileId(d, dateInfo) | ||
if guards:match("deferred") then | if guards:match("deferred") then | ||
outputs = "n/a (deferred)" | outputs = "n/a (deferred)" | ||
obsLine = "Observer: n/a (deferred)" | obsLine = "Observer: n/a (deferred)" | ||
hceLine = hceRef(dateInfo, u.code) | hceLine = bulletList({ | ||
hceRef(dateInfo, u.code), | |||
"pre-mission check only", | |||
}) | |||
end | end | ||
| Line 410: | Line 460: | ||
"Triggers: " .. triggers, | "Triggers: " .. triggers, | ||
} | } | ||
if surgeNote then table.insert(guardrailsParts, surgeNote) end | if surgeNote then | ||
local guardrailsNotes = | table.insert(guardrailsParts, surgeNote) | ||
end | |||
local guardrailsNotes = bulletList(guardrailsParts) | |||
local activityLabel = act.label or act.name | local activityLabel = act.label or act.name | ||
table.insert(out, string.format("| %s || %s || %s || %s || %s || %s || %s || %s", | table.insert(out, string.format( | ||
dateInfo.text, d.name, unitStr, activityLabel, outputs, obsLine, hceLine, guardrailsNotes)) | "| %s || %s || %s || %s || %s || %s || %s || %s", | ||
dateInfo.text, | |||
d.name, | |||
unitStr, | |||
activityLabel, | |||
outputs, | |||
obsLine, | |||
hceLine, | |||
guardrailsNotes | |||
)) | |||
end | end | ||
Latest revision as of 18:59, 29 November 2025
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