Buildings and Upgrades
Buildings are the passive producers (Cookie Clicker's tiers, Cursor → Idleverse, each with a basePrice and a baseValue of points per second); upgrades are one-shot purchases whose effects multiply building or mouse power. Both are bought from the store drawer on the left; bought items appear in the inventory drawer on the right.
How it works
The store list (ClickerModelStoreList) fetches BuildingMap and UpgradeMap through the clicker router on setup and hands them to the building/upgrade stores. Buildings are always visible; upgrades appear only when unlocked and disappear once bought, sorted by price.
- Unlock conditions — every upgrade carries
UnlockCondition[]: own at least N of a building (Target.Building+ amount), or own another upgrade (Target.Upgrade).unlockedUpgradesin the upgrade store evaluates all conditions against the save. - Price scaling — buildings cost
trunc(basePrice * 1.15 ** owned)(PRICE_GROWTHinservices/clicker/constants.ts), the idle-game-standard exponential curve Cookie Clicker'sbasePricetiers were designed for, recomputed from the owned count; upgrades have fixed prices. Selling does not exist. - Buying —
createBoughtBuilding/createBoughtUpgradepush into the save'sboughtBuildings/boughtUpgradesarrays and decrement points. Affordability is one reading,checkIsAffordablein the point store: the rows colour their price by it and the Buy button disables on it, and the store actions refuse a purchase it rejects — and an upgrade already bought — so no caller has to remember the check. An item that leaves its list while its details are open, as an upgrade does once bought, closes the popover with it. - Bulk buy — a ×1/×10/×100 quantity toggle in the store header (
BUY_QUANTITIES,buyQuantityin the building store).getBuildingPriceForQuantitysums the per-unit price overowned … owned + quantity - 1— summing the loop stays exact under any price formula — andcreateBoughtBuildingapplies the whole batch as one mutation (one save trigger). Upgrades are unaffected (they're one-shot). - Stats — each bought building's list item renders markdown stat lines (per-unit power, share of total production, lifetime
producedValue) computed from the effect engine.
Numbers render through formatNumberLong (thousand, million, … built by prefix × suffix composition up to nonagintillion).
Data model
Content lives in shared/assets/clicker/data/ as constant maps, typed by satisfies at compile time and covered by co-located tests for the ordering the type system can't express:
BuildingMap—BuildingperBuildingId:basePrice,baseValue, flavor text (with compiled variables).<Building>UpgradeMap— one map per building, merged intoUpgradeMapviamergeObjectsStrict(which fails the build on id collisions). AnUpgradehasprice,effects: Effect[],unlockConditions, description + flavor text. Only Cursor and Grandma have upgrade lines today (including Cursor's cross-building "Thousand Fingers" tier); giving the other buildings one is deferred on icon art.
Procedures
| Procedure | Auth | Input | Purpose |
|---|---|---|---|
clicker.readBuildingMap | rate-limited | — | serve the building definitions |
clicker.readUpgradeMap | rate-limited | — | serve the upgrade definitions |
Key files
Paths relative to apps/web.
| File | Role |
|---|---|
shared/assets/clicker/data/BuildingMap.ts | building definitions |
shared/assets/clicker/data/upgrades/UpgradeMap.ts | merged upgrade definitions |
app/services/clicker/building/getBuildingPrice.ts | the exponential price curve |
app/store/clicker/building.ts | prices, per-building power, stats, buying |
app/store/clicker/upgrade.ts | unlock evaluation, buying |
app/store/clicker/point.ts | points in and out, and whether a price can be paid |
app/components/Clicker/Model/Store/List.vue | store panel; fetches + initializes both maps |
app/components/Clicker/Model/Store/Header.vue | ×1/×10/×100 buy-quantity toggle |
app/components/Clicker/Model/ItemList.vue | a group's rows, and the one popover a pressed row opens |
app/components/Clicker/Model/Building/Detail.vue | a building's stats + Buy, in that popover |
app/components/Clicker/Model/Upgrade/Detail.vue | an upgrade's description + Buy, in that popover |
app/services/clicker/formatNumberLong.ts | long-word number notation (formatNumberLong) |
Notes
- Prices are always derived from the owned count and never stored, so retuning the curve needs no save migration — the next purchase simply costs the new amount.
- The Cursor building's flavor text says it autoclicks every 10 seconds, but mechanically it is a plain 0.1/s producer like every other building.
- Upgrade icons are per-upgrade PNGs keyed by upgrade id (
app/assets/clicker/icons/upgrades/<building>/), so an upgrade without art renders an empty icon slot. This is what gates the other buildings' lines — see all-building upgrades.