An upgrade is an UpgradeDefinition: a slot, a rarity, a coin price, tags and a list of StatModifiers. Add one
Upgrade(...) line in DefaultContent.Build() and it can show up in the shop. No other code is needed unless it
needs a new stat (Add a Stat) or a bat model.
Steps
Pick the effect. Every effect is a stat modifier (see Stats System). StatModifier.Percent(stat, 0.1f) is
+10%; StatModifier.Add(stat, 2f) adds a flat 2. Final value = (base + adds) * (1 + percents).
Add the line in Assets/_Project/Scripts/Data/DefaultContent.cs, in the // ---------- Upgrades ----------
block. The helper is Upgrade(id, name, slot, rarity, cost, description, tags, params modifiers):
Choose the slot (EquipmentSlot): Bat, Glove and Cleats hold one item each, so buying one replaces the
one there (RunState.AddUpgrade). Charm stacks: you can own several, even copies of the same charm.
Choose the rarity (Rarity). It sets how often the shop offers it (see Numbers).
Tag it for synergies. Tags are plain strings that must match a SynergyDefinition.tag exactly
("Power", "Timing", "Contact", "Greed", "Green" exist today). See Add a Synergy.
Optional: lock it behind meta points. Keep the returned object and set the unlock fields, as the Pocket
Metronome does:
Locked upgrades are bought in the PERKS popup of the top bar (UI/LobbyNav.DrawPerks). Only unlocked ones
enter the shop pool (MetaProgression.UpgradePool).
Optional: a batter starts with it. Put its id in a batter’s startingUpgradeIds (see Add a Batter).
Optional: a bat look. A Bat upgrade shows the model in Resources/BatModelLibrary.asset whose entry
upgradeId matches its id; with no entry it shows the library’s defaultBat.
Index: nothing to do. The INDEX lists every upgrade under EQUIPMENT, in the sub tab for its slot (BATS, CLEATS,
GLOVES, CHARMS), Common first, with its modifiers and tags (IndexCatalog.Build). See Index Screen.
Numbers
What
Value
Where to tune
Shop weight, Common
6
ShopService.RarityWeight
Shop weight, Rare
3
ShopService.RarityWeight
Shop weight, Epic
1
ShopService.RarityWeight
Offers per shop visit
3
RunConfig.shopOfferCount
Default price
5 coins
UpgradeDefinition.cost
Price at Medium / Hard
x1.25 / x1.5, rounded up
Difficulties.ShopPrice, RunState.PriceOf
Check it works
Press Play, start a run and open the shop. To skip the luck, press F1 (dev menu) → GIVE AN UPGRADE, then
look at the ATTRIBUTES list in the same menu (changed values turn yellow). See Dev Menu and Shortcuts.
A locked upgrade: F1 → add points, open PERKS, buy it (the button shows the points gem and the price), then
check it appears in shops.
The build panel on the map screen lists owned upgrades, active synergies and every stat (UI/BuildPanel).
Gotchas
Ids are forever. Saved runs store owned upgrade ids (RunSaveData.upgradeIds) and meta unlocks store ids in
SaveData.unlockedIds. Renaming an id silently drops it from old saves.
One id namespace.unlockedIds is shared by batters, upgrades and cosmetics, so an upgrade id must not equal a
batter or cosmetic id.
An owned non-charm never shows again in that run’s shop; charms always can, and each copy counts toward its tags.
If the project has an exported Resources/ContentDatabase.asset, the game loads that instead of DefaultContent.
Today none is committed, so DefaultContent is what runs. After an export, add upgrades with
Create → Home Run Derby → Upgrade and drag them into the database’s upgrades list.
Co-op: each player has their own shop, so a new upgrade needs no protocol bump on its own. The bat upgrade id is sent
to spectators (SpectateMessages.Begin) for the bat look; peers need the same content.