Files
A3Rebalancer/GENERATE_PATCHES_EXPLAINED.md
Samuele Lorefice 83d2cb6c52 Initial commit: ArmADump ammo/armor rebalance tool
- SQF extraction scripts (ammo, armor, weapons, magazines) with ACE3 properties
- Python pipeline: extract_csvs, split_by_mod, generate_patches
- Generates CfgPatches config.cpp normalizing non-RHS values to RHS baseline
- Documentation: REFERENCE.md, GENERATE_PATCHES_EXPLAINED.md, AGENTS.md
2026-07-20 18:27:33 +02:00

12 KiB

Generate Patches — How It Works

This document explains the logic behind generate_patches.py, the script that produces the rebalance config.cpp.


Purpose

Many Arma 3 mods define ammo and armor with values that don't match RHS standards. This script:

  1. Reads the extracted CSV data for every installed mod
  2. Compares each mod's values against RHS as the baseline
  3. Generates a config.cpp mod that overrides non-RHS values to match RHS equivalents

The result is a single drop-in mod that normalizes your entire mod list to RHS balance.


Overview of the Process

Per-mod CSV files (data/)
        |
        v
 +-------------------+
 | Load baseline     |  Reads RHS + vanilla + RHS sub-mods
 | (RHS / vanilla)   |  as the "correct" reference values
 +-------------------+
        |
        v
 +-------------------+
 | Classify every    |  Each ammo/armor item is sorted into a
 | entry into tiers  |  "tier" group based on caliber or protection level
 +-------------------+
        |
        v
 +-------------------+
 | Build baseline    |  For each tier, pick one representative RHS entry
 | lookup table      |  to use as the reference values
 +-------------------+
        |
        v
 +-------------------+
 | Compare each mod  |  For every non-RHS entry, look up its tier's
 | against baseline  |  RHS baseline and record any differences
 +-------------------+
        |
        v
 +-------------------+
 | Generate          |  Write a config.cpp that overrides every
 | config.cpp        |  differing value to match the RHS baseline
 +-------------------+
        |
        v
 output/config.cpp   (the rebalance mod)
 output/unmatched.csv (items with no RHS equivalent)

Step 1: Loading Baseline Data

The script loads CSVs from these folders as the baseline (the "correct" values):

Folder Source
_rhs RHS AFRF (Russian equipment)
_rhsusf RHS USMC (American equipment)
_rhsgref RHS GREF (generic/other factions)
_rhssaf RHS SAF (Serbian equipment)
_vanilla Arma 3 base game (BI)
_ace ACE3 mod (ballistic overrides)

All entries from these mods are pooled together. When two entries exist in the same tier, RHS entries are preferred over vanilla.


Step 2: Classification — Putting Items Into Tiers

Every ammo and armor item is classified into a tier. Items in the same tier are considered equivalent — a 5.56mm round from mod A should have similar stats to a 5.56mm round from RHS.

Ammo Classification

Ammo is classified by two properties: a caliber tier (the type of round) and a subtype (ball, AP, tracer, etc.).

How Caliber Tier Is Determined

The script checks the ammo class name against a list of patterns. The first pattern that matches wins. This is done by name, not by the numeric caliber value, because Arma's caliber property is a penetration coefficient, not the actual bullet caliber.

Pattern in class name Tier Typical RHS values (hit / caliber / speed)
9x19, 9mm, 9x18 pistol_9mm 5 / 0.4 / 370
45acp, .45 pistol_45acp 6 / 0.5 / 260
57x28, 5.7 smg_57x28 4 / 0.3 / 390
46x30, 4.6 smg_46x30 4 / 0.3 / 375
545x39, 5.45 rifle_545x39 9 / 0.65 / 900
556x45, 5.56 rifle_556x45 9 / 0.87 / 920
762x39 rifle_762x39 11 / 1.2 / 730
762x51, .308 rifle_762x51 11.6 / 1.6 / 800
762x54 rifle_762x54 11.6 / 1.8 / 830
300blk rifle_300blk (no RHS baseline)
338, 338lapua sniper_338 (no RHS baseline)
408, cheytac sniper_408 (no RHS baseline)
127x99, 50bmg, 12.7 hmg_50 22 / 2.8 / 900
145x114, 14.5 hmg_145 28 / 3.5 / 950
20mm cannon_20 30 / 1.2 / 1000
23mm cannon_23 40 / 1.5 / 950
25mm cannon_25 60 / 2.0 / 900
30mm cannon_30 80 / 2.5 / 850
35mm cannon_35 85 / 2.5 / 850
40mm, mk19 cannon_40 100 / 2.5 / 800
mortar, 82mm, 81mm mortar_82 varies
100mm and above gun_heavy varies
12gauge, buckshot shotgun_12g varies
rocket, pg7, rpg rocket varies
missile, titan, hellfire missile varies
grenade, 40mm_he grenade_40mm varies
mine, ied, satchel explosive varies
penetrator, heat penetrator varies

If no name pattern matches, the script falls back to the simulation property:

Simulation Tier
shotBullet (speed < 350) bullet_subsonic
shotBullet (other) bullet_unknown
shotShell + explosive > 0.5 shell_he
shotShell (other) shell_ap
shotMissile missile
shotRocket rocket
shotGrenade grenade_40mm
shotIlluminating, shotSmokeX utility

How Subtype Is Determined

Within each caliber tier, items are further sorted into subtypes by checking the class name for keywords:

Condition Subtype
Speed > 0 and < 350 m/s subsonic
Name contains "ap" ap
Name contains "tracer" ball_tracer
Name contains "match", "sniper", "otm", "mk262", "mk316" match
Name contains "incendiary" or "incen" incendiary
Name contains "hedp" hedp
Name contains "he" (but not "hedp") he
Everything else ball

Armor Classification

Armor is classified by item type (Vest or HeadGear) and a protection tier.

Vest Tiers

Based on the armor_chest value (front plate protection):

Chest Armor Value Tier Examples
0 (no plate) soft Cloth vests, chest rigs
1 - 16 light Plate carrier lite, PASGT vest
17 - 31 medium IOTV, SPC, 6B13
32 - 50 heavy Full battle carrier
50+ eod EOD suits, GL carriers

Helmet Tiers

Based on the armor_head value:

Head Armor Value Tier Examples
0 (no armor) cap Berets, caps
1 - 6 light Cloth helmets, bump helmets
7 - 15 medium PASGT, MICH
16 - 25 heavy ACH, ECH, 6B47
25+ special Specialized heavy helmets

Step 3: Building the Baseline Lookup Table

After all entries are loaded and classified, the script builds a lookup table. For each tier, it picks one representative RHS entry to use as the reference.

Example: For the rifle_556x45 / ball tier, the baseline might be RHS's B_556x45_Ball with:

  • hit = 9
  • caliber = 0.87
  • typicalSpeed = 920
  • airFriction = -0.001033
  • deflecting = 21

Any 5.56mm ball round from any other mod will be compared against these values.


Step 4: Finding Overrides

For each non-RHS mod entry, the script:

  1. Looks up the entry's tier in the baseline table
  2. If the tier exists in the baseline, compares every field
  3. If a field differs and the mod's value is not zero, records a change to the baseline value

What Gets Compared

Ammo fields (14 values):

Field Arma Config Name What It Controls
hit hit Direct damage on impact
indirect_hit indirectHit Splash damage
indirect_hit_range indirectHitRange Splash radius (meters)
caliber caliber Penetration coefficient
typical_speed typicalSpeed Muzzle velocity (m/s)
air_friction airFriction Drag coefficient
deflecting deflecting Min ricochet angle (degrees)
explosive explosive 0 = kinetic, 1 = explosive
ace_caliber ACE_caliber Actual bullet diameter (mm)
ace_bullet_length ACE_bulletLength Bullet length (mm)
ace_bullet_mass ACE_bulletMass Bullet mass (grams)
ace_drag_model ACE_dragModel Drag curve (1=G1, 7=G7, etc.)
ace_transonic ACE_transonicStabilityCoef Transonic stability (0-1)
ace_mv_var ACE_muzzleVelocityVariationSD Velocity spread (%)

Plus 4 ACE array fields compared as text:

  • ACE_ballisticCoefficients[] — drag coefficient at different velocities
  • ACE_velocityBoundaries[] — velocity thresholds for BC changes
  • ACE_muzzleVelocities[] — muzzle velocity per barrel length
  • ACE_barrelLengths[] — barrel lengths for velocity interpolation

Armor fields (16 values):

For each body part (Head, Neck, Chest, Diaphragm, Abdomen, Body, Arms, Legs):

  • armor — additional armor points
  • passThrough — 0 = full block, 1 = no protection

What Does NOT Get Overridden

  • Fields where the mod's value is zero (treated as "not defined, keep it")
  • passThrough values where both the mod and RHS baseline are 1.0 (no change for unarmored areas)

Step 5: Generating config.cpp

The output is a standard Arma 3 config.cpp with three sections:

CfgPatches (Dependency Declaration)

Each mod that has overrides gets a dependency entry:

class CfgPatches {
    class armadump_afou {
        units[] = {};
        weapons[] = {};
        requiredAddons[] = {"afou"};
    };
};

This tells Arma to load our overrides after the original mod.

CfgAmmo (Ammo Overrides)

class CfgAmmo {
    class B_afou_556x45_Ball {
        airFriction = -0.001033;
        caliber = 0.87;
        deflecting = 21;
        hit = 9;
        typicalSpeed = 920;
    };
};

Only fields that differ from RHS are listed. Fields not listed keep their original mod values.

CfgWeapons (Armor Overrides)

class CfgWeapons {
    class MyMod_Vest {
        class ItemInfo {
            class HitpointsProtectionInfo {
                class Chest {
                    armor = 24;
                    passThrough = 0.5;
                };
                class Abdomen {
                    armor = 16;
                    passThrough = 0.6;
                };
            };
        };
    };
};

Only body parts with differing armor values are listed.


Step 6: Unmatched Items

Items that are classified into a tier with no RHS baseline equivalent are written to output/unmatched.csv for manual review.

Common reasons an item is unmatched:

Reason Example
Caliber has no RHS equivalent .300 Blackout, .57x28mm, .338 Lapua
Subtype has no RHS equivalent Tracer variants when only ball exists in RHS
Simulation-based fallback tier Unknown simulation type

These items are not included in the generated config.cpp. They retain their original mod values.


What You End Up With

After running the script:

  1. output/config.cpp — Drop this into an @mod/addons/ folder and load it in Arma. It silently overrides every non-RHS ammo and armor value to match RHS balance.

  2. output/unmatched.csv — A spreadsheet of items that couldn't be matched. Review these to decide if they need manual adjustment.

  3. No changes to original mods — The script only reads data. Original mod files are untouched.


Example: Tracing One Entry

Taking the AFou mod's 5.56mm ball round as an example:

  1. Classification: Class name B_afou_556x45_Ball matches pattern 556x45 -> tier rifle_556x45. No "tracer", "ap", etc. in name -> subtype ball.

  2. Baseline lookup: Tier rifle_556x45 / ball maps to RHS's B_556x45_Ball:

    • hit = 9, caliber = 0.87, typicalSpeed = 920, airFriction = -0.001033, deflecting = 21
  3. Comparison: AFou's values differ on airFriction, caliber, deflecting, hit, typicalSpeed.

  4. Override generated: The config.cpp sets those 5 fields to RHS values. All other fields (ACE properties, cost, etc.) keep AFou's original values.


Limitations

  • Tier-based, not per-class matching: All 5.56mm ball rounds are normalized to the same RHS reference. If a mod intentionally makes a round weaker or stronger (e.g., training ammo), it will be overwritten.
  • No threshold filtering: Even a difference of 0.001 generates an override. There is no minimum difference filter.
  • First RHS entry wins: If multiple RHS entries exist in the same tier, the first one encountered is used. This is usually fine since RHS values within a tier are consistent.
  • Unmatched items skipped: Calibers without an RHS equivalent (like .300 BLK) are left untouched.