Battle Weight Correction
Status: Server-staged rebalance — table is live on the server (BattleWeightCorrectionDataTable, 8 entries, server-only) but the in-game client still uses the old hardcoded values in WeightTableHelper. Effectively a preview of the next gameplay-balance patch.
Discovered during the 1.1.1.0 server-canonical mining pass (one of the 3 server-only tables alongside ExoticEvent + ExoticShop).
What it controls
Loot-tier weighting when the game generates an equipment drop or rune drop during exploration. The system biases what rarity (C/B/A/S/SS) of item shows up based on three independent correction sources that all stack additively.
Formula (decompiled MainScripts.dll, lines 35087-35095, 35184-35192, 47855-47862):
final_weight(loot_step) = base_Weight(loot_item) × max(0, 1 + ΔD + ΔE + ΔP)
where:
ΔD = difficultyCorrection = DifficultySpec.FixC/B/A/S[map.difficulty][loot_step]
ΔE = eventCorrection = battle_tier × loot_step (this table, battleX rows)
ΔP = prefixCorrection = enemy_prefix_tier × loot_step (this table, prefixX rows)
The 3 corrections are summed, clamped to ≥ 0, then multiplied into the item's Weight field. WeightedRandom picks the actual drop from the resulting list.
Table contents
Pair-structured: 4 rows for the battle/event tier axis + 4 rows for the monster prefix tier axis. Each row spells out the modifier for each of the 5 loot tiers (C / B / A / S / SS).
battleX rows — event/battle tier × loot tier
The "battle tier" is the rarity step of the current encounter (event step). Read row = your current battle's tier, column = candidate drop's tier:
| battle ↓ \ loot → | C | B | A | S | SS |
|---|---|---|---|---|---|
| battleC | 0 | 0 | −0.5 | −1 | 0 |
| battleB | 0 | 0 | 0 | −0.5 | 0 |
| battleA | −0.5 | 0 | 0 | 0 | 0 |
| battleS | −1 | −0.5 | 0 | 0 | 0 |
Reading guide: - In a C-tier battle: S-loot is forced to zero weight (−1). A-loot at half. C/B unaffected. → You only get junk drops. - In an S-tier battle: C-loot is forced to zero (−1). B-loot at half. A/S unaffected. → You only get top-tier drops. - Symmetric ceiling-AND-floor design. SS column is always 0 (no battle ever boosts/blocks SS — SS-tier loot is presumably gated elsewhere).
prefixX rows — monster prefix tier × loot tier
The "prefix tier" is the rarity step of the prefix modifier rolled onto the killed monster (e.g. Ancient C, Infected A, Astral S — all from PrefixDataTable, 143 entries). Read row = monster's prefix tier, column = candidate drop's tier:
| prefix ↓ \ loot → | C | B | A | S | SS |
|---|---|---|---|---|---|
| prefixC | +0.5 | +0.5 | 0 | 0 | 0 |
| prefixB | 0 | +0.5 | +0.5 | 0 | 0 |
| prefixA | −0.5 | 0 | +0.5 | +0.5 | 0 |
| prefixS | −0.5 | −0.5 | 0 | +1 | 0 |
Reading guide:
- Higher-tier prefixes shift the drop window UP. A prefixS monster doubles S-loot weight (+1) AND penalizes C/B drops (−0.5 each).
- Low-tier prefixes (C/B) only ADD without penalty — easy mode.
- A prefixA monster boosts both A and S equally — convenient for farming S-step gear.
- If the monster has no prefix (role.prefix == ""), the entire ΔP term is 0 (early return in GetPrefixCorrection).
Concrete example
Player is in map difficulty 50 (suppose FixS = -0.4), in a battleA event (current battle tier = A), killing a prefixS Astral monster. A candidate S-tier gear drop with Weight = 10 gets:
ΔD = DifficultySpec[50].FixS = -0.4
ΔE = battleA × S = 0 (row battleA, column S)
ΔP = prefixS × S = +1 (row prefixS, column S)
final = max(0, 1 + -0.4 + 0 + +1) × 10
= max(0, 1.6) × 10
= 16
vs. an A-tier gear (Weight = 100):
ΔD = DifficultySpec[50].FixA = ? (read from DifficultySpec row 50)
ΔE = battleA × A = 0
ΔP = prefixS × A = 0
final = max(0, 1 + ΔD + 0 + 0) × 100 ≈ 100 (assuming ΔD ≈ 0)
So S-gear's effective weight goes 10 → 16 (1.6×) while A-gear stays flat — the Astral prefix concentrates rolls into the higher tier.
What's NEW vs. the hardcoded baseline
The live game client doesn't actually consume BattleWeightCorrectionDataTable yet. WeightTableHelper (decompile lines 89268-89345) holds hardcoded 2D float arrays that are still the live values:
public static float[,] eventStepWeightTable = new float[4, 5]
{
{ 0f, -0.7f, -0.9f, -1f, 0f }, // C battle (old)
{ -0.5f, 0f, -0.7f, -0.9f, 0f }, // B battle (old)
{ -0.5f, -0.5f, 0f, -0.7f, 0f }, // A battle (old)
{ -0.5f, -0.5f, -0.5f, 0f, 0f } // S battle (old)
};
public static float[,] prefixWeightTable = new float[4, 5]
{
{ 0.5f, 0.5f, 0f, -0.5f, 0f }, // C prefix (old)
{ -0.5f, 0.5f, 0.5f, 0f, 0f }, // B prefix (old)
{ -0.5f, 0f, 0.5f, 0.5f, 0f }, // A prefix (old)
{ -0.5f, -0.5f, 0.5f, 0.5f, 0f } // S prefix (old)
};
Diff: battleX rows
| C / loot | B / loot | A / loot | S / loot | |
|---|---|---|---|---|
| C battle | 0 → 0 | −0.7 → 0 | −0.9 → −0.5 | −1 → −1 |
| B battle | −0.5 → 0 | 0 → 0 | −0.7 → 0 | −0.9 → −0.5 |
| A battle | −0.5 → −0.5 | −0.5 → 0 | 0 → 0 | −0.7 → 0 |
| S battle | −0.5 → −1 | −0.5 → −0.5 | −0.5 → 0 | 0 → 0 |
Direction:
- Old = one-way ceiling. Low-tier battles ONLY suppressed high-tier loot.
- New = bidirectional. S-tier battles now ALSO zero-out C-tier junk (-0.5 → -1). Late-game becomes less spammy with garbage drops.
- A/B-tier battles relax the suppression on adjacent-tier loot (-0.7 → 0, off-by-one diagonal becomes flat). → A wider distribution of drop tiers across mid-game.
Diff: prefixX rows
| C / loot | B / loot | A / loot | S / loot | |
|---|---|---|---|---|
| C prefix | +0.5 → +0.5 | +0.5 → +0.5 | 0 → 0 | −0.5 → 0 |
| B prefix | −0.5 → 0 | +0.5 → +0.5 | +0.5 → +0.5 | 0 → 0 |
| A prefix | −0.5 → −0.5 | 0 → 0 | +0.5 → +0.5 | +0.5 → +0.5 |
| S prefix | −0.5 → −0.5 | −0.5 → −0.5 | +0.5 → 0 | +0.5 → +1 |
Direction: - C/B prefixes no longer apply penalties to off-axis tiers. → More forgiving for early-game low-prefix monsters. - S prefixes drop the A-loot boost entirely (+0.5 → 0) but double the S-loot boost (+0.5 → +1). → S-prefix monsters become the canonical farming target for S gear / runes specifically — they no longer "leak" rolls into the A tier. - Net: rewards are concentrated rather than spread out. Players will recognize S-prefix monsters as "S-tier farm spots" rather than generic loot upgrade.
Where it's read in code
| Decompile line | Caller | Purpose |
|---|---|---|
| 35087 | (equip drop pass 1) | Initial gear drop generation when a chest spawns |
| 35184 | (equip drop pass 2) | Re-roll path if pass 1 fails the filter |
| 35186 | (rune drop) | Rune generation in the same drop event |
| 47855 | (event reward eq) | Event-style equipment reward (slightly different consumer than chest path) |
| 46847, 47335, 47342, 47350, 47358, 47365, 47372, 47379, 47386, 47414, 47534 | only GetDifficultyCorrection |
All non-equip drops (monster, ingredient, lime, battle-event, saga, chest, relic) use ONLY difficulty correction — no event/prefix correction. The 8-row table affects equip + rune drops, not generic loot. |
role.prefix is set on a monster instance during spawn (line 88721) by picking a random PrefixDataItem.IDs from the legal pool. Each prefix has its own Step (C/B/A/S) → drives the prefixX-row lookup.
eventStep for GetEventCorrection is the current battle's tier, supplied by the caller (the event spawner already knows the step it rolled).
Why server pushed it but client doesn't use it
The DTO is fully wired in MainScripts:
// line 185730
public class BattleWeightCorrectionEntry {
public string Type { get; set; }
public float C_Correction { get; set; }
public float B_Correction { get; set; }
public float A_Correction { get; set; }
public float S_Correction { get; set; }
public float SS_Correction { get; set; }
}
// line 189893 — on the parent GameConfig
[JsonProperty("Battle_Weight_Correction", ...)]
public Dictionary<string, BattleWeightCorrectionEntry> Battle_Weight_Correction { get; set; }
…but WeightTableHelper.GetEventCorrection / GetPrefixCorrection read the hardcoded arrays (lines 89328-89344), not the deserialized server dictionary. Conclusion: the next HotUpdate is expected to swap those getters to read from Singleton<DataTableManager>.Self.BattleWeightCorrection.DataTable instead. Timing unknown — no in-game release notes have flagged this rebalance yet (2026-05-15). Worth monitoring on next patch.
Insight for players
When the live switch happens:
- S-tier farm targets shift to S-prefix monsters specifically. Old behavior gave A-prefix and S-prefix equal +0.5 on S loot; new behavior gives only S-prefix the boost (doubled to +1). Old farming routes that targeted A-prefix mobs for S-gear become noticeably worse.
- Late-game stops spawning C-tier junk. S-tier battles will literally never roll C-tier equipment/rune drops anymore (
-1zeroes them). Cleanup of trash inventory pressure. - Mid-game gets more variety. A/B-battles let adjacent-tier loot through more freely (
-0.7 → 0on off-diagonal). Less "always B-loot in B-battles" predictability. - C/B-prefix monsters are still useful early-game. Penalty removal (
-0.5 → 0on lower tier) means low-prefix kills don't cost you potential better drops.
References
data-session/mgg_datamine/BattleWeightCorrectionDataTable.md— 8-row source table (server-only)data-session/intermediate/decompiled_full_mainscripts.cs:35087— main consumer (equip drop)data-session/intermediate/decompiled_full_mainscripts.cs:89268-89345—WeightTableHelper(live hardcoded values + getters)data-session/intermediate/decompiled_full_mainscripts.cs:185730—BattleWeightCorrectionEntryDTO- PrefixDataTable — 143 monster prefixes feeding the
role.prefixfield - DifficultySpecDataTable —
FixC/FixB/FixA/FixSper-difficulty correction supplying the ΔD term