Module:StraitsDaily: Difference between revisions

From MicrasWiki
Jump to navigationJump to search
NewZimiaGov (talk | contribs)
Created page with "------------------------------------------------------------------------------- -- Module: StraitsDaily -- Purpose: Daily report of Straits Conventions enforcement (Strait of Haifa) -- Focus: Bias scrutiny on Imperial Federation (esp. with "Florian cargo"), -- no explicit Florian vessels shown. -- Guardrails: outcome caps, compliance balance, non-escalatory language. ------------------------------------------------------------------------------- local p = {} ---..."
 
NewZimiaGov (talk | contribs)
No edit summary
Line 1: Line 1:
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- Module: StraitsDaily
-- Module: StraitsDaily
-- Purpose: Daily report of Straits Conventions enforcement (Strait of Haifa)
-- Purpose: Daily, in-world report of Strait of Haifa enforcement
-- Focus: Bias scrutiny on Imperial Federation (esp. with "Florian cargo"),
-- Changes in this version:
--       no explicit Florian vessels shown.
--  • No mention of Florian cargo or the Imperial Federation.
-- Guardrails: outcome caps, compliance balance, non-escalatory language.
-- • Explicit inclusion of Moorland and Bassaridian-flag vessels.
-- • Frequency smoothing + per-flag caps to avoid over-mentioning any registry.
--  • Non-escalatory language, outcome caps, and balance guardrails retained.
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------


Line 10: Line 12:


-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- (A) PSSC DATE FUNCTION
-- (A) PSSC DATE
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
local function getPSSCDate()
local function getPSSCDate()
Line 44: Line 46:
}
}


-- Flags: IF vessels can be “with Florian cargo” (biased for scrutiny)
-- Registries / flags:
--  - Explicit: Moorland, Bassaridian-flag
--  - Generic/neutral: Neutral Merchant, Distant Partner, Consortium Registry, Other Coastal State
-- Per-flag 'risky' toggles are modest, to stay non-escalatory.
local flags = {
local flags = {
     {name="Imperial Federation (with Florian cargo)", weight=5, risky=true},
     {name="Bassaridian-flag",           weight=3, risky=false},
     {name="Imperial Federation", weight=4},
     {name="Moorland",                   weight=3, risky=false},
     {name="Neutral Merchant", weight=3},
     {name="Neutral Merchant",           weight=4, risky=false},
     {name="Bassaridian-flag", weight=2},
    {name="Distant Partner",            weight=3, risky=true},
     {name="Other Coastal State", weight=2}
     {name="Consortium Registry",       weight=2, risky=true},
     {name="Other Coastal State",       weight=2, risky=false},
}
}


Line 60: Line 66:
local clauses = {
local clauses = {
     {key="Pilotage & Traffic Scheme", cite="SC §A2–A4"},
     {key="Pilotage & Traffic Scheme", cite="SC §A2–A4"},
     {key="Tonnage & Warship Limits", cite="SC §B1–B4"},
     {key="Tonnage & Warship Limits", cite="SC §B1–B4"},
     {key="Submarine Surface/Declare", cite="SC §C2–C3"},
     {key="Submarine Surface/Declare", cite="SC §C2–C3"},
     {key="Environmental Buffer", cite="SC §D1–D3"},
     {key="Environmental Buffer",     cite="SC §D1–D3"},
     {key="Sacred Site Stand-off", cite="SC §E1–E2"},
     {key="Sacred Site Stand-off",     cite="SC §E1–E2"},
     {key="Emergency/Leviathan Protocol", cite="SC §L1–L4"},
     {key="Emergency/Leviathan",       cite="SC §L1–L4"},
     {key="Right-of-Visit (Suspicion)", cite="SC §R1–R3"}
     {key="Right-of-Visit (Suspicion)",cite="SC §R1–R3"}
}
}


local actions = {
local actions = {
     {name="Documents & pilotage verification", weight=5, basis="Pilotage & Traffic Scheme"},
     {name="Documents & pilotage verification",     weight=5, basis="Pilotage & Traffic Scheme"},
     {name="Tonnage/armament declaration check", weight=4, basis="Tonnage & Warship Limits"},
     {name="Tonnage/armament declaration check",     weight=4, basis="Tonnage & Warship Limits"},
     {name="Environmental reroute order", weight=3, basis="Environmental Buffer"},
     {name="Environmental reroute order",           weight=3, basis="Environmental Buffer"},
     {name="Sacred buffer stand-off adjustment", weight=3, basis="Sacred Site Stand-off"},
     {name="Sacred buffer stand-off adjustment",     weight=3, basis="Sacred Site Stand-off"},
     {name="Submarine declare signal issued", weight=2, basis="Submarine Surface/Declare"},
     {name="Submarine declare signal issued",       weight=2, basis="Submarine Surface/Declare"},
     {name="Right-of-visit boarding (lawful suspicion)", weight=2, basis="Right-of-Visit (Suspicion)"},
     {name="Right-of-visit boarding (lawful suspicion)", weight=2, basis="Right-of-Visit (Suspicion)"},
     {name="SAR escort under Leviathan Protocol", weight=2, basis="Emergency/Leviathan Protocol"}
     {name="SAR escort under Leviathan Protocol",   weight=2, basis="Emergency/Leviathan"}
}
}


local outcomes = {
local outcomes = {
     "Compliant; cleared","Minor non-compliance; corrected; cleared",
     "Compliant; cleared",
     "Rerouted; passage maintained","Inspection completed; cargo intact",
    "Minor non-compliance; corrected; cleared",
     "Rerouted; passage maintained",
    "Inspection completed; cargo intact",
     "Administrative notice issued; monitoring",
     "Administrative notice issued; monitoring",
     "Escorted to anchorage for formal review","Denied entry pending documents",
     "Escorted to anchorage for formal review",
     "SAR/aid rendered; voyage resumed"
    "Denied entry pending documents",
     "SAR/aid rendered; voyage resumed",
    "No reports"
}
}


Line 99: Line 109:
     return list[#list]
     return list[#list]
end
end
local function pick(list) return list[math.random(#list)] end
local function pick(list) return list[math.random(#list)] end
 
local clauseIndex = (function()
local clauseIndex={} for _,c in ipairs(clauses) do clauseIndex[c.key]=c.cite end
    local t={} for _,c in ipairs(clauses) do t[c.key]=c.cite end; return t
end)()


-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- (D) PROBABILITY & EVENT GEN
-- (D) ENFORCEMENT PROBABILITY & FLAG SMOOTHING
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
local function enforcementProbability(flag, sector)
local function baseProb(flag, sector)
     local base=0.30
     local p = 0.30
     if flag.risky then base=0.50 end
     if flag.risky then p = p + 0.10 end         -- mild, non-accusatory bias
     if sector=="Sacred Buffer Approaches" or sector=="Environmental Buffer" then
     if sector=="Sacred Buffer Approaches" or sector=="Environmental Buffer" then
         base=base+0.10
         p = p + 0.10
     end
     end
     if base>0.65 then base=0.65 end
     if p>0.60 then p=0.60 end
     return base
     return p
end
end


local function makeEvent(dateText, denyCount)
-- To avoid over-mentioning, impose per-flag cap and discourage repeats.
     local sector=pick(sectors)
local function pickFlagWithCaps(flagCounts, maxPerFlag, lastFlagName)
     local flag=pickWeighted(flags)
     local tries = 0
     local vessel=pick(vessels)
     while tries < 20 do
        local f = pickWeighted(flags)
        if (flagCounts[f.name] or 0) < maxPerFlag then
            -- discourage immediate repeats 50% of the time
            if lastFlagName and f.name == lastFlagName and math.random() < 0.5 then
                tries = tries + 1
            else
                return f
            end
        else
            tries = tries + 1
        end
     end
    -- fallback to any with room
    for _,f in ipairs(flags) do
        if (flagCounts[f.name] or 0) < maxPerFlag then return f end
    end
    return flags[#flags]
end


     local prob=enforcementProbability(flag,sector)
-------------------------------------------------------------------------------
     if math.random()>prob then
-- (E) EVENT GENERATION
         return {date=dateText,sector=sector,vessel=vessel.." ("..flag.name..")",
-------------------------------------------------------------------------------
             action="Routine transit",legal="N/A",outcome="No reports",notes="Compliant passage"}
local function makeEvent(dateText, state)
    local sector = pick(sectors)
    local flag  = pickFlagWithCaps(state.flagCounts, state.maxPerFlag, state.lastFlag)
    local vessel = pick(vessels)
    state.flagCounts[flag.name] = (state.flagCounts[flag.name] or 0) + 1
    state.lastFlag = flag.name
 
     local pEnf = baseProb(flag, sector)
     if math.random() > pEnf then
         return {
            date=dateText, sector=sector,
            vessel=vessel.." ("..flag.name..")",
             action="Routine transit", legal="N/A",
            outcome="No reports", notes="Compliant passage"
        }
     end
     end


     local act=pickWeighted(actions)
     local act   = pickWeighted(actions)
     local cite=clauseIndex[act.basis] or "SC §—"
     local cite = clauseIndex[act.basis] or "SC §—"
     local outcome=pick(outcomes)
     local out  = pick(outcomes)
 
    -- Guardrail: cap strong outcomes (denied/anchorage review) total per day
    if (out=="Denied entry pending documents" or out=="Escorted to anchorage for formal review")
      and state.strongCount >= state.strongCap then
        out = "Administrative notice issued; monitoring"
    elseif out=="Denied entry pending documents" or out=="Escorted to anchorage for formal review" then
        state.strongCount = state.strongCount + 1
    end


     -- Guardrail: cap denied/anchorage outcomes
     -- Sub rules & notes
     if (outcome=="Denied entry pending documents" or outcome=="Escorted to anchorage for formal review")
    local notes = "—"
         and denyCount>=3 then
     if vessel=="Undeclared submarine" then
        outcome="Administrative notice issued; monitoring"
        act  = {name="Submarine declare signal issued", basis="Submarine Surface/Declare"}
        cite = clauseIndex[act.basis]
        notes= "Undeclared contact directed to surface and declare per procedure"
         if out=="No reports" then out="Administrative notice issued; monitoring" end
    elseif vessel=="Declared submarine" then
        notes = "Declared sub transited in daylight per SC §C2–C3"
    elseif sector=="Sacred Buffer Approaches" then
        notes = "Stand-off distance verified for sacred sites"
    elseif sector=="Environmental Buffer" then
        notes = "Sensitive habitat protection enforced during passage"
     end
     end


     return {date=dateText,sector=sector,vessel=vessel.." ("..flag.name..")",
    -- Count enforcement share for later balancing
         action=act.name,legal=act.basis.." — "..cite,outcome=outcome,notes="—"}
    if out~="No reports" then state.enfCount = state.enfCount + 1 end
 
     return {
        date=dateText, sector=sector,
        vessel=vessel.." ("..flag.name..")",
         action=act.name, legal=(act.basis.." — "..cite),
        outcome=out, notes=notes
    }
end
end


-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- (E) PUBLIC FUNCTION
-- (F) PUBLIC: makeTable
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
function p.makeTable(frame)
function p.makeTable(frame)
     local dateInfo=getPSSCDate()
     local dateInfo = getPSSCDate()
     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 18
     local ROWS = tonumber(frame and frame.args and frame.args.rows) or 18
     local out={"{| class=\"wikitable sortable\"","! Date !! Sector !! Vessel/Flag !! Action !! Legal Basis !! Outcome !! Notes"}
     local out = {}
    table.insert(out, '{| class="wikitable sortable"')
    table.insert(out, '! Date !! Sector !! Vessel/Flag !! Action !! Legal Basis !! Outcome !! Notes')


     local denyCount,totalEnf=0,0
    -- Per-flag cap: ~35% of rows per registry max (rounded up)
    local events={}
     local maxPerFlag = math.max(1, math.ceil(ROWS * 0.35))


     for i=1,ROWS do
    local state = {
         local ev=makeEvent(dateInfo.text,denyCount)
        strongCount = 0,          -- denied/anchorage combined
        if ev.outcome=="Denied entry pending documents" or ev.outcome=="Escorted to anchorage for formal review" then
        strongCap  = 3,          -- daily cap
            denyCount=denyCount+1
        flagCounts  = {},
        end
        lastFlag    = nil,
        if ev.outcome~="No reports" then totalEnf=totalEnf+1 end
        enfCount    = 0
         table.insert(events,ev)
    }
 
    local events = {}
     for i=1, ROWS do
         local ev = makeEvent(dateInfo.text, state)
         table.insert(events, ev)
     end
     end


     -- Guardrail: if >40% enforcement, downgrade some
     -- Balance guardrail: keep total enforcement ≤ 40% of rows
     local maxEnf=math.floor(ROWS*0.40)
     local maxEnf = math.floor(ROWS * 0.40)
     if totalEnf>maxEnf then
     if state.enfCount > maxEnf then
         local reduced=0
         local toReduce = state.enfCount - maxEnf
         for _,ev in ipairs(events) do
         for _,ev in ipairs(events) do
             if reduced>=totalEnf-maxEnf then break end
             if toReduce <= 0 then break end
             if ev.outcome~="No reports" and ev.outcome~="Compliant; cleared" then
             if ev.outcome ~= "No reports" and ev.outcome ~= "Compliant; cleared" then
                 ev.outcome="Compliant; cleared"; ev.notes="Auto-downgraded for balance"
                 ev.outcome = "Compliant; cleared"
                 reduced=reduced+1
                ev.notes  = (ev.notes=="—" and "Auto-balanced for daily transparency") or (ev.notes.."; auto-balanced")
                 toReduce = toReduce - 1
             end
             end
         end
         end
Line 178: Line 252:


     for _,ev in ipairs(events) do
     for _,ev in ipairs(events) do
         table.insert(out,"|-")
         table.insert(out, "|-")
         table.insert(out,"| "..ev.date.." || "..ev.sector.." || "..ev.vessel.." || "..ev.action.." || "..ev.legal.." || "..ev.outcome.." || "..ev.notes)
         table.insert(out, "| "..ev.date.." || "..ev.sector.." || "..ev.vessel.." || "..ev.action.." || "..ev.legal.." || "..ev.outcome.." || "..ev.notes)
     end
     end


     table.insert(out,"|}")
     table.insert(out, '|}')
     table.insert(out,"<!-- StraitsDaily model v2.1, "..os.date().." -->")
     table.insert(out, '<!-- StraitsDaily v3.0 (no explicit IF/Floria; Moorland+Bassaridian included; per-flag cap & smoothing) -->')
     return table.concat(out,"\n")
     return table.concat(out, "\n")
end
end


return p
return p

Revision as of 23:43, 5 September 2025

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

-------------------------------------------------------------------------------
-- Module: StraitsDaily
-- Purpose: Daily, in-world report of Strait of Haifa enforcement
-- Changes in this version:
--  • No mention of Florian cargo or the Imperial Federation.
--  • Explicit inclusion of Moorland and Bassaridian-flag vessels.
--  • Frequency smoothing + per-flag caps to avoid over-mentioning any registry.
--  • Non-escalatory language, outcome caps, and balance guardrails retained.
-------------------------------------------------------------------------------

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 totalDaysElapsed = math.floor((now - startDate) / secondsInDay)

    local yearFraction = totalDaysElapsed / 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 }
end

-------------------------------------------------------------------------------
-- (B) STATIC DATA
-------------------------------------------------------------------------------
local sectors = {
    "Haifa West Inbound","Haifa East Outbound","Mid-Strait Separation Zone",
    "Sacred Buffer Approaches","Environmental Buffer","Northern Approach TSS","Southern Approach TSS"
}

-- Registries / flags:
--  - Explicit: Moorland, Bassaridian-flag
--  - Generic/neutral: Neutral Merchant, Distant Partner, Consortium Registry, Other Coastal State
-- Per-flag 'risky' toggles are modest, to stay non-escalatory.
local flags = {
    {name="Bassaridian-flag",           weight=3, risky=false},
    {name="Moorland",                   weight=3, risky=false},
    {name="Neutral Merchant",           weight=4, risky=false},
    {name="Distant Partner",            weight=3, risky=true},
    {name="Consortium Registry",        weight=2, risky=true},
    {name="Other Coastal State",        weight=2, risky=false},
}

local vessels = {
    "Handymax bulker","Product tanker","General cargo ship","Container feeder",
    "Ro-Ro ferry","Research craft","Declared submarine","Undeclared submarine"
}

local clauses = {
    {key="Pilotage & Traffic Scheme", cite="SC §A2–A4"},
    {key="Tonnage & Warship Limits",  cite="SC §B1–B4"},
    {key="Submarine Surface/Declare", cite="SC §C2–C3"},
    {key="Environmental Buffer",      cite="SC §D1–D3"},
    {key="Sacred Site Stand-off",     cite="SC §E1–E2"},
    {key="Emergency/Leviathan",       cite="SC §L1–L4"},
    {key="Right-of-Visit (Suspicion)",cite="SC §R1–R3"}
}

local actions = {
    {name="Documents & pilotage verification",      weight=5, basis="Pilotage & Traffic Scheme"},
    {name="Tonnage/armament declaration check",     weight=4, basis="Tonnage & Warship Limits"},
    {name="Environmental reroute order",            weight=3, basis="Environmental Buffer"},
    {name="Sacred buffer stand-off adjustment",     weight=3, basis="Sacred Site Stand-off"},
    {name="Submarine declare signal issued",        weight=2, basis="Submarine Surface/Declare"},
    {name="Right-of-visit boarding (lawful suspicion)", weight=2, basis="Right-of-Visit (Suspicion)"},
    {name="SAR escort under Leviathan Protocol",    weight=2, basis="Emergency/Leviathan"}
}

local outcomes = {
    "Compliant; cleared",
    "Minor non-compliance; corrected; cleared",
    "Rerouted; passage maintained",
    "Inspection completed; cargo intact",
    "Administrative notice issued; monitoring",
    "Escorted to anchorage for formal review",
    "Denied entry pending documents",
    "SAR/aid rendered; voyage resumed",
    "No reports"
}

-------------------------------------------------------------------------------
-- (C) UTILITIES
-------------------------------------------------------------------------------
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
local function pick(list) return list[math.random(#list)] end
local clauseIndex = (function()
    local t={} for _,c in ipairs(clauses) do t[c.key]=c.cite end; return t
end)()

-------------------------------------------------------------------------------
-- (D) ENFORCEMENT PROBABILITY & FLAG SMOOTHING
-------------------------------------------------------------------------------
local function baseProb(flag, sector)
    local p = 0.30
    if flag.risky then p = p + 0.10 end         -- mild, non-accusatory bias
    if sector=="Sacred Buffer Approaches" or sector=="Environmental Buffer" then
        p = p + 0.10
    end
    if p>0.60 then p=0.60 end
    return p
end

-- To avoid over-mentioning, impose per-flag cap and discourage repeats.
local function pickFlagWithCaps(flagCounts, maxPerFlag, lastFlagName)
    local tries = 0
    while tries < 20 do
        local f = pickWeighted(flags)
        if (flagCounts[f.name] or 0) < maxPerFlag then
            -- discourage immediate repeats 50% of the time
            if lastFlagName and f.name == lastFlagName and math.random() < 0.5 then
                tries = tries + 1
            else
                return f
            end
        else
            tries = tries + 1
        end
    end
    -- fallback to any with room
    for _,f in ipairs(flags) do
        if (flagCounts[f.name] or 0) < maxPerFlag then return f end
    end
    return flags[#flags]
end

-------------------------------------------------------------------------------
-- (E) EVENT GENERATION
-------------------------------------------------------------------------------
local function makeEvent(dateText, state)
    local sector = pick(sectors)
    local flag   = pickFlagWithCaps(state.flagCounts, state.maxPerFlag, state.lastFlag)
    local vessel = pick(vessels)
    state.flagCounts[flag.name] = (state.flagCounts[flag.name] or 0) + 1
    state.lastFlag = flag.name

    local pEnf = baseProb(flag, sector)
    if math.random() > pEnf then
        return {
            date=dateText, sector=sector,
            vessel=vessel.." ("..flag.name..")",
            action="Routine transit", legal="N/A",
            outcome="No reports", notes="Compliant passage"
        }
    end

    local act   = pickWeighted(actions)
    local cite  = clauseIndex[act.basis] or "SC §—"
    local out   = pick(outcomes)

    -- Guardrail: cap strong outcomes (denied/anchorage review) total per day
    if (out=="Denied entry pending documents" or out=="Escorted to anchorage for formal review")
       and state.strongCount >= state.strongCap then
        out = "Administrative notice issued; monitoring"
    elseif out=="Denied entry pending documents" or out=="Escorted to anchorage for formal review" then
        state.strongCount = state.strongCount + 1
    end

    -- Sub rules & notes
    local notes = "—"
    if vessel=="Undeclared submarine" then
        act  = {name="Submarine declare signal issued", basis="Submarine Surface/Declare"}
        cite = clauseIndex[act.basis]
        notes= "Undeclared contact directed to surface and declare per procedure"
        if out=="No reports" then out="Administrative notice issued; monitoring" end
    elseif vessel=="Declared submarine" then
        notes = "Declared sub transited in daylight per SC §C2–C3"
    elseif sector=="Sacred Buffer Approaches" then
        notes = "Stand-off distance verified for sacred sites"
    elseif sector=="Environmental Buffer" then
        notes = "Sensitive habitat protection enforced during passage"
    end

    -- Count enforcement share for later balancing
    if out~="No reports" then state.enfCount = state.enfCount + 1 end

    return {
        date=dateText, sector=sector,
        vessel=vessel.." ("..flag.name..")",
        action=act.name, legal=(act.basis.." — "..cite),
        outcome=out, notes=notes
    }
end

-------------------------------------------------------------------------------
-- (F) PUBLIC: makeTable
-------------------------------------------------------------------------------
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 18
    local out = {}
    table.insert(out, '{| class="wikitable sortable"')
    table.insert(out, '! Date !! Sector !! Vessel/Flag !! Action !! Legal Basis !! Outcome !! Notes')

    -- Per-flag cap: ~35% of rows per registry max (rounded up)
    local maxPerFlag = math.max(1, math.ceil(ROWS * 0.35))

    local state = {
        strongCount = 0,           -- denied/anchorage combined
        strongCap   = 3,           -- daily cap
        flagCounts  = {},
        lastFlag    = nil,
        enfCount    = 0
    }

    local events = {}
    for i=1, ROWS do
        local ev = makeEvent(dateInfo.text, state)
        table.insert(events, ev)
    end

    -- Balance guardrail: keep total enforcement ≤ 40% of rows
    local maxEnf = math.floor(ROWS * 0.40)
    if state.enfCount > maxEnf then
        local toReduce = state.enfCount - maxEnf
        for _,ev in ipairs(events) do
            if toReduce <= 0 then break end
            if ev.outcome ~= "No reports" and ev.outcome ~= "Compliant; cleared" then
                ev.outcome = "Compliant; cleared"
                ev.notes   = (ev.notes=="—" and "Auto-balanced for daily transparency") or (ev.notes.."; auto-balanced")
                toReduce = toReduce - 1
            end
        end
    end

    for _,ev in ipairs(events) do
        table.insert(out, "|-")
        table.insert(out, "| "..ev.date.." || "..ev.sector.." || "..ev.vessel.." || "..ev.action.." || "..ev.legal.." || "..ev.outcome.." || "..ev.notes)
    end

    table.insert(out, '|}')
    table.insert(out, '<!-- StraitsDaily v3.0 (no explicit IF/Floria; Moorland+Bassaridian included; per-flag cap & smoothing) -->')
    return table.concat(out, "\n")
end

return p