Examples
Examples
Labs, cards, uptime, enemy stats, saves, modules, Effective Paths, and the package glossary.
Lab Costs
Coins and research time for the next levels.
Live
- Coin cost (levels 0 → 10)
- 18.759K
- Research Time
- 15h 45m
10 levels · lab max 99
Code
import { LAB_CATALOG } from 'thetowersdk/data'
import { formatNumberForDisplay } from 'thetowersdk/formatting'
// Find the lab by its display name
const lab = LAB_CATALOG.find((l) => l.name === 'Attack Speed')
// Levels are 0-based in the array; level 10 → index 10
const from = 10
const steps = 10
const next = lab.levels.slice(from, from + steps)
// Each level has a coin cost
const coins = next.reduce((sum, level) => sum + level.cost, 0)
// Format like the game (1.1q, 2.3s, …)
console.log(formatNumberForDisplay(coins))Golden Tower vs Black Hole Uptime
Two ultimate weapons compared on the same terms — duration over cooldown, at the same level.
Live
| Weapon | Duration (s) | Cooldown (s) | Uptime (%) |
|---|---|---|---|
| Golden Tower | 23s | 220s | 10.5% |
| Black Hole | 23s | 120s | 19.2% |
Code
import { uwStoneChartData } from 'thetowersdk/data'
import { estimateBotUptimeFraction } from 'thetowersdk/mechanics'
function uptime(weaponName, level) {
const weapon = Object.values(uwStoneChartData).find((w) => w.name === weaponName)
// Ladders are keyed by level, but not every stat starts at the same level — find, don't index.
const stat = (name) =>
weapon.stats.find((s) => s.name === name).levels.find((l) => l.level === level).value
const duration = stat('Duration') // '23s' — a string with units, as the game writes it
const cooldown = stat('Cooldown') // '220s'
// Pass the raw values through. estimateBotUptimeFraction parses the units itself, and
// throws on a number — stripping the 's' yourself is what breaks it.
return { duration, cooldown, uptime: estimateBotUptimeFraction(duration, cooldown) }
}
const gt = uptime('Golden Tower', 8) // { duration: '23s', cooldown: '220s', uptime: 0.1045… }
const bh = uptime('Black Hole', 8) // { duration: '23s', cooldown: '120s', uptime: 0.1917… }
// Same duration at this level; Black Hole comes back nearly twice as often.
console.log({ gt, bh })Enemy Stats
Base health and damage for a farm tier or tournament league base.
Live
- Enemy Health
- 1.343S
- Enemy Damage
- 110.445T
Code
import {
computeWaveBaseHealth,
computeWaveBaseDamage,
} from 'thetowersdk/mechanics'
import { formatNumberForDisplay } from 'thetowersdk/formatting'
// Farm tier 10, or tournament Copper as tier 1 + tournament:true
const hp = computeWaveBaseHealth({ tier: 10, wave: 4500, tournament: false })
const dmg = computeWaveBaseDamage({ tier: 10, wave: 4500, tournament: false })
console.log(formatNumberForDisplay(hp))
console.log(formatNumberForDisplay(dmg))Read A Save
Sample extractor shapes from a decoded playerInfo.dat (decode runs in Node).
Sample
Decode playerInfo.dat, then pull labs, modules, cards, ultimate weapons, bots,
vault, relics, lifetime stats, and battle reports from the same root:
{
"labs": {
"researchedCount": 142,
"maxedCount": 38,
"bySlug": {
"attack_speed": 45,
"labs_speed": 99
}
},
"modules": {
"owned": 48,
"equipped": {
"cannon": "Dimension Core",
"armor": "Anti-Cube Portal",
"generator": "Galaxy Compressor",
"core": "Sharp Fortitude"
}
},
"cards": {
"unlocked": 62,
"masteriesStarted": 11
},
"ultimateWeapons": {
"unlocked": [
"Golden Tower",
"Black Hole",
"Death Wave",
"Chrono Field"
],
"stonesSpent": 18420
},
"bots": {
"unlocked": [
"Golden Bot",
"Flame Bot",
"Thunder Bot"
],
"medalsSpent": 3200
},
"vault": {
"harmonyNodes": 18,
"powerNodes": 12
},
"relics": {
"owned": 27
},
"lifetime": {
"totalWaves": 2450000,
"highestTier": {
"farm": 18,
"tournament": "Champion"
}
},
"battleReports": {
"recentRuns": 40
},
"warnings": [
"cards extractor: save predates card mastery fields"
]
} thetowersdk/node decodes the file; thetowersdk/save extractors return typed slices (plus warnings when a field is missing or legacy).
Code
import { readFile } from 'node:fs/promises'
import { decodePlayerInfoSaveBytes } from 'thetowersdk/node'
import {
readLabsFromSaveRoot,
readModulesFromSaveRoot,
readCardsFromSaveRoot,
readUltimateWeaponsFromSaveRoot,
readBotsFromSaveRoot,
readVaultFromSaveRoot,
} from 'thetowersdk/save'
const { parsedRoot } = decodePlayerInfoSaveBytes(
await readFile('playerInfo.dat')
)
const labs = readLabsFromSaveRoot(parsedRoot)
const modules = readModulesFromSaveRoot(parsedRoot)
const cards = readCardsFromSaveRoot(parsedRoot)
const uws = readUltimateWeaponsFromSaveRoot(parsedRoot)
const bots = readBotsFromSaveRoot(parsedRoot)
const vault = readVaultFromSaveRoot(parsedRoot)Generate A Cost Table
Level, value, cost and running total, generated from the catalog.
Code
import { uwStoneChartData } from 'thetowersdk/data'
import { formatNumberForDisplay } from 'thetowersdk/formatting'
const gt = Object.values(uwStoneChartData).find((w) => w.name === 'Golden Tower')
const multiplier = gt.stats.find((s) => s.name === 'Multiplier')
// Level, value, cost, running total — the table players actually want.
let running = 0
const rows = multiplier.levels.map((l) => {
if (typeof l.cost === 'number') running += l.cost
return [l.level, l.value, l.cost, formatNumberForDisplay(running)]
})Module Shard Cost
Shards to take a module from one level to the next.
Live
Shards to level 161
5K
Code
import { getModuleShardUpgradeCost } from 'thetowersdk/data'
import { formatNumberForDisplay } from 'thetowersdk/formatting'
// Cost to reach level 161 from 160
const shards = getModuleShardUpgradeCost(161)
console.log(formatNumberForDisplay(shards))Card Gem Cost
Gems to raise a card from one level to another (copies × 20).
Live
Gem cost (range)
1.6K
| Level | Copies Owned | Gems |
|---|---|---|
| 0 → 1 | 1 | 20 |
| 1 → 2 | 2 | 40 |
| 2 → 3 | 5 | 100 |
| 3 → 4 | 8 | 160 |
| 4 → 5 | 12 | 240 |
| 5 → 6 | 20 | 400 |
| 6 → 7 | 32 | 640 |
Code
import { cardLevelUpgradeGemCost } from 'thetowersdk/mechanics'
import { formatNumberForDisplay } from 'thetowersdk/formatting'
const gems = cardLevelUpgradeGemCost(0, 7)
console.log(formatNumberForDisplay(gems))Effective Paths
Next economy buys from your starting levels, including what was skipped.
Live
- 1. Coins / Kill Bonus lv 1
- 2. Coins / Kill Bonus lv 2
- 3. Coins / Kill Bonus lv 3
- 4. Coins / Kill Bonus lv 4
- 5. Coins / Kill Bonus lv 5
21 candidates skipped (with reasons on the full object).
Code
import {
planEffectiveEconomyPath,
ZERO_EFFECTIVE_ECONOMY_LEVELS,
zeroEffectiveEconomyConfig,
} from 'thetowersdk/mechanics'
const levels = {
...ZERO_EFFECTIVE_ECONOMY_LEVELS,
time: {
...ZERO_EFFECTIVE_ECONOMY_LEVELS.time,
coinsPerKillBonus: 20,
},
}
const plan = planEffectiveEconomyPath({
config: zeroEffectiveEconomyConfig(),
levels,
variant: 'time',
steps: 5,
})
console.log(plan.steps.map((s) => s.name))
console.log(plan.excluded.length, 'skipped')Glossary Lookup
Resolve Tower acronyms from the package glossary (generated + curated, no network).
Live
CF — Chrono Field
ultimate-weapon
Chrono Field ultimate weapon.
Code
import { lookupGlossary } from 'thetowersdk/data'
const hits = lookupGlossary('CF')
console.log(hits.map((h) => h.expansion ?? h.term))Any Calculator, From Its Own Declaration
Fields, units and caps are declared, so a form renders a calculator it has never seen.
Live
What share of the time an ultimate weapon is active, from its duration and cooldown.
Measured from activation, so duration ≥ cooldown means permanent uptime.
- Ratio
- 30.00%
- Percent
- 30.00%
- Permanent
- false
- Downtime seconds
- 70
Code
import { CALCULATOR_BUILDERS, findCalculatorBuilder } from 'thetowersdk/builders'
const builder = findCalculatorBuilder('assist.stones')
builder.fields // [{ key: 'currentLevel', label: 'Current level', kind: 'number', min: 0, max: 69 }, …]
const result = builder.compute(builder.normalize({ currentLevel: 0, targetLevel: 10 }))
console.log(result.totalStones) // 285