Module:WarLeagueUnitUtilizationBVR

From MicrasWiki
Jump to navigationJump to search

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

-- Module:WarLeagueUnitUtilizationBVR
--
-- Displays the embedded War League inventory table and overlays a daily “used” estimate by division,
-- derived from Module:WarLeagueDivisionOpsBVR (formations + daily ops context) PLUS routine activity.
--
-- Civilian militia (Baratar) integration:
--   • Baratar personnel rows are inventory-only (never consumed by War League TOE demand)
--   • Baratar weapons are deployable ONLY for internal/domestic ops (riot, security, checkpoints, unrest, etc.)
--   • Leading Baratar icons are stripped for internal matching
--   • Grenades are classified as event-driven munitions (high-intensity internal ops)
--
-- Guarantees:
--   1) Table renders exactly as authored (no early break on blank lines; indentation stripped).
--   2) Robust formation parsing (naval/air echelons with parentheses + wiki markup).
--   3) “Used” NEVER exceeds availability (per cell, and per shared-division column-group).
--   4) All units are potentially activatable over time (broad background activation).
--   5) Small arms are NOT forced daily; they are selected only when appropriate to the day’s events.
--   6) Hatch Ministry always deploys 30 Ismael Class Privateers (capped to availability).
--   7) Hover platforms & quadwalkers are explicitly handled (category + activation).
--
-- Invoke:
--   {{#invoke:WarLeagueUnitUtilizationBVR|render}}
--   {{#invoke:WarLeagueUnitUtilizationBVR|render|breadth=low}}        -- low|standard|high
--   {{#invoke:WarLeagueUnitUtilizationBVR|render|breadth=high}}
--   {{#invoke:WarLeagueUnitUtilizationBVR|render|seed=520154|opsrows=40}}
--   {{#invoke:WarLeagueUnitUtilizationBVR|render|debug=yes|debugrows=8}}

local p = {}

--------------------------------------------------------------------------------
-- 1) FULL EMBEDDED TABLE (embedded exactly as provided)
--------------------------------------------------------------------------------
local RAW_BASE_TABLE = [=[
{| class="wikitable sortable" style="width:100%;"
|+ '''Unit Numbers (Activated 32/3/52 PSSC)'''
! rowspan="2" | Image
! rowspan="2" | Unit
! rowspan="2" | Total
! colspan="6" | Regional investors
! colspan="5" | Service divisions / national allocations
|-
! [[File:VaeringheimDivisionFlag.png|25px]] Vaeringheim Regional Investor
! [[File:AlpazkigzDivisionFlag.png|25px]] Alpazkigz Regional Investor
! [[File:OdiferiaFlag.png|25px]] Odiferia Regional Investor
! [[File:HafaanFlag.png|25px]] Hafaan Regional Investor
! [[File:JeseriFlag.png|25px]] Jeseri Regional Investor
! [[File:HatchMinistryFlag.png|25px]] Hatch Ministry Regional Investor
! [[File:NewSouthJangsongFlag.png|25px]] New South Jangsong Division
! [[File:HaifaDivisionFlag.png|25px]] Haifan Bassaridia Division
! [[File:BassaridianNormarkFlagB.png|25px]] Bassaridian Normark
! [[File:OurianaFlag.png|25px]] Ouriana Division
! [[File:CouncilofKingsFlag.png|25px]] Council of Kings Division

|- 
! colspan="14" style="background:#f2f2f2; text-align:left;" | '''Personnel & readiness'''
|-
| [[File:ActiveUnit1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Active_Units|Active Units]] || 249,973 || 25,866 || 20,847 || 25,042 || 1,749 || 1,008 || 7,246 || 10,187 || 15,385 || 8,354 || 21,206 || 113,083
|-
| [[File:ReserveSoldier1226.jpeg|150px|border]]  || [[Somniant_Stock_Fund_Military_Hardware#Reserve_Units|Reserve Units]] || 305,525 || 31,615 || 25,480 || 30,608 || 2,138 || 1,232 || 8,857 || 12,451 || 18,803 || 10,211 || 25,918 || 138,212
|-
| [[File:BaratarActive2026.jpeg|150px|border]] || '''[[File:Baratar420.png|23px]] Active Units''' || 1,316,449 || 148,005 || 165,738 || 136,255 || 11,394 || 5,002 || 48,964 || 69,264 || 73,785 || 45,868 || 143,225 || 468,949
|-
| [[File:BaratarReserve2026.jpeg|150px|border]] || '''[[File:Baratar420.png|23px]] Reserve Units''' || 1,608,990 || 180,894 || 202,568 || 166,534 || 13,926 || 6,114 || 59,844 || 84,656 || 90,182 || 56,061 || 175,052 || 573,159

|-
! colspan="14" style="background:#f2f2f2; text-align:left;" | '''Small arms & infantry weapons'''
|-
| [[File:KalithrosClass1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Kalithros_Class_Rifle|Kalithros Class Rifle]] || 169,837 || 17,563 || 14,156 || 17,006 || 1,192 || 688 || 4,931 || 6,916 || 10,466 || 5,684 || 14,402 || 76,832
|-
|[[File:Doryon2026.jpeg|150px|border]] || '''[[File:Baratar420.png|23px]] Doryon Rifle 7.62×39mm''' || 574,077 || 64,542 || 72,275 || 59,418 || 4,969 || 2,181 || 21,352 || 30,205 || 32,176 || 20,002 || 62,458 || 204,499
|-
|[[File:Aurean2026.jpeg|150px|border]] || '''[[File:Baratar420.png|23px]] Aurean Carbine 6.5mm Compact''' || 511,166 || 57,469 || 64,355 || 52,907 || 4,424 || 1,942 || 19,012 || 26,895 || 28,650 || 17,810 || 55,613 || 182,089
|-
| [[File:Kleisthenes.jpeg|150px|border]] || '''[[File:Baratar420.png|23px]] Kleisthenes Carbine 5.45mm Folding Stock''' || 425,972 || 47,891 || 53,629 || 44,089 || 3,687 || 1,619 || 15,843 || 22,412 || 23,875 || 14,842 || 46,344 || 151,741
|-
| [[File:DelphicaClass1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Delphica_Class_Grenadier_Rifle|Delphica Class Grenadier Rifle]] || 44,520 || 4,604 || 3,711 || 4,458 || 312 || 180 || 1,293 || 1,813 || 2,744 || 1,490 || 3,775 || 20,140
|-
| [[File:ChrysosClass1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Chrysos_Class_Commando_Rifle|Chrysos Class Commando Rifle]] || 19,821 || 2,050 || 1,652 || 1,985 || 139 || 80 || 575 || 807 || 1,221 || 663 || 1,681 || 8,967
|-
| [[File:Lothaya1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Lothaya_Class_Sniper_Rifle|Lothaya Class Sniper Rifle]] || 6,099 || 631 || 509 || 611 || 43 || 25 || 177 || 248 || 375 || 204 || 517 || 2,556
|-
|[[File:Skopion2026.jpeg|150px|border]] || '''[[File:Baratar420.png|23px]] Skopion Sniper Rifle 8.6mm with 5x Scope''' || 78,043 || 8,807 || 9,863 || 8,113 || 678 || 297 || 2,915 || 4,123 || 4,390 || 2,731 || 8,516 || 28,610
|-
| [[File:ErythrosPist1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Erythros_Class_Pistol|Erythros Class Pistol]] || 28,540 || 2,952 || 2,379 || 2,858 || 200 || 116 || 828 || 1,162 || 1,758 || 955 || 2,421 || 12,911
|-
| [[File:Lykastos2026.jpeg|150px|border]] || '''[[File:Baratar420.png|23px]] Lykastos Pistol 9mm Swampproof''' || 738,354 || 83,011 || 92,957 || 76,421 || 6,391 || 2,806 || 27,462 || 38,848 || 41,384 || 25,726 || 80,330 || 263,018
|-
| [[File:Phokion2026.jpeg|150px|border]] || '''[[File:Baratar420.png|23px]] Phokion Sidearm 10mm Officer Issue''' || 331,477 || 37,249 || 41,711 || 34,291 || 2,868 || 1,259 || 12,323 || 17,432 || 18,570 || 11,544 || 36,046 || 118,184
|-
| [[File:Bubalus1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Bubalus_XT_Class_Revolver|Bubalus XT Class Revolver]] || 150,285 || 15,541 || 12,526 || 15,049 || 1,055 || 609 || 4,363 || 6,120 || 9,261 || 5,030 || 12,744 || 67,987
|-
| [[File:Hapyriya1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Harpyia_Class_Submachine_Gun|Harpyia Class Submachine Gun]] || 22,349 || 2,312 || 1,863 || 2,238 || 157 || 91 || 649 || 910 || 1,377 || 748 || 1,896 || 10,110
|-
| [[File:Sphex2026 (2).jpeg|150px|border]] || '''[[File:Baratar420.png|23px]] Sphex SMG 5.7mm Short Barrel''' || 60,831 || 6,515 || 7,297 || 6,000 || 502 || 220 || 2,157 || 3,048 || 3,245 || 2,018 || 6,313 || 23,516
|-
| [[File:StrixShotgun1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Strix_Class_Combat_Shotgun|Strix Class Combat Shotgun]] || 35,555 || 3,677 || 2,964 || 3,561 || 249 || 144 || 1,032 || 1,448 || 2,190 || 1,190 || 3,015 || 15,346
|-
| [[File:Orontes2026.jpeg|150px|border]] || '''[[File:Baratar420.png|23px]] Orontes Shotgun 12-Gauge Semi-Auto''' || 207,519 || 23,413 || 26,219 || 21,555 || 1,802 || 791 || 7,746 || 10,957 || 11,672 || 7,256 || 22,657 || 73,451
|-
| [[File:Regavis1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Regavis_Class_DMR|Regavis Class DMR]] || 43,178 || 4,465 || 3,599 || 4,323 || 303 || 175 || 1,254 || 1,759 || 2,661 || 1,445 || 3,661 || 19,534
|-
| [[File:Cathartes1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Cathartes_Class_RPG|Cathartes Class RPG]] || 6,182 || 640 || 516 || 620 || 43 || 25 || 179 || 251 || 380 || 206 || 525 || 2,797
|-
| [[File:Myrrhex2026.jpeg|150px|border]] || '''[[File:Baratar420.png|23px]] Myrrhex Grenade Mk.IV Incense Compound''' || 375,794 || 42,256 || 47,325 || 38,920 || 3,256 || 1,430 || 13,975 || 19,770 || 21,060 || 13,080 || 40,948 || 134,774

|-
|-
! colspan="14" style="background:#f2f2f2; text-align:left;" | '''Hellhound detachments (Hellhound Breeders of Dragevik)'''
|-
|[[File:HellhoundScout1.jpeg|150px|border]] || Hellhound (scout) || 510 || 510 || 491 || 468 || 35 || 35 || 167 || 192 || 203 || 187 || 213 || 2,719
|-
|[[File:Hellhoundsoldier1.jpeg|150px|border]]|| Hellhound (soldier) || 680 || 680 || 655 || 624 || 35 || 35 || 167 || 192 || 203 || 187 || 213 || 3,422
|-
| [[File:HellhoundSpy1.jpeg|150px|border]] || Hellhound (spy) || 2,380 || 2,380 || 2,294 || 2,186 || 140 || 140 || 749 || 866 || 911 || 842 || 960 || 12,561

|-
! colspan="14" style="background:#f2f2f2; text-align:left;" | '''Armored vehicles & ground support'''
|-
| [[File:Makra1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Makra_Class_Battle_Tank|Makra Class Battle Tank]] || 1,290 || 133 || 107 || 129 || 9 || 5 || 37 || 53 || 79 || 43 || 109 || 584
|-
| [[File:LayaClass1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Laya_Class_Heavy_Tank|Laya Class Heavy Tank]] || 444 || 46 || 37 || 45 || 3 || 2 || 13 || 18 || 27 || 15 || 38 || 201
|-
| [[File:Arachne1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Arachne_Class_Light_Tank|Arachne Class Light Tank]] || 1,560 || 162 || 130 || 157 || 11 || 6 || 45 || 63 || 95 || 52 || 133 || 705
|-
| [[File:ThalassaMBT1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Thalassa_Class_Main_Battle_Tank|Thalassa Class Main Battle Tank]] || 765 || 80 || 64 || 77 || 5 || 3 || 22 || 30 || 46 || 25 || 66 || 346
|-
| [[File:Syrinx1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Syrinx_Class_Armored_Infantry|Syrinx Class Armored Infantry]] || 7,067 || 731 || 589 || 708 || 50 || 29 || 205 || 288 || 436 || 237 || 599 || 3,198
|-
| [[File:Bijarian1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Bijarian_Command_Vehicle|Bijarian Command Vehicle]] || 115 || 12 || 10 || 12 || 1 || 0 || 3 || 4 || 6 || 3 || 10 || 52
|-
| [[File:Onceanic1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Onceanic_Recon_Vehicle|Onceanic Recon Vehicle]] || 495 || 52 || 42 || 50 || 3 || 2 || 14 || 20 || 30 || 16 || 43 || 223
|-
| [[File:Ephyra1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Ephyra_Class_Anti-Aircraft_Vehicle|Ephyra Class Anti-Aircraft Vehicle]] || 218 || 23 || 19 || 22 || 1 || 1 || 6 || 8 || 13 || 7 || 19 || 99
|-
| [[File:AmpelosARV1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Ampelos_Class_Armored_Recovery_Vehicle|Ampelos Class Armored Recovery Vehicle]] || 495 || 52 || 42 || 50 || 3 || 2 || 14 || 20 || 30 || 16 || 43 || 223
|-
| [[File:Penthia1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Penthia_Class_Bridge-Laying_Vehicle|Penthia Class Bridge-Laying Vehicle]] || 146 || 16 || 13 || 15 || 1 || 0 || 4 || 5 || 8 || 4 || 13 || 66
|-
| [[File:Ampelostr1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Ampelos_Class_Rail-Laying_Engineering_Vehicle|Ampelos Class Rail-Laying Engineering Vehicle]] || 301 || 32 || 26 || 31 || 2 || 1 || 8 || 12 || 17 || 9 || 26 || 137
|-
| [[File:Corythia1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Corythia_Class_Dedicated_Logistics_and_Transport_Truck|Corythia Class Dedicated Logistics and Transport Truck]] || 301 || 32 || 26 || 31 || 2 || 1 || 8 || 12 || 17 || 9 || 26 || 137
|-
| [[File:Icaria1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Icaria_Class_Mine-Clearing_/Combat_Engineering_Vehicle|Icaria Class Mine-Clearing / Combat Engineering Vehicle]] || 134 || 14 || 11 || 14 || 1 || 0 || 4 || 5 || 8 || 4 || 11 || 61

|-
! colspan="14" style="background:#f2f2f2; text-align:left;" | '''Artillery, mortars & coastal fires'''
|-
| [[File:Aithra1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Aithra_Class_Howitzer|Aithra Class Howitzer]] || 321 || 34 || 27 || 33 || 2 || 1 || 9 || 13 || 19 || 10 || 28 || 145
|-
| [[File:Aetheris1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Aetheris_Class_Towed_Howitzer|Aetheris Class Towed Howitzer]] || 941 || 98 || 79 || 95 || 7 || 4 || 27 || 38 || 57 || 31 || 80 || 426
|-
| [[File:Iynas1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Iynas_Class_Field_Gun|Iynas Class Field Gun]] || 753 || 78 || 63 || 76 || 5 || 3 || 22 || 30 || 46 || 25 || 64 || 341
|-
| [[File:Secutor1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#300mm_Secutor_Class_Coastal_Gun|300mm Secutor Class Coastal Gun]] || 516 || 53 || 43 || 52 || 4 || 2 || 15 || 21 || 32 || 17 || 43 || 234
|-
| [[File:Halicarn1226.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#155mm_Halicarn_Class_Self-Propelled_Howitzer|155mm Halicarn Class Self-Propelled Howitzer]] || 373 || 39 || 32 || 38 || 3 || 1 || 10 || 15 || 22 || 12 || 32 || 169
|-
| [[File:Odiferian1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#120mm_Odiferian_Class_Mortar_Carrier|120mm Odiferian Class Mortar Carrier]] || 218 || 23 || 19 || 22 || 1 || 1 || 6 || 8 || 13 || 7 || 19 || 99

|-
! colspan="14" style="background:#f2f2f2; text-align:left;" | '''Autonomous systems, prototypes & fixed defenses'''
|-
| [[File:Houndbot231227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Quadwalker_“Horehound”_4170|Quadwalker “Horehound” 4170]] || 495 || 52 || 42 || 50 || 3 || 2 || 14 || 20 || 30 || 16 || 43 || 223
|-
| [[File:Dungbeetle1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Quadwalker_“Dungbeetle”_4172|Quadwalker “Dungbeetle” 4172]] || 361 || 37 || 30 || 36 || 3 || 1 || 10 || 15 || 22 || 12 || 30 || 164
|-
| [[File:KillBot1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Quadwalker_“Killbot”_4200|Quadwalker “Killbot” 4200]] || 734 || 76 || 62 || 74 || 5 || 3 || 21 || 29 || 45 || 24 || 62 || 333
|-
| [[File:Mechaoble1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Quadwalker_“Oble-Lisea”_4189|Quadwalker “Oble-Lisea” 4189]] || 528 || 55 || 44 || 53 || 4 || 2 || 15 || 21 || 32 || 17 || 45 || 239
|-
| [[File:Nexa1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Nexa_Class_Advanced_Prototype|Nexa Class Advanced Prototype]] || 1,095 || 114 || 92 || 110 || 8 || 4 || 31 || 44 || 67 || 36 || 93 || 496
|-
| [[File:GarganiClass1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Gargani_Class_Quadwalker|Gargani Class Quadwalker]] || 683 || 71 || 57 || 69 || 5 || 3 || 19 || 27 || 41 || 22 || 58 || 309
|-
| [[File:AbeisMobile1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Abeis_Class_Advanced_Air_Defense_System_(Mobile_Laser)|Abeis Class Advanced Air Defense System (Mobile Laser)]] || 146 || 16 || 13 || 15 || 1 || 0 || 4 || 5 || 8 || 4 || 13 || 66
|-
| [[File:AbeisStatic1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Abeis_Class_Static_Emplacement_(Fixed_Laser)|Abeis Class Static Emplacement (Fixed Laser)]] || 95 || 11 || 9 || 10 || 0 || 0 || 2 || 3 || 5 || 3 || 9 || 43
|-
| [[File:Fenruvian1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Fenruvian_THAAD_System_Facility|Fenruvian THAAD System Facility]] || 12 || 1 || 1 || 1 || 0 || 0 || 0 || 1 || 1 || 1 || 1 || 5
|-
| [[File:DelphisTower1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Delphis_Class_Coastal_Surveillance_Tower|Delphis Class Coastal Surveillance Tower]] || 28 || 2 || 2 || 2 || 2 || 2 || 2 || 2 || 2 || 2 || 2 || 8
|-
| [[File:AnguillaClass1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Anguilla_Class_Coastal_Missile_Battery|Anguilla Class Coastal Missile Battery]] || 373 || 39 || 32 || 38 || 3 || 1 || 10 || 15 || 22 || 12 || 32 || 169
|-
| [[File:V991227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Model_V-99_Thunderguard_Citadel_Train|Model V-99 Thunderguard Citadel Train]] || 31 || 4 || 3 || 3 || 0 || 0 || 1 || 1 || 2 || 1 || 3 || 14
|-
| [[File:SomniantBastion2026.jpeg|150px|border]] || [[Eosphorus_Motor_Company#Class_SZ-88_Somniant_Bastion|Class SZ-88 Somniant Bastion]] || 158 || 30 || 30 || 26 || 2 || 1 || 8 || 12 || 12 || 9 || 22 || 6
|-
! colspan="14" style="background:#f2f2f2; text-align:left;" | '''Naval forces & maritime platforms'''
|-
| [[File:Erigone1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Abeis-Erigone_Class_Littoral_Orbital_&_Missile_Intercept_Catamaran|Abeis-Erigone Class Littoral Orbital & Missile Intercept Catamaran]] || 12 || 2 || 1 || 2 || 0 || 0 || 0 || 0 || 0 || 0 || 2 || 5
|-
| [[File:GenportManf1225.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Abeis-Empress_Class_Littoral_Battlecruiser|Abeis-Empress Class Littoral Battlecruiser]] || 31 || 4 || 3 || 3 || 0 || 0 || 1 || 1 || 2 || 1 || 3 || 14
|-
| [[File:Aetherion1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Abeis-Aetherion Class Multidomain Hover-Corvette|Abeis-Aetherion Class Multidomain Hover-Corvette]]<br> ('''active 1/2/52 [[PSSC]]''') || 4 || 0 || 0 || 0 || 0 || 0 || 0 || 0 || 0 || 0 || 0 || 4
|-
| [[File:KalithrosBarge1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Kalithros-Class_Amphibious_Airstrip_Barge|Kalithros-Class Amphibious Airstrip Barge]] || 12 || 2 || 1 || 2 || 0 || 0 || 0 || 0 || 0 || 0 || 2 || 5
|-
| [[File:Aiji1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Aiji_Class_Air-Caravan|Aiji Class Air-Caravan]] || 31 || 4 || 3 || 3 || 0 || 0 || 1 || 1 || 2 || 1 || 3 || 14
|-
| [[File:Seidolon1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Sideron_Class_Coastal_Battlecruiser|Sideron Class Coastal Battlecruiser]] || 31 || 4 || 3 || 3 || 0 || 0 || 1 || 1 || 2 || 1 || 3 || 14
|-
| [[File:Eidolon1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Eidolon_Class_Cruiser|Eidolon Class Cruiser]] || 31 || 4 || 3 || 3 || 0 || 0 || 1 || 1 || 2 || 1 || 3 || 14
|-
| [[File:Acheron1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Acheron_Class_Submersible_Destroyer|Acheron Class Submersible Destroyer]] || 12 || 2 || 1 || 2 || 0 || 0 || 0 || 0 || 0 || 0 || 2 || 5
|-
| [[File:Xylanda1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Xylanda_Class_Attack_Submarine|Xylanda Class Attack Submarine]] || 31 || 4 || 3 || 3 || 0 || 0 || 1 || 1 || 2 || 1 || 3 || 14
|-
| [[File:Bijarian1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Bijarian_Ballistic_Missile_Submarine|Bijarian Ballistic Missile Submarine]] || 12 || 2 || 1 || 2 || 0 || 0 || 0 || 0 || 0 || 0 || 2 || 5
|-
| [[File:Vaeringheim1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Vaeringheim_Class_Corvette|Vaeringheim Class Corvette]] || 83 || 9 || 7 || 9 || 0 || 0 || 2 || 3 || 5 || 3 || 7 || 38
|-
| [[File:Sylvapolis1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Sylvapolis_Class_Frigate|Sylvapolis Class Frigate]] || 43 || 5 || 4 || 5 || 0 || 0 || 1 || 1 || 2 || 1 || 4 || 20
|-
| [[File:Atteran1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Atterian_Class_Combat_Ship|Atterian Class Combat Ship]] || 12 || 2 || 1 || 2 || 0 || 0 || 0 || 0 || 0 || 0 || 2 || 5
|-
| [[File:Aetherium1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Aetherium_Class_Patrol_Ship|Aetherium Class Patrol Ship]] || 95 || 11 || 9 || 10 || 0 || 0 || 2 || 3 || 5 || 3 || 9 || 43
|-
| [[File:Velkara1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Velkara_Class_Littoral_Landing_Ship|Velkara Class Littoral Landing Ship]] || 31 || 4 || 3 || 3 || 0 || 0 || 1 || 1 || 2 || 1 || 3 || 14
|-
| [[File:Saluria1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Saluria_Class_Gunboat|Saluria Class Gunboat]] || 43 || 5 || 4 || 5 || 0 || 0 || 1 || 1 || 2 || 1 || 4 || 20
|-
| [[File:Riceship.jpg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Cetus_Class_Attack_Craft|Cetus Class Attack Craft]] || 64 || 7 || 6 || 7 || 0 || 0 || 1 || 2 || 3 || 2 || 6 || 29
|-
| [[File:Tartarian1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Tartarian_Amphibious_Assault_Craft|Tartarian Amphibious Assault Craft]] || 83 || 9 || 7 || 9 || 0 || 0 || 2 || 3 || 5 || 3 || 7 || 38
|-
| [[File:Ismael1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Ismael_Class_Privateer_Commission|Ismael Class Privateer Commission]] || 50 || 0 || 0 || 0 || 0 || 0 || 0 || 50 || 0 || 0 || 0 || 0
|-
| [[File:Odobenus1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Odobenus_Class_Minelayer|Odobenus Class Minelayer]] || 31 || 4 || 3 || 3 || 0 || 0 || 1 || 1 || 2 || 1 || 3 || 14
|-
| [[File:Cetomagna1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Cetomagna_Class_Minehunter|Cetomagna Class Minehunter]] || 31 || 4 || 3 || 3 || 0 || 0 || 1 || 1 || 2 || 1 || 3 || 14
|-
! colspan="14" style="background:#f2f2f2; text-align:left;" | '''Maritime security services (Nefelean Seas Armory)'''
|-
| [[File:ReconDolphin.jpeg|150px|border]] || Reconnaissance Patrol (armored dolphin pair) || 62,657 || 6,120 || 5,898 || 5,620 || 421 || 421 || 1,998 || 2,310 || 2,431 || 2,245 || 2,561 || 32,636
|-
| [[File:EscortDolphin.jpeg|150px|border]] || Escort Run (convoy / VIP transit) || 65,799 || 6,460 || 6,226 || 5,933 || 421 || 421 || 2,082 || 2,406 || 2,532 || 2,339 || 2,667 || 34,317
|-
| [[File:Defensedolphin.jpeg|150px|border]] || Harbor Defense Shift (night watch) || 95,178 || 9,350 || 9,011 || 8,587 || 631 || 631 || 2,997 || 3,464 || 3,646 || 3,368 || 3,841 || 49,656
|-
| [[File:Hydrophonedolphin.jpeg|150px|border]]  || Hydrophone Sweep & Echolocation Mapping || 46,993 || 4,590 || 4,424 || 4,215 || 316 || 316 || 1,499 || 1,732 || 1,823 || 1,684 || 1,921 || 24,477
|-
| [[File:Interceptdolphin.jpeg|150px|border]]  || Rapid Intercept / Deterrence Callout || 116,064 || 11,390 || 10,977 || 10,460 || 772 || 772 || 3,663 || 4,234 || 4,456 || 4,116 || 4,694 || 60,534
|-

! colspan="14" style="background:#f2f2f2; text-align:left;" | '''Mobile platforms, UAVs & aviation'''
|-
| [[File:Bulhanu1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Abeis-Bulhanu_Class_High-Speed_Skirmisher_(Hoverbike)|Abeis-Bulhanu Class High-Speed Skirmisher (Hoverbike)]] || 1,290 || 133 || 107 || 129 || 9 || 5 || 37 || 53 || 79 || 43 || 109 || 584
|-
| [[File:Khamsin1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Abeis-Bulhanu_Khamsin-Class_Desert_Skirmisher|Abeis-Bulhanu Khamsin-Class Desert Skirmisher]] || 1,063 || 110 || 89 || 107 || 7 || 4 || 31 || 43 || 65 || 35 || 90 || 481
|-
| [[File:Virelia1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Abeis-Bulhanu_Virelia-Class_Urban_Pacifier|Abeis-Bulhanu Virelia-Class Urban Pacifier]] || 671 || 69 || 56 || 67 || 5 || 3 || 19 || 27 || 41 || 22 || 57 || 303
|-
| [[File:Hrimthurs1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Abeis-Bulhanu_Hrimthurs-Class_Arctic_Hoverbike|Abeis-Bulhanu Hrimthurs-Class Arctic Hoverbike]] || 805 || 84 || 67 || 81 || 5 || 3 || 23 || 33 || 49 || 27 || 69 || 364
|-
| [[File:IsmaelHover1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Abeis-Ismael_Class_“Cathartes”_Littoral_Hoverbike|Abeis-Ismael Class “Cathartes” Littoral Hoverbike]] || 857 || 89 || 72 || 86 || 6 || 3 || 25 || 35 || 52 || 28 || 73 || 387
|-
| [[File:Strix1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Abeis-Empress_Class_“Strix-E”_Swarm-Interdictor_Littoral_Hoverbike|Abeis-Empress Class “Strix-E” Swarm-Interdictor Littoral Hoverbike]] || 683 || 71 || 57 || 69 || 5 || 3 || 19 || 27 || 41 || 22 || 58 || 309
|-
| [[File:Minerva1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Abeis-Minerva_Class_Combat_Littoral_Strike_Aircraft|Abeis-Minerva Class Combat Littoral Strike Aircraft]] || 83 || 9 || 7 || 9 || 0 || 0 || 2 || 3 || 5 || 3 || 7 || 38
|-
| [[File:Osirios1227.jpeg|149px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Abeis-Osiríos_Class_CAS_Gunship|Abeis-Osiríos Class CAS Gunship]] || 185 || 18 || 18 || 15 || 1 || 1 || 5 || 6 || 7 || 5 || 18 || 92
|-
| [[File:Boreas1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Abeis-Boreas_Littoral_Dominance_Helicopter|Abeis-Boreas Littoral Dominance Helicopter]] || 134 || 14 || 11 || 14 || 1 || 0 || 4 || 5 || 8 || 4 || 11 || 61
|-
| [[File:AithraFighter1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Aithra_Class_Proximity_Aircraft|Aithra Class Proximity Aircraft]] || 146 || 16 || 13 || 15 || 1 || 0 || 4 || 5 || 8 || 4 || 13 || 66
|-
| [[File:Intercept1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Symphonara_Class_Compound_Intercept|Symphonara Class Compound Intercept]] || 83 || 9 || 7 || 9 || 0 || 0 || 2 || 3 || 5 || 3 || 7 || 38
|-
| [[File:ThalassaCopt1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Thalassa_Class_Attack_Helicopter|Thalassa Class Attack Helicopter]] || 198 || 21 || 17 || 21 || 1 || 1 || 5 || 7 || 11 || 6 || 17 || 90
|-
| [[File:Catonisuav1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Catonis_Class_Unmanned_Aircraft_(UAV)|Catonis Class Unmanned Aircraft (UAV)]] || 95 || 11 || 9 || 10 || 0 || 0 || 2 || 3 || 5 || 3 || 9 || 43
|-
| [[File:Lotos1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Lotos_Class_Tactical_UAV|Lotos Class Tactical UAV]] || 167 || 18 || 14 || 17 || 1 || 1 || 4 || 6 || 10 || 5 || 15 || 75
|-
| [[File:Aurantius1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Aurantius_Class_Multi-Role_UAV|Aurantius Class Multi-Role UAV]] || 167 || 18 || 14 || 17 || 1 || 1 || 4 || 6 || 10 || 5 || 15 || 75
|-
| [[File:SeaBomber1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Chrysos_Class_Sea_Bomber|Chrysos Class Sea Bomber]] || 218 || 23 || 19 || 22 || 1 || 1 || 6 || 8 || 13 || 7 || 19 || 99
|-
| [[File:MisttalonBomber1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Misttalon_Class_Heavy_Bomber|Misttalon Class Heavy Bomber]] || 31 || 4 || 3 || 3 || 0 || 0 || 1 || 1 || 2 || 1 || 3 || 14
|-
| [[File:Change into a 202512291428.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Orphali_Class_Tanker_Aircraft|Orphali Class Tanker Aircraft]] || 64 || 7 || 6 || 7 || 0 || 0 || 1 || 2 || 3 || 2 || 6 || 29
|-
| [[File:Ventiflor1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Ventiflor_Eye_Class_AEW&C_Aircraft|Ventiflor Eye Class AEW&C Aircraft]] || 31 || 4 || 3 || 3 || 0 || 0 || 1 || 1 || 2 || 1 || 3 || 14
|-
| [[File:Fluxus1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Fluxus_Class_Maritime_Patrol_Aircraft|Fluxus Class Maritime Patrol Aircraft]] || 95 || 11 || 9 || 10 || 0 || 0 || 2 || 3 || 5 || 3 || 9 || 43
|-
| [[File:Hostica1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Hostica_Class_Maritime_Patrol_Aircraft|Hostica Class Maritime Patrol Aircraft]] || 95 || 11 || 9 || 10 || 0 || 0 || 2 || 3 || 5 || 3 || 9 || 43
|-
| [[File:NefelianTransport1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Nefelian_Transport_Plane|Nefelian Transport Plane]] || 115 || 12 || 10 || 12 || 1 || 0 || 3 || 4 || 6 || 3 || 10 || 52
|-
| [[File:Noctiluna1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Noctiluna_Class_Medium_Transport_Helicopter|Noctiluna Class Medium Transport Helicopter]] || 134 || 14 || 11 || 14 || 1 || 0 || 4 || 5 || 8 || 4 || 11 || 61
|-
| [[File:Umbraclaw1227.jpeg|150px|border]] || [[Somniant_Stock_Fund_Military_Hardware#Umbraclaw_Class_Heavy_Transport_Helicopter|Umbraclaw Class Heavy Transport Helicopter]] || 115 || 12 || 10 || 12 || 1 || 0 || 3 || 4 || 6 || 3 || 10 || 52
|}

]=]

--------------------------------------------------------------------------------
-- 2) Column mapping
--------------------------------------------------------------------------------
local COL_KEYS = { "VREG","ALPK","ODFR","HAFA","JESR","HMIN","NSJ","HBAS","BNOR","OURN","CKD" }
local COL_TO_DIV = {
	VREG="VAE", ALPK="ALP", ODFR="ODF",
	HAFA="HFA", JESR="HFA", HBAS="HFA",
	HMIN="HAT", NSJ="NSJ", BNOR="BNO", OURN="OUR", CKD="CKD",
}

--------------------------------------------------------------------------------
-- Hatch policy: always deploy 30 Ismael commissions (capped)
--------------------------------------------------------------------------------
local ALWAYS_HATCH_PRIVATEERS = 30
local ISMAEL_UNIT_NAME = "Ismael Class Privateer Commission"
--------------------------------------------------------------------------------
-- Hatch policy: always deploy its single V-99 train (capped)
--------------------------------------------------------------------------------
local ALWAYS_HATCH_V99 = 1
local HATCH_V99_UNIT_NAME = "Model V-99 Thunderguard Citadel Train"

--------------------------------------------------------------------------------
local ALWAYS_SZ88_MIN = {
	VAE = 18,
	ALP = 18,
	ODF = 15,
	HFA = 1,
	JES = 1,
	HAT = 5,
	NSJ = 7,
	HBD = 7,
	BNO = 5,
	OUR = 7,
	CKD = 2,
}
local SZ88_UNIT_NAME = "Class SZ-88 Somniant Bastion"

--------------------------------------------------------------------------------
-- Council of Kings policy: always use at least 20% of available CKD forces
--------------------------------------------------------------------------------
local CKD_MIN_FORCE_USE_PCT = 0.20

--------------------------------------------------------------------------------
-- Hellhound policy: always deploy a minimum daily presence (capped in overlay)
--------------------------------------------------------------------------------
-- IMPORTANT: these strings MUST exactly match the unit names in RAW_BASE_TABLE.
-- They already exist in the table at lines 111–117.
local HELLHOUND_SCOUT_KEY   = "Hellhound (scout)"
local HELLHOUND_SOLDIER_KEY = "Hellhound (soldier)"
local HELLHOUND_SPY_KEY     = "Hellhound (spy)"

-- Minimum DAILY demand added every day. Overlay later caps by availability.
local ALWAYS_HELLHOUNDS_LOW  = { scout = 8,  soldier = 12, spy = 3 }
local ALWAYS_HELLHOUNDS_STD  = { scout = 15, soldier = 25, spy = 6 }
local ALWAYS_HELLHOUNDS_HIGH = { scout = 25, soldier = 40, spy = 10 }

-- Divisions that should always show Hellhound presence
-- NOTE: These are DIV CODES (same as used elsewhere: VAE/ALP/ODF/HFA/HAT/NSJ/BNO/OUR/CKD)
local HELLHOUND_DUTY_DIVS = { "VAE","ALP","ODF","HFA","HAT","NSJ","BNO","OUR","CKD" }

--------------------------------------------------------------------------------
-- Hellhound surge policy: occasionally “exercise the full kennel”
-- (overlay caps to availability, so large demand is safe)
--------------------------------------------------------------------------------
-- How often a FULL surge occurs (lower = more frequent).
local HELLHOUND_FULL_SURGE_EVERY_LOW  = 21   -- about ~1-in-21 days
local HELLHOUND_FULL_SURGE_EVERY_STD  = 14   -- about ~1-in-14 days
local HELLHOUND_FULL_SURGE_EVERY_HIGH = 7    -- about ~1-in-7 days

-- If ops are high urgency, make surges more likely.
local HELLHOUND_SURGE_URGENCY_BONUS = true

-- Demand used for a “use everything” surge.
-- Any huge number works; overlay hard-caps it to availability.
local HELLHOUND_FULL_SURGE_DEMAND = 1000000000


--------------------------------------------------------------------------------
-- Baratar personnel activation: percent-based + hard cap
--------------------------------------------------------------------------------
local BARATAR_FORCE_CAP_PCT = 0.20          -- hard cap: never use >20% of Baratar forces
local BARATAR_DUTY_PCT_LOW  = 0.10          -- tweak as desired
local BARATAR_DUTY_PCT_STD  = 0.15
local BARATAR_DUTY_PCT_HIGH = 0.20          -- can equal cap

-- must match the key produced by unitKeyFromCell() for the Baratar Active Units row
local BARATAR_ACTIVE_KEY  = "Baratar Active Units"
local BARATAR_RESERVE_KEY = "Baratar Reserve Units" -- optional, only if you ever decide to use it


--------------------------------------------------------------------------------
-- Baratar policy: always keep some Baratar Active Units on duty (baseline internal security)
--------------------------------------------------------------------------------
local ALWAYS_BARATAR_ACTIVE_DUTY_LOW  = 5000
local ALWAYS_BARATAR_ACTIVE_DUTY_STD  = 12000
local ALWAYS_BARATAR_ACTIVE_DUTY_HIGH = 25000

-- IMPORTANT: must match the row key produced by unitKeyFromCell() for the Baratar "Active Units" row
local BARATAR_ACTIVE_KEY = "Baratar Active Units"

-- Div codes used by your overlay grouping (matches COL_TO_DIV outputs)
local BARATAR_DUTY_DIVS = { "VAE","ALP","ODF","HFA","HAT","NSJ","BNO","OUR","CKD" }
--------------------------------------------------------------------------------
-- Baratar weapons baseline: % of EACH gun type used daily + hard cap
--------------------------------------------------------------------------------
local BARATAR_WEAPON_DUTY_PCT = 0.05   -- 5% of EACH Baratar gun type (set to 0.20 if you truly want ~20%)
local BARATAR_WEAPON_CAP_PCT  = 0.20   -- never exceed 20% of any Baratar gun type

-- Apply baseline usage ONLY to these specific Baratar weapon rows (exact names as in the table)
local BARATAR_WEAPON_WHITELIST = {
	["Doryon Rifle 7.62×39mm"] = true,
	["Aurean Carbine 6.5mm Compact"] = true,
	["Kleisthenes Carbine 5.45mm Folding Stock"] = true,
	["Skopion Sniper Rifle 8.6mm with 5x Scope"] = true,
	["Lykastos Pistol 9mm Swampproof"] = true,
	["Phokion Sidearm 10mm Officer Issue"] = true,
	["Sphex SMG 5.7mm Short Barrel"] = true,
	["Orontes Shotgun 12-Gauge Semi-Auto"] = true,
	["Myrrhex Grenade Mk.IV Incense Compound"] = true,
}



--------------------------------------------------------------------------------
-- Helpers
--------------------------------------------------------------------------------
local function push(t, v) t[#t+1] = v end
local function trim(s) s=tostring(s or ""); return (s:gsub("^%s+",""):gsub("%s+$","")) end

local function toNumber(s)
	s = tostring(s or "")
	-- Keep decimals; strip commas/spaces; ignore non-numeric cells like "—"
	s = s:gsub(",", ""):gsub("&nbsp;", " "):gsub("%s+", "")
	local num = s:match("[-]?%d+%.?%d*")
	if not num or num == "" then return nil end
	return tonumber(num)
end

local function _commaInt(intStr)
	local out = intStr:reverse():gsub("(%d%d%d)", "%1,"):reverse()
	return out:gsub("^,", "")
end

local function fmt(n)
	if n == nil then return "" end
	if math.floor(n) == n then
		return _commaInt(tostring(n))
	end
	-- 2dp for service-style rows
	local s = string.format("%.2f", n)
	local intPart, frac = s:match("^(%-?%d+)%.(%d%d)$")
	return _commaInt(intPart) .. "." .. frac
end


local function cellColor(pct)
	if pct <= 0.02 then return "#d4edda" end
	if pct <= 0.05 then return "#fff3cd" end
	if pct <= 0.10 then return "#ffe5cc" end
	return "#f8d7da"
end

local function normalizeWikitextTable(text)
	local out={}
	for line in tostring(text):gmatch("([^\n]*)\n?") do
		push(out, line:gsub("^[ \t]+",""))
	end
	return table.concat(out,"\n")
end

--------------------------------------------------------------------------------
-- Baratar / Civilian Militia helpers
--------------------------------------------------------------------------------
local BARATAR_ICON_PATTERN = "File:Baratar420%.png"

local function isBaratarCell(cell)
	return tostring(cell or ""):find(BARATAR_ICON_PATTERN, 1, false) ~= nil
end

local function stripLeadingIcons(s)
	s = tostring(s or "")
	-- remove italics/bold markers so file icons can be truly "leading"
	s = s:gsub("''+","")
	-- strip one or more leading file icons
	s = s:gsub("^%s*(%[%[File:[^%]]-%]%]%s*)+", "")
	return trim(s)
end

local function extractDisplayText(wikilinkCell)
	local c = tostring(wikilinkCell or "")
	-- never treat file-links as display text (prevents capturing "23px")
	c = c:gsub("%[%[File:[^%]]-%]%]%s*", "")
	local display = c:match("%[%[[^%]|]-|([^%]]-)%]%]")
	if display and display~="" then return trim(display) end
	display = c:match("%[%[([^%]]-)%]%]")
	if display and display~="" then return trim(display) end
	-- strip remaining wiki/HTML formatting
	c = c:gsub("<[^>]+>","")
	c = c:gsub("''+","")
	return trim(c:gsub("[%[%]]",""))
end

local function extractUnitName(cell)
	-- strip Baratar/other leading icons + formatting, then extract display text
	return extractDisplayText(stripLeadingIcons(cell))
end

local function normName(name)
	name=tostring(name or ""):lower()
	name=name:gsub("[^a-z0-9%-%s/()]", " "):gsub("%s+"," ")
	return trim(name)
end

local function isPersonnelAggregate(unitName)
	local n = normName(unitName)
	return (n == "active units" or n == "reserve units"
		or n == "baratar active units" or n == "baratar reserve units")
end

-- Internal key for a row: Baratar personnel are uniquely keyed so TOE demand never hits them.
local function unitKeyFromCell(cell)
	local raw = tostring(cell or "")
	local name = extractUnitName(raw)
	if isBaratarCell(raw) and (normName(name)=="active units" or normName(name)=="reserve units") then
		return "Baratar " .. name
	end
	return name
end

-- Heuristic Baratar weapon detection (used for restriction filtering)
local function isBaratarUnitName(unitName)
	local nn = normName(unitName)
	return nn:find("lykastos",1,true)
		or nn:find("phokion",1,true)
		or nn:find("doryon",1,true)
		or nn:find("aurean",1,true)
		or nn:find("kleisthenes",1,true)
		or nn:find("orontes",1,true)
		or nn:find("skopion",1,true)
		or nn:find("sphex",1,true)
		or nn:find("myrrhex",1,true)
end

--------------------------------------------------------------------------------
-- Roles (includes grenades as event-driven munitions)
--------------------------------------------------------------------------------
local function rolesForUnit(unitName)
	local n=normName(unitName)
	local roles={}
	local function add(r) roles[r]=true end

	if isPersonnelAggregate(unitName) then
		add("personnel"); return roles
	end

	-- small arms & munitions (event-driven only)
	if n:find("rifle",1,true) or n:find("pistol",1,true) or n:find("revolver",1,true)
		or n:find("shotgun",1,true) or n:find("submachine",1,true) or n:find("dmr",1,true)
		or n:find("sniper",1,true) or n:find("rpg",1,true)
		or n:find("grenade",1,true) then
		add("small_arms")
	end

	if n:find("quadwalker",1,true) then add("autonomous"); add("all_ground") end
	if n:find("hover",1,true) or n:find("hoverbike",1,true) then add("hover_mobility"); add("all_ground") end

	if n:find("ship",1,true) or n:find("corvette",1,true) or n:find("frigate",1,true)
		or n:find("cruiser",1,true) or n:find("battlecruiser",1,true)
		or n:find("submarine",1,true) or n:find("submersible",1,true)
		or n:find("gunboat",1,true) or n:find("minelayer",1,true) or n:find("minehunter",1,true)
		or n:find("landing ship",1,true) or n:find("airstrip barge",1,true)
		or n:find("privateer",1,true) or n:find("air%-caravan",1,true) then
		add("all_sea")
	end

if n:find("uav",1,true) or n:find("aircraft",1,true) or n:find("helicopter",1,true)
	or n:find("gunship",1,true)
	or n:find("bomber",1,true) or n:find("intercept",1,true) or n:find("transport plane",1,true)
	or n:find("zeppelin",1,true) or n:find("airship",1,true) or n:find("bastion",1,true) then
	return "air"
end

	if n:find("tank",1,true) or n:find("armored infantry",1,true) or n:find("recon vehicle",1,true)
		or n:find("command vehicle",1,true) or n:find("recovery vehicle",1,true)
		or n:find("bridge%-laying",1,true) or n:find("rail%-laying",1,true) or n:find("citadel train",1,true)
		or n:find("transport truck",1,true) or n:find("mine%-clearing",1,true) or n:find("engineering",1,true)
		or n:find("howitzer",1,true) or n:find("field gun",1,true) or n:find("mortar",1,true)
		or n:find("coastal gun",1,true) or n:find("coastal missile",1,true)
		or n:find("air defense system",1,true) or n:find("static emplacement",1,true) or n:find("thaad",1,true)
		or n:find("surveillance tower",1,true) then
		add("all_ground")
	end

	-- Maritime service units (dolphin teams, harbor shifts, hydrophone sweeps)
	if n:find("dolphin",1,true)
		or n:find("hydrophone",1,true)
		or n:find("harbor defense",1,true)
		or n:find("escort run",1,true)
		or n:find("handler team",1,true)
		or n:find("intercept / deterrence",1,true)
		or n:find("reconnaissance patrol",1,true) then
		add("all_sea")
		-- Harbor defense often has shore-side posture too
		if n:find("harbor defense",1,true) then add("all_ground") end
	end

	-- Hellhound units (scout / soldier / spy)
	if n:find("hellhound", 1, true) then
		add("all_ground")
		if n:find("(spy)", 1, true) or n:find(" spy", 1, true) then
			add("specops")
			add("recon")
		elseif n:find("(scout)", 1, true) or n:find(" scout", 1, true) then
			add("recon")
		else
			add("infantry")
		end
	end



	if next(roles)==nil then add("misc") end
	return roles
end

--------------------------------------------------------------------------------
-- Ops parsing helpers
--------------------------------------------------------------------------------
local function sanitizeOpsSegment(seg)
	seg=tostring(seg or "")
	seg=seg:gsub("<br%s*/?>","\n")
	seg=seg:gsub("<[^>]+>","")
	seg=seg:gsub("''+","")
	seg=seg:gsub("%[%[[^%]|]-|([^%]]-)%]%]","%1")
	seg=seg:gsub("%[%[([^%]]-)%]%]","%1")
	seg=seg:gsub("&nbsp;"," ")
	seg=seg:gsub("%s+"," ")
	return trim(seg)
end

local function parseFormation(seg)
	seg = sanitizeOpsSegment(seg)
	local divCode = seg:match("%((%u%u%u)%-[%w]+%s*%)%s*$")
	if not divCode then return nil,nil end
	local echelon = trim(seg:gsub("%(%u%u%u%-%w+%s*%)%s*$",""))
	return echelon, divCode
end

local function urgencyScore(urg)
	urg=tostring(urg or "")
	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

local function parseImpact(impactCell)
	local s=tostring(impactCell or "")
	local g=s:match("Gnd:%s*([^;]+)") or "OK"
	local sea=s:match("Sea:%s*([^;]+)") or "OK"
	local air=s:match("Air:%s*([^;]+)") or "OK"
	return { ground=trim(g), sea=trim(sea), air=trim(air) }
end

local function impactAllows(imp, domain)
	if not imp then return true end
	local v = (domain=="sea" and imp.sea) or (domain=="air" and imp.air) or imp.ground
	v=tostring(v or "OK"):lower()
	if v:find("no%-go",1,true) then return false end
	if v:find("closed",1,true) then return false end
	return true
end

--------------------------------------------------------------------------------
-- Lua 5.1-safe deterministic picker
--------------------------------------------------------------------------------
local function hash32(s)
	s=tostring(s or "")
	local h=0
	for i=1,#s do
		h = (h*131 + s:byte(i)) % 4294967296
	end
	return h
end

local function pickDeterministic(list, k, salt)
	if not list or #list==0 or k<=0 then return {} end
	local picked, used = {}, {}
	local h = hash32(salt)
	for _=1,k do
		if #picked >= #list then break end
		h = (1103515245*h + 12345) % 4294967296
		local idx = (h % #list) + 1
		local guard=0
		while used[idx] and guard<100 do
			h = (1103515245*h + 12345) % 4294967296
			idx = (h % #list) + 1
			guard=guard+1
		end
		if not used[idx] then
			used[idx]=true
			picked[#picked+1]=list[idx]
		end
	end
	return picked
end

--------------------------------------------------------------------------------
-- Baseline TOE by echelon (canon)
--------------------------------------------------------------------------------
local TOE = {
	["Turmae"] = {
		["Active Units"]=120, ["Kalithros Class Rifle"]=120, ["Bubalus XT Class Revolver"]=20,
		["Syrinx Class Armored Infantry"]=2, ["Onceanic Recon Vehicle"]=1,
		["Corythia Class Dedicated Logistics and Transport Truck"]=2,
	},
	["Contubernia"] = {
		["Active Units"]=60, ["Kalithros Class Rifle"]=60, ["Bubalus XT Class Revolver"]=10,
		["Syrinx Class Armored Infantry"]=1, ["Corythia Class Dedicated Logistics and Transport Truck"]=1,
	},
	["Ordinis"] = {
		["Active Units"]=240, ["Kalithros Class Rifle"]=240, ["Delphica Class Grenadier Rifle"]=24,
		["Bubalus XT Class Revolver"]=40, ["Syrinx Class Armored Infantry"]=6, ["Onceanic Recon Vehicle"]=2,
		["Corythia Class Dedicated Logistics and Transport Truck"]=6,
	},
	["Manipuli"] = {
		["Active Units"]=900, ["Kalithros Class Rifle"]=900, ["Delphica Class Grenadier Rifle"]=120,
		["Chrysos Class Commando Rifle"]=60, ["Lothaya Class Sniper Rifle"]=18,
		["Bubalus XT Class Revolver"]=120, ["Cathartes Class RPG"]=18,
		["Syrinx Class Armored Infantry"]=24, ["Makra Class Battle Tank"]=2, ["Arachne Class Light Tank"]=4,
		["Ephyra Class Anti-Aircraft Vehicle"]=2, ["Corythia Class Dedicated Logistics and Transport Truck"]=30,
		["Ampelos Class Armored Recovery Vehicle"]=2,
	},
	["Centuriae"] = {
		["Active Units"]=2400, ["Kalithros Class Rifle"]=2400, ["Delphica Class Grenadier Rifle"]=360,
		["Chrysos Class Commando Rifle"]=120, ["Lothaya Class Sniper Rifle"]=36,
		["Bubalus XT Class Revolver"]=240, ["Cathartes Class RPG"]=36,
		["Syrinx Class Armored Infantry"]=80, ["Makra Class Battle Tank"]=8, ["Thalassa Class Main Battle Tank"]=4,
		["Arachne Class Light Tank"]=16, ["Aithra Class Howitzer"]=6, ["Aetheris Class Towed Howitzer"]=12,
		["Corythia Class Dedicated Logistics and Transport Truck"]=90, ["Ampelos Class Armored Recovery Vehicle"]=6,
		["Icaria Class Mine-Clearing / Combat Engineering Vehicle"]=4,
	},
	["Cohortes"] = {
		["Active Units"]=8000, ["Kalithros Class Rifle"]=8000, ["Delphica Class Grenadier Rifle"]=1200,
		["Chrysos Class Commando Rifle"]=400, ["Lothaya Class Sniper Rifle"]=120,
		["Bubalus XT Class Revolver"]=900, ["Cathartes Class RPG"]=120,
		["Syrinx Class Armored Infantry"]=240, ["Makra Class Battle Tank"]=24, ["Thalassa Class Main Battle Tank"]=12,
		["Arachne Class Light Tank"]=48, ["Aithra Class Howitzer"]=18, ["Aetheris Class Towed Howitzer"]=36,
		["Iynas Class Field Gun"]=12, ["Corythia Class Dedicated Logistics and Transport Truck"]=300,
		["Ampelos Class Armored Recovery Vehicle"]=18, ["Penthia Class Bridge-Laying Vehicle"]=4,
		["Icaria Class Mine-Clearing / Combat Engineering Vehicle"]=12,
	},
	["Birlik-i Gemi (Patrol Element)"] = { ["Aetherium Class Patrol Ship"]=1, ["Cetus Class Attack Craft"]=2 },
	["Tabur-i Derya (Task Unit)"] = {
		["Vaeringheim Class Corvette"]=2, ["Aetherium Class Patrol Ship"]=2, ["Cetus Class Attack Craft"]=6,
		["Odobenus Class Minelayer"]=1,
	},
	["Ordu (Task Group)"] = {
		["Sylvapolis Class Frigate"]=2, ["Vaeringheim Class Corvette"]=4, ["Aetherium Class Patrol Ship"]=6,
		["Cetus Class Attack Craft"]=12, ["Cetomagna Class Minehunter"]=1, ["Odobenus Class Minelayer"]=1,
	},
	["Zeygi (Element)"] = { ["Catonis Class Unmanned Aircraft (UAV)"]=1, ["Lotos Class Tactical UAV"]=1 },
	["Ptisis (Flight)"] = {
		["Abeis-Osiríos Class CAS Gunship"]=1, ["Thalassa Class Attack Helicopter"]=1, ["Noctiluna Class Medium Transport Helicopter"]=1,
		["Catonis Class Unmanned Aircraft (UAV)"]=2,
	},
	["Moira (Squadron)"] = {
		["Abeis-Osiríos Class CAS Gunship"]=2, ["Abeis-Boreas Littoral Dominance Helicopter"]=2, ["Thalassa Class Attack Helicopter"]=4,
		["Noctiluna Class Medium Transport Helicopter"]=2, ["Catonis Class Unmanned Aircraft (UAV)"]=6,
		["Aurantius Class Multi-Role UAV"]=2,
	},
}

--------------------------------------------------------------------------------
-- Coverage enforcement: ensure every unit exists in each TOE template (default 0)
--------------------------------------------------------------------------------
local function collectAllUnitsFromBase(baseText)
	local units={}
	for line in tostring(baseText):gmatch("[^\n]+") do
		if line:match("^|%s") and line:find("||",1,true) then
			local parts = mw.text.split(line:gsub("^|%s*",""), "%s*||%s*")
			if #parts == 14 then
				local unitName = unitKeyFromCell(parts[2])
				if unitName ~= "" then units[unitName]=true end
			end
		end
	end
	return units
end

local function normalizeToeTemplates(allUnits)
	for _, toe in pairs(TOE) do
		if type(toe)=="table" then
			for unitName,_ in pairs(allUnits) do
				if toe[unitName]==nil then toe[unitName]=0 end
			end
		end
	end
end

--------------------------------------------------------------------------------
-- Pools: broad activation (all units) + typed small-arms pools (event-driven)
--------------------------------------------------------------------------------
local function buildPools(allUnits)
	local pools = {
		all_any={}, all_ground={}, all_sea={}, all_air={}, misc={},
		small_arms_all={},
		small_arms_rifle={}, small_arms_grenadier={}, small_arms_commando={},
		small_arms_sniper={}, small_arms_pistol={}, small_arms_revolver={},
		small_arms_smg={}, small_arms_shotgun={}, small_arms_dmr={}, small_arms_rpg={},
		small_arms_grenade={}
	}

	for unitName,_ in pairs(allUnits) do
		if not isPersonnelAggregate(unitName) then
			local roles = rolesForUnit(unitName)

			pools.all_any[#pools.all_any+1] = unitName
			if roles.all_ground then pools.all_ground[#pools.all_ground+1] = unitName end
			if roles.all_sea then pools.all_sea[#pools.all_sea+1] = unitName end
			if roles.all_air then pools.all_air[#pools.all_air+1] = unitName end
			if roles.misc then pools.misc[#pools.misc+1] = unitName end

			if roles.small_arms then
				pools.small_arms_all[#pools.small_arms_all+1] = unitName
				local nn = normName(unitName)
				if nn:find("grenade",1,true) then pools.small_arms_grenade[#pools.small_arms_grenade+1]=unitName
				elseif nn:find("grenadier",1,true) then pools.small_arms_grenadier[#pools.small_arms_grenadier+1]=unitName
				elseif nn:find("commando",1,true) then pools.small_arms_commando[#pools.small_arms_commando+1]=unitName
				elseif nn:find("sniper",1,true) then pools.small_arms_sniper[#pools.small_arms_sniper+1]=unitName
				elseif nn:find("pistol",1,true) then pools.small_arms_pistol[#pools.small_arms_pistol+1]=unitName
				elseif nn:find("revolver",1,true) then pools.small_arms_revolver[#pools.small_arms_revolver+1]=unitName
				elseif nn:find("submachine",1,true) then pools.small_arms_smg[#pools.small_arms_smg+1]=unitName
				elseif nn:find("shotgun",1,true) then pools.small_arms_shotgun[#pools.small_arms_shotgun+1]=unitName
				elseif nn:find("dmr",1,true) then pools.small_arms_dmr[#pools.small_arms_dmr+1]=unitName
				elseif nn:find("rpg",1,true) then pools.small_arms_rpg[#pools.small_arms_rpg+1]=unitName
				elseif nn:find("rifle",1,true) then pools.small_arms_rifle[#pools.small_arms_rifle+1]=unitName
				end
			end
		end
	end

	for _,list in pairs(pools) do
		if type(list)=="table" then table.sort(list) end
	end
	return pools
end

--------------------------------------------------------------------------------
-- Category-scaled background amounts (big enough to show meaningful “used”)
--------------------------------------------------------------------------------
local function unitCategory(unitName)
	local n = normName(unitName)
	if n:find("quadwalker",1,true) then return "autonomous" end
	if n:find("hover",1,true) or n:find("hoverbike",1,true) then return "hover_mobility" end

	if n:find("howitzer",1,true) or n:find("field gun",1,true) or n:find("mortar",1,true)
		or n:find("coastal gun",1,true) or n:find("coastal missile",1,true) then
		return "fires"
	end

	if n:find("air defense system",1,true) or n:find("static emplacement",1,true) or n:find("thaad",1,true)
		or n:find("surveillance tower",1,true) then
		return "fixed_defense"
	end

	if n:find("tank",1,true) or n:find("armored infantry",1,true) or n:find("recon vehicle",1,true)
		or n:find("command vehicle",1,true) or n:find("recovery vehicle",1,true)
		or n:find("bridge%-laying",1,true) or n:find("rail%-laying",1,true)
		or n:find("transport truck",1,true) or n:find("mine%-clearing",1,true) or n:find("engineering",1,true)
		or n:find("citadel train",1,true) then
		return "ground_vehicle"
	end

if n:find("uav",1,true) or n:find("aircraft",1,true) or n:find("helicopter",1,true)
	or n:find("gunship",1,true)
	or n:find("bomber",1,true) or n:find("intercept",1,true) or n:find("transport plane",1,true)
	or n:find("zeppelin",1,true) or n:find("airship",1,true) or n:find("bastion",1,true) then
	return "air"
end

	if n:find("ship",1,true) or n:find("corvette",1,true) or n:find("frigate",1,true) or n:find("cruiser",1,true)
		or n:find("battlecruiser",1,true) or n:find("submarine",1,true) or n:find("submersible",1,true)
		or n:find("gunboat",1,true) or n:find("minelayer",1,true) or n:find("minehunter",1,true)
		or n:find("landing ship",1,true) or n:find("airstrip barge",1,true) or n:find("privateer",1,true)
		or n:find("air%-caravan",1,true) then
		return "sea"
	end

	if n:find("dolphin",1,true)
		or n:find("hydrophone",1,true)
		or n:find("harbor defense",1,true)
		or n:find("escort run",1,true)
		or n:find("handler team",1,true)
		or n:find("intercept / deterrence",1,true)
		or n:find("reconnaissance patrol",1,true) then
		return "maritime_service"
	end

	if n:find("hellhound", 1, true) then
		return "hellhound_unit"
	end


	return "misc"
end

local function breadthLevel(args)
	local b=tostring((args and args.breadth) or "standard"):lower()
	if b=="low" then return 1 end
	if b=="high" then return 3 end
	return 2
end

local function scaleFactor(uScore, bLevel)
	local s = 1
	if bLevel == 2 then s = 2 end
	if bLevel == 3 then s = 4 end
	if uScore >= 3 then s = s + 1 end
	if uScore >= 4 then s = s + 2 end
	return s
end

local function breadthAmountFor(unitName, uScore, bLevel)
	local cat = unitCategory(unitName)
	local s = scaleFactor(uScore, bLevel)
	if cat == "ground_vehicle" then return math.max(1, math.floor(2 * s)) end
	if cat == "autonomous" then return math.max(1, math.floor(2 * s)) end
	if cat == "hover_mobility" then return math.max(1, math.floor(3 * s)) end
	if cat == "fires" then return math.max(1, math.floor(1 * s)) end
	if cat == "fixed_defense" then return math.max(1, math.floor(1 * s)) end
	if cat == "air" then return math.max(1, math.floor(1 * s)) end
	if cat == "sea" then return math.max(1, math.floor(1 * s)) end
	if cat == "maritime_service" then return math.max(25, math.floor(40 * s)) end
	if cat == "hellhound_unit" then
		return math.max(10, math.floor(25 * s))
	end
	return 1
end

--------------------------------------------------------------------------------
-- Demand accumulation
--------------------------------------------------------------------------------
local function addDemand(util, divCode, unitName, amount)
	if not divCode or divCode=="" or not unitName or unitName=="" or not amount or amount<=0 then return end
	util[divCode]=util[divCode] or {}
	util[divCode][unitName]=(util[divCode][unitName] or 0) + amount
end

--------------------------------------------------------------------------------
-- Event-driven small arms selection (Baratar weapons only for domestic/internal)
--------------------------------------------------------------------------------
local function isDomesticOp(op)
	op = normName(op)
	return op:find("riot",1,true)
		or op:find("unrest",1,true)
		or op:find("checkpoint",1,true)
		or op:find("cordon",1,true)
		or op:find("curfew",1,true)
		or op:find("security",1,true)
		or op:find("stabil",1,true)
		or op:find("crowd",1,true)
		or op:find("polic",1,true)
		or op:find("domestic",1,true)
		or op:find("internal",1,true)
end

local function filteredPool(list, allowBaratar)
	if allowBaratar then return list end
	if not list or #list==0 then return list end
	local out={}
	for _,u in ipairs(list) do
		if not isBaratarUnitName(u) then out[#out+1]=u end
	end
	return out
end

local function addSmallArmsForOp(util, pools, divCode, opText, uScore, bLevel, salt)
	local op = normName(opText)
	local wants = {}

	local function want(poolName, k)
		if k and k > 0 then wants[#wants+1] = { pool = poolName, k = k } end
	end

	-- scale
	local s = 1
	if bLevel == 2 then s = 2 end
	if bLevel == 3 then s = 3 end
	if uScore >= 3 then s = s + 1 end
	if uScore >= 4 then s = s + 1 end

	-- Does this op plausibly call for infantry weapons today?
	local infantryRelevant =
		   op:find("vbss",1,true) or op:find("boarding",1,true) or op:find("interdiction",1,true)
		or op:find("contraband",1,true) or op:find("raid",1,true) or op:find("security",1,true)
		or op:find("riot",1,true) or op:find("stabil",1,true) or op:find("search",1,true)
		or op:find("cordon",1,true) or op:find("checkpoint",1,true) or op:find("corridor",1,true)
		or op:find("hostage",1,true) or op:find("sabotage",1,true)

	-- Hazard response is usually engineering-first; only include small arms if there’s unrest/looting/security language.
	if op:find("hazard",1,true) or op:find("evac",1,true) or op:find("debris",1,true) or op:find("clearance",1,true) then
		infantryRelevant = infantryRelevant or op:find("looting",1,true) or op:find("unrest",1,true) or op:find("security",1,true)
	end

	if not infantryRelevant then return end

	local domestic = isDomesticOp(opText)
	local allowBaratar = domestic  -- hard restriction

	-- Choose an appropriate mix
	if op:find("vbss",1,true) or op:find("boarding",1,true) or op:find("interdiction",1,true) then
		want("small_arms_commando", 1 + (uScore>=3 and 1 or 0))
		want("small_arms_rifle", 1 + (uScore>=3 and 1 or 0))
		want("small_arms_pistol", 1)
		want("small_arms_dmr", (uScore>=3 and 1 or 0))
		want("small_arms_smg", (uScore>=3 and 1 or 0))
	elseif op:find("checkpoint",1,true) or op:find("corridor",1,true) or op:find("security",1,true) then
		want("small_arms_rifle", 2)
		want("small_arms_smg", 1)
		want("small_arms_dmr", (uScore>=3 and 1 or 0))
		want("small_arms_shotgun", (uScore>=3 and 1 or 0))
	elseif op:find("riot",1,true) or op:find("stabil",1,true) or op:find("urban",1,true) or op:find("unrest",1,true) then
		want("small_arms_rifle", 2)
		want("small_arms_smg", 1)
		want("small_arms_commando", (uScore>=3 and 1 or 0))
		want("small_arms_pistol", 1)
	else
		want("small_arms_rifle", 2)
		want("small_arms_grenadier", (uScore>=3 and 1 or 0))
		want("small_arms_dmr", (uScore>=3 and 1 or 0))
	end

	-- Grenades: event-driven munitions for high-intensity domestic situations only
	local highIntensityDomestic = domestic and (uScore >= 3) and (
		   op:find("riot",1,true) or op:find("unrest",1,true) or op:find("violent",1,true)
		or op:find("crackdown",1,true) or op:find("insurg",1,true) or op:find("siege",1,true)
		or op:find("raid",1,true)
	)
	if highIntensityDomestic then
		want("small_arms_grenade", (uScore>=4 and 2 or 1))
	end

	-- Apply picks
	for _, w in ipairs(wants) do
		local poolList = pools[w.pool]
		poolList = filteredPool(poolList, allowBaratar)
		if poolList and #poolList > 0 then
			local picks = pickDeterministic(poolList, w.k, salt.."|"..w.pool.."|"..tostring(allowBaratar))
			for _, u in ipairs(picks) do
				local qty = 150 * s
				local nn = normName(u)

				-- scale by weapon type
				if nn:find("commando",1,true) then qty = math.floor(qty * 0.40) end
				if nn:find("grenadier",1,true) then qty = math.floor(qty * 0.35) end
				if nn:find("sniper",1,true) then qty = math.floor(qty * 0.15) end
				if nn:find("pistol",1,true) or nn:find("revolver",1,true) then qty = math.floor(qty * 0.25) end
				if nn:find("grenade",1,true) then
					-- grenades are lower-volume but can spike under CRITICAL
					qty = math.floor(60 * s)
					if uScore >= 4 then qty = qty * 2 end
				end

				if qty < 25 then qty = 25 end
				addDemand(util, divCode, u, qty)
			end
		end
	end
end

--------------------------------------------------------------------------------
-- Broad activation for all units (excluding small arms; those are event-driven)
--------------------------------------------------------------------------------
local function applyBreadthActivation(util, pools, divCode, opText, uScore, impact, salt, bLevel)
	-- Background activation: training, maintenance, readiness checks, unpublished activities.
	local baseK = (bLevel==1 and 2) or (bLevel==2 and 4) or 7
	if uScore>=3 then baseK = baseK + 2 end
	if uScore>=4 then baseK = baseK + 3 end
	if baseK > 12 then baseK = 12 end

	-- Ground (vehicles, drones, defenses; NOT small arms)
	local gPicks = pickDeterministic(pools.all_ground, baseK, salt.."|bg_ground")
	for _, u in ipairs(gPicks) do
		addDemand(util, divCode, u, breadthAmountFor(u, uScore, bLevel))
	end

	-- Sea
	if impactAllows(impact,"sea") then
		local kSea = math.max(2, math.floor(baseK/2))
		local sPicks = pickDeterministic(pools.all_sea, kSea, salt.."|bg_sea")
		for _, u in ipairs(sPicks) do
			addDemand(util, divCode, u, breadthAmountFor(u, uScore, bLevel))
		end
	end

	-- Air
	if impactAllows(impact,"air") then
		local kAir = math.max(2, math.floor(baseK/2))
		local aPicks = pickDeterministic(pools.all_air, kAir, salt.."|bg_air")
		for _, u in ipairs(aPicks) do
			addDemand(util, divCode, u, breadthAmountFor(u, uScore, bLevel))
		end
	end

	-- Misc
	local mPicks = pickDeterministic(pools.misc, (bLevel>=2 and 2 or 1), salt.."|bg_misc")
	for _, u in ipairs(mPicks) do
		addDemand(util, divCode, u, breadthAmountFor(u, uScore, bLevel))
	end

	-- Event-driven small arms (only when appropriate)
	addSmallArmsForOp(util, pools, divCode, opText, uScore, bLevel, salt.."|event_small_arms")
end

--------------------------------------------------------------------------------
-- Ismael commissions: treat NSJ column availability as HAT for this unit only
--------------------------------------------------------------------------------
local function divOverrideForUnit(unitName, colKey, defaultDiv)
	if unitName == ISMAEL_UNIT_NAME then
		if colKey == "NSJ" then return "HAT" end
	end
	return defaultDiv
end

--------------------------------------------------------------------------------
-- Compute utilization from DailyOps (TOE + breadth activation + hatch privateers)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Read availability from the embedded inventory table for a specific unit-key
-- Returns totals grouped by ops-division codes (VAE/ALP/ODF/HFA/HAT/NSJ/BNO/OUR/CKD).
--------------------------------------------------------------------------------
local function availByDivForUnitKey(baseText, targetKey)
	local avail = {}
	for line in tostring(baseText):gmatch("[^\n]+") do
		if line:match("^|%s") and line:find("||", 1, true) then
			local parts = mw.text.split(line:gsub("^|%s*", ""), "%s*||%s*")
			if #parts == 14 then
				local key = unitKeyFromCell(parts[2])
				if key == targetKey then
					for i = 4, 14 do
						local colKey = COL_KEYS[i-3]
						local defaultDiv = COL_TO_DIV[colKey]
						local divCode = divOverrideForUnit(targetKey, colKey, defaultDiv)
						local n = toNumber(parts[i]) or 0
						if divCode and n > 0 then
							avail[divCode] = (avail[divCode] or 0) + n
						end
					end
					return avail
				end
			end
		end
	end
	return avail
end

--------------------------------------------------------------------------------
-- Minimum utilization floor for one division across all inventory rows.
-- Adds enough demand so the division reaches at least floorPct of each available row.
-- Existing overlay/list logic still caps final "used" values to availability.
--------------------------------------------------------------------------------
local function applyDivisionMinimumUseFloor(baseText, util, targetDiv, floorPct)
	for line in tostring(baseText):gmatch("[^\n]+") do
		if line:match("^|%s") and line:find("||", 1, true) then
			local parts = mw.text.split(line:gsub("^|%s*", ""), "%s*||%s*")
			if #parts == 14 then
				local unitKey = unitKeyFromCell(parts[2])

				for i = 4, 14 do
					local colKey = COL_KEYS[i-3]
					local defaultDiv = COL_TO_DIV[colKey]
					local divCode = divOverrideForUnit(unitKey, colKey, defaultDiv)

					if divCode == targetDiv then
						local avail = toNumber(parts[i]) or 0
						if avail > 0 then
							local floorUse = math.floor(avail * floorPct)
							if floorUse < 1 then floorUse = 1 end

							local alreadyDemanded = (util[targetDiv] and util[targetDiv][unitKey]) or 0
							if alreadyDemanded < floorUse then
								addDemand(util, targetDiv, unitKey, floorUse - alreadyDemanded)
							end
						end
					end
				end
			end
		end
	end
end
--------------------------------------------------------------------------------
-- Availability for every Baratar small-arms row in the embedded table.
-- Returns: out[unitName][divCode] = totalAvail
--------------------------------------------------------------------------------
local function baratarSmallArmsAvailByDiv(baseText)
	local out = {}

	for line in tostring(baseText):gmatch("[^\n]+") do
		if line:match("^|%s") and line:find("||", 1, true) then
			local parts = mw.text.split(line:gsub("^|%s*", ""), "%s*||%s*")
			if #parts == 14 then
				local unitName = unitKeyFromCell(parts[2])

				-- Baratar weapon rows only (exclude personnel), and only small arms/munitions
				if BARATAR_WEAPON_WHITELIST[unitName] then
					local roles = rolesForUnit(unitName)
					if roles and roles.small_arms then
						out[unitName] = out[unitName] or {}
						for i = 4, 14 do
							local colKey = COL_KEYS[i-3]
							local defaultDiv = COL_TO_DIV[colKey]
							local divCode = divOverrideForUnit(unitName, colKey, defaultDiv)
							local n = toNumber(parts[i]) or 0
							if divCode and n > 0 then
								out[unitName][divCode] = (out[unitName][divCode] or 0) + n
							end
						end
					end
				end
			end
		end
	end

	return out
end


local function computeUtilization(args, pools)
	local util, debugRows = {}, {}
	local ok, ops = pcall(require, "Module:WarLeagueDivisionOpsBVR")
	if not ok or not ops or type(ops.makeTable) ~= "function" then
		return util, debugRows, false
	end

	local rows = tonumber(args.opsrows) or tonumber(args.rows) or 36
	local seed = args.seed
	local bLevel = breadthLevel(args)

	-- Always keep some Baratar active units on duty (internal security baseline)
	local duty = (bLevel==1 and ALWAYS_BARATAR_ACTIVE_DUTY_LOW)
		or (bLevel==3 and ALWAYS_BARATAR_ACTIVE_DUTY_HIGH)
		or ALWAYS_BARATAR_ACTIVE_DUTY_STD

	for _,div in ipairs(BARATAR_DUTY_DIVS) do
		addDemand(util, div, BARATAR_ACTIVE_KEY, duty)
	end

	-- Baratar baseline activation: % of available, hard-capped at 20%
	local baseText = normalizeWikitextTable(RAW_BASE_TABLE)
	local barAvail = availByDivForUnitKey(baseText, BARATAR_ACTIVE_KEY)

	local pct = (bLevel==1 and BARATAR_DUTY_PCT_LOW)
		or (bLevel==3 and BARATAR_DUTY_PCT_HIGH)
		or BARATAR_DUTY_PCT_STD

	for divCode, totalAvail in pairs(barAvail) do
		local cap = math.floor(totalAvail * BARATAR_FORCE_CAP_PCT)
		if cap > 0 then
			local duty = math.floor(totalAvail * pct)
			if duty < 1 then duty = 1 end
			if duty > cap then duty = cap end
			addDemand(util, divCode, BARATAR_ACTIVE_KEY, duty)
		end
	end

	-- Baseline Baratar weapons usage: 5% of EACH gun type, capped at 20%
	local barGuns = baratarSmallArmsAvailByDiv(baseText)

	for unitName, divMap in pairs(barGuns) do
		for divCode, totalAvail in pairs(divMap) do
			local cap = math.floor(totalAvail * BARATAR_WEAPON_CAP_PCT)
			if cap > 0 then
				local duty = math.floor(totalAvail * BARATAR_WEAPON_DUTY_PCT)
				if duty < 1 then duty = 1 end
				if duty > cap then duty = cap end
				if duty > 0 then
					addDemand(util, divCode, unitName, duty)
				end
			end
		end
	end



	local tbl = ops.makeTable({ args = {
		rows = rows,
		seed = seed,
		showlegend = "no",
		flagsize = args.flagsize or "20",
		division = args.division or "",
	}})

	-- avoid applying breadth multiple times per same (div, op-row, domain)
	local activated = {}

	local function activateOnce(divCode, opText, uScore, impact, salt)
		activated[divCode] = activated[divCode] or {}
		if activated[divCode][salt] then return end
		activated[divCode][salt] = true
		applyBreadthActivation(util, pools, divCode, opText, uScore, impact, salt, bLevel)
	end

	local function handleCell(forceCell, opText, uScore, impact, saltBase)
		forceCell=tostring(forceCell or "—")
		if forceCell=="—" or trim(forceCell)=="" then return end
		forceCell = forceCell:gsub("<br%s*/?>","\n")

		for _,seg in ipairs(mw.text.split(forceCell,"\n")) do
			seg=trim(seg)
			if seg~="" and seg~="—" then
				local echelon, divCode = parseFormation(seg)
				if echelon and divCode then
					-- baseline TOE demand
					local toe = TOE[echelon]
					if toe then
						for unitName, amount in pairs(toe) do
							if amount and amount>0 then addDemand(util, divCode, unitName, amount) end
						end
					end
					-- breadth once
					activateOnce(divCode, opText, uScore, impact, saltBase.."|"..divCode)
				end
			end
		end
	end

	for line in tostring(tbl):gmatch("[^\n]+") do
		if line:match("^|%s") and line:find("||",1,true) then
			local parts = mw.text.split(line:gsub("^|%s*",""), "%s*||%s*")
			-- expected: date, division, city, daily event, urgency, ops alert, ops impact, operation, ground, naval, air, ...
			if #parts >= 11 then
				local dateCell = parts[1] or ""
				local urg = parts[5] or ""
				local uScore = urgencyScore(urg)
				local impact = parseImpact(parts[7] or "")
				local opText = parts[8] or ""
				local ground = parts[9] or "—"
				local naval  = parts[10] or "—"
				local air    = parts[11] or "—"

				local salt = tostring(seed or "").."|"..tostring(dateCell).."|"..tostring(parts[2] or "").."|"..tostring(parts[3] or "").."|"..tostring(opText)

				push(debugRows, {
					date=dateCell, division=parts[2] or "", city=parts[3] or "",
					urgency=urg, impact=parts[7] or "", op=opText,
					ground=ground, naval=naval, air=air
				})

				if impactAllows(impact,"ground") then handleCell(ground, opText, uScore, impact, salt.."|G") end
				if impactAllows(impact,"sea") then handleCell(naval, opText, uScore, impact, salt.."|N") end
				if impactAllows(impact,"air") then handleCell(air, opText, uScore, impact, salt.."|A") end
			end
		end
	end

	-- Hatch always deploys 30 Ismael commissions (capped in overlay)
	addDemand(util, "HAT", ISMAEL_UNIT_NAME, ALWAYS_HATCH_PRIVATEERS)
-- Hatch always deploys its single V-99 Thunderguard train (capped in overlay)
addDemand(util, "HAT", HATCH_V99_UNIT_NAME, ALWAYS_HATCH_V99)
-- Always deploy some SZ-88 Somniant Bastions where available (capped in overlay)
for divCode, n in pairs(ALWAYS_SZ88_MIN) do
	addDemand(util, divCode, SZ88_UNIT_NAME, n)
end
	-- Hellhounds: baseline minimum presence daily (capped in overlay)
	local hh = ALWAYS_HELLHOUNDS_STD
	if bLevel <= 1 then hh = ALWAYS_HELLHOUNDS_LOW end
	if bLevel >= 3 then hh = ALWAYS_HELLHOUNDS_HIGH end

	for _, divCode in ipairs(HELLHOUND_DUTY_DIVS) do
		addDemand(util, divCode, HELLHOUND_SCOUT_KEY,   hh.scout)
		addDemand(util, divCode, HELLHOUND_SOLDIER_KEY, hh.soldier)
		addDemand(util, divCode, HELLHOUND_SPY_KEY,     hh.spy)
	end

	--------------------------------------------------------------------
	-- Hellhounds: occasional FULL surge (so all units get used sometimes)
	-- Overlay will cap the “used” value to availability automatically.
	--------------------------------------------------------------------
	local maxUScore = 0
	local lastDate = ""
	if #debugRows > 0 then
		lastDate = tostring(debugRows[1].date or "")
		for i=1, #debugRows do
			local u = urgencyScore(debugRows[i].urgency or "")
			if u > maxUScore then maxUScore = u end
		end
	end

	local d, m, y = lastDate:match("(%d+)%s*/%s*(%d+)%s*/%s*(%d+)")
	d, m, y = tonumber(d), tonumber(m), tonumber(y)
	local dayKey = 0
	if d and m and y then
		dayKey = (y * 10000) + (m * 100) + d
	end

	local every = HELLHOUND_FULL_SURGE_EVERY_STD
	if bLevel <= 1 then every = HELLHOUND_FULL_SURGE_EVERY_LOW end
	if bLevel >= 3 then every = HELLHOUND_FULL_SURGE_EVERY_HIGH end

	if HELLHOUND_SURGE_URGENCY_BONUS and maxUScore >= 3 then
		every = math.max(2, math.floor(every / 2))
	end

	if dayKey > 0 and every > 0 and (dayKey % every == 0) then
		for _, divCode in ipairs(HELLHOUND_DUTY_DIVS) do
			addDemand(util, divCode, HELLHOUND_SCOUT_KEY,   HELLHOUND_FULL_SURGE_DEMAND)
			addDemand(util, divCode, HELLHOUND_SOLDIER_KEY, HELLHOUND_FULL_SURGE_DEMAND)
			addDemand(util, divCode, HELLHOUND_SPY_KEY,     HELLHOUND_FULL_SURGE_DEMAND)
		end
	end

	-- Council of Kings Division: always use at least 20% of available CKD forces.
	applyDivisionMinimumUseFloor(baseText, util, "CKD", CKD_MIN_FORCE_USE_PCT)

	return util, debugRows, true
end

--------------------------------------------------------------------------------
-- Overlay highlights (NEVER exceed availability)
--------------------------------------------------------------------------------
local function overlayTable(baseText, util)
	local out={}
	local inMain=false

	for line in tostring(baseText):gmatch("([^\n]*)\n?") do
		if line:match("^%{%|") then
			inMain=true
			push(out,line)
		elseif inMain and line:match("^|%}%s*$") then
			inMain=false
			push(out,line)
		elseif inMain and line:match("^|%s") and line:find("||",1,true) then
			local parts = mw.text.split(line:gsub("^|%s*",""), "%s*||%s*")
			if #parts == 14 then
				local unitName = unitKeyFromCell(parts[2])

				local divCols, availByIdx = {}, {}
				for i=4,14 do
					local colKey = COL_KEYS[i-3]
					local defaultDiv = COL_TO_DIV[colKey]
					local divCode = divOverrideForUnit(unitName, colKey, defaultDiv)

					local avail = toNumber(parts[i]) or 0
					availByIdx[i]=avail
					if divCode then
						divCols[divCode]=divCols[divCode] or {}
						divCols[divCode][#divCols[divCode]+1] = { idx=i, avail=avail }
					end
				end

				local usedByIdx = {}

				for divCode, cols in pairs(divCols) do
					local demand = (util[divCode] and util[divCode][unitName]) or 0
					if demand > 0 then
						local totalAvail = 0
						for _,c in ipairs(cols) do totalAvail = totalAvail + (c.avail or 0) end
						local usedTotal = math.min(demand, totalAvail)
						-- Hard cap the whitelisted Baratar weapons at 20% (baseline + event-driven combined)
						if BARATAR_WEAPON_WHITELIST[unitName] then
							local cap = math.floor(totalAvail * BARATAR_WEAPON_CAP_PCT)
							if usedTotal > cap then usedTotal = cap end
						end

						-- Hard cap Baratar personnel usage at 20% (future-proof)
						if unitName == BARATAR_ACTIVE_KEY or unitName == BARATAR_RESERVE_KEY then
							local cap = math.floor(totalAvail * BARATAR_FORCE_CAP_PCT)
							if usedTotal > cap then usedTotal = cap end
						end
						-- Hard cap Baratar small arms/munitions usage at 20% (baseline + event-driven combined)
						if isBaratarUnitName(unitName) then
							local roles = rolesForUnit(unitName)
							if roles and roles.small_arms then
								local cap = math.floor(totalAvail * BARATAR_WEAPON_CAP_PCT)
								if usedTotal > cap then usedTotal = cap end
							end
						end



						if totalAvail>0 and usedTotal>0 then
							local assigned=0
							for _,c in ipairs(cols) do
								local share = math.floor(usedTotal * c.avail / totalAvail)
								if share > c.avail then share = c.avail end
								usedByIdx[c.idx] = share
								assigned = assigned + share
							end

							local rem = usedTotal - assigned
							if rem > 0 then
								table.sort(cols, function(x,y)
									local cx=(x.avail or 0) - (usedByIdx[x.idx] or 0)
									local cy=(y.avail or 0) - (usedByIdx[y.idx] or 0)
									return cx > cy
								end)
								local safety=0
								while rem>0 and safety<2000 do
									safety=safety+1
									local progressed=false
									for _,c in ipairs(cols) do
										if rem<=0 then break end
										local cap=(c.avail or 0) - (usedByIdx[c.idx] or 0)
										if cap>0 then
											usedByIdx[c.idx]=(usedByIdx[c.idx] or 0)+1
											rem=rem-1
											progressed=true
										end
									end
									if not progressed then break end
								end
							end
						end
					end
				end

				for i=4,14 do
					local avail=availByIdx[i] or 0
					local used=usedByIdx[i] or 0
					if used>avail then used=avail end
					if avail>0 and used>0 then
						local pct=used/avail; if pct>1 then pct=1 end
						local bg=cellColor(pct)
						local usedText=string.format("<br/><span style=\"font-size:85%%;\"><b>used:</b> %s</span>", fmt(used))
						parts[i]=string.format("style=\"background:%s; text-align:center;\" | %s%s", bg, parts[i], usedText)
					end
				end

				push(out, "| " .. table.concat(parts, " || "))
			else
				push(out,line)
			end
		else
			push(out,line)
		end
	end

	push(out,"")
	push(out,"<div style=\"margin-top:0.5em; font-size:90%;\">")
	push(out,"<b>Daily overview:</b> usage is a capped estimate derived from War League daily ops plus routine training/maintenance/unpublished activity.")
	push(out,"<br/><b>Hard rule:</b> used never exceeds availability in any cell or across a shared ops-division group.")
	push(out,"<br/><b>Hatch Ministry:</b> always attempts to deploy 30 <i>Ismael Class Privateer</i> commissions (capped to what exists).")
push(out,"<br/><b>Council of Kings Division:</b> always maintains at least 20% utilization of available CKD forces, capped to inventory.")
	push(out,"<br/><b>Small arms:</b> selected only when appropriate to the day’s operations (VBSS, security, unrest, etc.).")
	push(out,"<br/><b>Baratar militia:</b> Baratar personnel rows are inventory-only; Baratar weapons (including grenades) are estimated as used only during internal/domestic operations.")
	push(out,"</div>")

	return table.concat(out,"\n")
end


--------------------------------------------------------------------------------
-- Division unit list output (per-division used totals)
--------------------------------------------------------------------------------

-- Order you want divisions to print in (matches your overlay div codes)
local DIV_ORDER = { "VAE","ALP","ODF","HFA","HAT","NSJ","BNO","OUR","CKD" }

-- Display labels (plain text to avoid broken links)
local DIV_LABEL = {
	VAE = "Vaeringheim",
	ALP = "Alpazkigz",
	ODF = "Odiferia",
	HFA = "Haifan Bassaridia",
	HAT = "Hatch Ministry",
	NSJ = "New South Jangsong",
	BNO = "Bassaridian Normark",
	OUR = "Ouriana",
	CKD = "Council of Kings",
}

local function listLabelForRow(unitKey, rawUnitCell)
	-- Baratar personnel keys are normalized to "Baratar Active Units"/"Baratar Reserve Units"
	if tostring(unitKey):match("^Baratar%s+") then
		local base = tostring(unitKey):gsub("^Baratar%s+","")
		return base .. " (Baratar)"
	end

	-- For everything else, use the human display name from the unit cell
	local name = extractUnitName(rawUnitCell)

	-- If the unit cell itself contains the Baratar icon, label it as (Baratar)
	-- (covers Baratar weapons rows)
	if isBaratarCell(rawUnitCell) then
		return name .. " (Baratar)"
	end

	return name
end

-- Compute USED TOTAL per division for each row, using the same capping rules as overlayTable().
-- Returns: usedTotals[divCode][unitKey] = usedTotal
local function computeUsedTotalsByDivision(baseText, util)
	local usedTotals = {}
	for _,div in ipairs(DIV_ORDER) do usedTotals[div] = {} end

	for line in tostring(baseText):gmatch("[^\n]+") do
		if line:match("^|%s") and line:find("||", 1, true) then
			local parts = mw.text.split(line:gsub("^|%s*", ""), "%s*||%s*")
			if #parts == 14 then
				local unitKey = unitKeyFromCell(parts[2])

				-- Build availability groups by divCode (same grouping logic as overlay)
				local divAvail = {}
				for i = 4, 14 do
					local colKey = COL_KEYS[i-3]
					local defaultDiv = COL_TO_DIV[colKey]
					local divCode = divOverrideForUnit(unitKey, colKey, defaultDiv)
					local avail = toNumber(parts[i]) or 0
					if divCode then
						divAvail[divCode] = (divAvail[divCode] or 0) + avail
					end
				end

				-- For each divCode, cap demand to availability (and apply Baratar caps)
				for divCode, totalAvail in pairs(divAvail) do
					local demand = (util[divCode] and util[divCode][unitKey]) or 0
					if demand > 0 and totalAvail > 0 then
						local usedTotal = math.min(demand, totalAvail)

						-- Hard cap whitelisted Baratar weapons at 20% (baseline + event-driven combined)
						if BARATAR_WEAPON_WHITELIST[unitKey] then
							local cap = math.floor(totalAvail * BARATAR_WEAPON_CAP_PCT)
							if usedTotal > cap then usedTotal = cap end
						end

						-- Hard cap Baratar personnel usage at 20%
						if unitKey == BARATAR_ACTIVE_KEY or unitKey == BARATAR_RESERVE_KEY then
							local cap = math.floor(totalAvail * BARATAR_FORCE_CAP_PCT)
							if usedTotal > cap then usedTotal = cap end
						end

						-- Future-proof: cap any Baratar small arms/munitions at 20%
						if isBaratarUnitName(unitKey) then
							local roles = rolesForUnit(unitKey)
							if roles and roles.small_arms then
								local cap = math.floor(totalAvail * BARATAR_WEAPON_CAP_PCT)
								if usedTotal > cap then usedTotal = cap end
							end
						end

						if usedTotal > 0 then
							usedTotals[divCode] = usedTotals[divCode] or {}
							usedTotals[divCode][unitKey] = (usedTotals[divCode][unitKey] or 0) + usedTotal
						end
					end
				end
			end
		end
	end

	return usedTotals
end

local function buildDivisionLists(baseText, usedTotals, filterDiv)
	local out = {}
	local currentCategory = nil

	-- We will store, per category, the units that appear in table order:
	-- catOrder[#] = categoryName
	-- catUnits[categoryName] = { {unitKey=..., label=...}, ... }
	local catOrder, catUnits = {}, {}

	for line in tostring(baseText):gmatch("[^\n]+") do
		-- Category header rows look like: ! colspan="14" ... | '''Personnel & readiness'''
		if line:match("^!%s*colspan") and line:find("'''", 1, true) then
			local cat = line:match("'''(.-)'''")
			cat = trim(cat or "")
			if cat ~= "" then
				currentCategory = cat
				if not catUnits[currentCategory] then
					catUnits[currentCategory] = {}
					catOrder[#catOrder+1] = currentCategory
				end
			end

		-- Unit rows start with "| " and have 14 parts
		elseif line:match("^|%s") and line:find("||", 1, true) then
			local parts = mw.text.split(line:gsub("^|%s*", ""), "%s*||%s*")
			if #parts == 14 and currentCategory then
				local unitKey = unitKeyFromCell(parts[2])
				local label = listLabelForRow(unitKey, parts[2])

				-- Keep the row in category order once (the usedTotals decide whether it prints)
				catUnits[currentCategory][#catUnits[currentCategory]+1] = {
					unitKey = unitKey,
					label = label
				}
			end
		end
	end

	-- Helper to emit one division
	local function emitDivision(divCode)
		local divTitle = (DIV_LABEL[divCode] or divCode) .. " (" .. divCode .. ")"
		out[#out+1] = "=== " .. divTitle .. " ==="
		out[#out+1] = ""

		for _,cat in ipairs(catOrder) do
			local any = false
			for _,row in ipairs(catUnits[cat]) do
				local used = usedTotals[divCode] and usedTotals[divCode][row.unitKey] or 0
				if used and used > 0 then
					any = true
					break
				end
			end

			if any then
				out[#out+1] = "==== " .. cat .. " ===="
				out[#out+1] = ""
				for _,row in ipairs(catUnits[cat]) do
					local used = usedTotals[divCode] and usedTotals[divCode][row.unitKey] or 0
					if used and used > 0 then
						out[#out+1] = string.format("* %s — '''used: %s'''", row.label, fmt(used))
					end
				end
				out[#out+1] = ""
			end
		end
	end

	if filterDiv and filterDiv ~= "" then
		emitDivision(filterDiv)
	else
		for _,divCode in ipairs(DIV_ORDER) do
			emitDivision(divCode)
		end
	end

	return table.concat(out, "\n")
end

-- Public: prints a list like your example, for one division or all divisions.
-- Invoke:
--   {{#invoke:WarLeagueUnitUtilizationBVR|divisionList}}                -> all divisions
--   {{#invoke:WarLeagueUnitUtilizationBVR|divisionList|div=NSJ}}        -> one division
--   {{#invoke:WarLeagueUnitUtilizationBVR|divisionList|div=HFA}}        -> one division
function p.divisionList(frame)
	local args = (frame and frame.args) or {}

	local base = normalizeWikitextTable(RAW_BASE_TABLE)

	-- Build pools + utilization exactly like render() does
	local allUnits = collectAllUnitsFromBase(base)
	normalizeToeTemplates(allUnits)
	local pools = buildPools(allUnits)
	local util = select(1, computeUtilization(args, pools))

	local usedTotals = computeUsedTotalsByDivision(base, util)
	local filterDiv = trim(args.div or args.division or "")

	return buildDivisionLists(base, usedTotals, filterDiv)
end

--------------------------------------------------------------------------------
-- Public entrypoint
--------------------------------------------------------------------------------
function p.render(frame)
	local args = (frame and frame.args) or {}

	local base = normalizeWikitextTable(RAW_BASE_TABLE)

	-- TOE coverage
	local allUnits = collectAllUnitsFromBase(base)
	normalizeToeTemplates(allUnits)

	-- pools from embedded inventory (so all units can be reached)
	local pools = buildPools(allUnits)

	-- compute usage
	local util, debugRows, ok = computeUtilization(args, pools)
	if not ok then
		return base .. "\n<div style=\"margin-top:0.5em; font-size:90%;\"><b>Note:</b> Ops module unavailable; showing base table only.</div>"
	end

	local rendered = overlayTable(base, util)

	-- debug
	if tostring(args.debug or ""):lower() == "yes" then
		local max = tonumber(args.debugrows) or 8
		local dbg={}
		push(dbg,"")
		push(dbg,"<div style=\"margin-top:0.75em; font-size:90%;\">")
		push(dbg,"<b>Debug (proof of source):</b> Parsed rows from <i>Module:WarLeagueDivisionOpsBVR</i> (Operation/Urgency/Impact + force cells).")
		push(dbg,"<ul>")
		for i=1, math.min(max, #debugRows) do
			local r=debugRows[i]
			push(dbg, string.format(
				"<li><b>%s</b> | %s | %s | <b>%s</b><br/>Impact: %s<br/>Op: %s<br/>G: %s<br/>N: %s<br/>A: %s</li>",
				tostring(r.date or ""), tostring(r.division or ""), tostring(r.city or ""),
				tostring(r.urgency or ""), tostring(r.impact or ""), tostring(r.op or ""),
				tostring(r.ground or ""), tostring(r.naval or ""), tostring(r.air or "")
			))
		end
		push(dbg,"</ul></div>")
		rendered = rendered .. table.concat(dbg,"\n")
	end

	return rendered
end

return p