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(" ", " "):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(" "," ")
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