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

WeaponDuration (s)Cooldown (s)Uptime (%)
Golden Tower23s220s10.5%
Black Hole23s120s19.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

LevelCopies OwnedGems
0 → 1120
1 → 2240
2 → 35100
3 → 48160
4 → 512240
5 → 620400
6 → 732640

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