TheTowerSDK

What's In The Package

Counts from the installed package — including the detailed pieces inside each system.

31,079

Workshop levels

6,053

Lab levels

1,446

Guardian levels

800

Chartable series

739

UW levels

914

Formulas

546

Bot levels

544

Card levels

1,148

Milestone rewards

475

Glossary terms

321

Relics

94

Vault nodes

74

Module substats

34

Perks

29

Battle conditions

What You Get

Complete Game Catalogs

Labs, workshop, enhancements, modules, cards, relics, bots, guardians, ultimate weapons, vault trees, perks, battle conditions, tiers, and milestones — ready to query in code.

Combat And Economy Math

Enemy scaling by tier and wave, ultimate weapon timing, lab and workshop costs, damage reduction, resource drops, and Effective Paths planning — all as functions you call with plain numbers.

Player Save Reading

Decode playerInfo.dat and pull labs, modules, cards, ultimate weapons, and run history from a real account. adb-bridge fetches the file from a phone or emulator.

Charts And Cost Tables

Every catalog with a level progression generates a chart or a table — cost curves, research time, stat scaling. Add rows to the data and every view regenerates.

Calculators As Data

Each calculator describes its own inputs — names, units, ranges and caps. Read that description to generate a form, a slash command, or a test straight from the calculator itself.

Five Years Of Patch Notes

Every announcement the developers have made since July 2021. Search by text, pull a single version, read a date range, or find the note where a mechanic first appeared.

Spreadsheets And Bots

Read live community spreadsheets, write catalogs into Google Sheets as working formulas, and expose any calculator as a Discord slash command that answers with the same numbers as your site.

Wiki Ingestion

Pull The Tower wiki into Markdown inside your app, so mechanic descriptions live alongside the numbers they describe.

Assistant Tooling

An MCP server with thirty-seven tools your editor can register: run the shipped calculators for real numbers, work in an instrumented sandbox, decode a save, and trace the result. Plus a game-knowledge oracle and a spreadsheet oracle.

TowerAI

The assistant core, published alongside the package. Fill its knowledge base with the mechanics you care about and it answers from your curation, pulling live values from the catalogs as it writes.

Examples

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))

More Examples →

Tracking Runs

The game stores a battle report for every completed run. listImportableBattleRuns hands back those entries as they were recorded — tier, wave, duration, coins, timestamp, damage breakdowns, what killed you — so a run tracker is a table over them plus whatever totals matter to you. Coins per hour falls out of duration and coins, which is why most trackers start there.

Sample

TierWaveDurationCoinsDate
115,2417h 33m4.12q2026-08-16 21:40
114,9887h 05m3.74q2026-08-16 08:15
123,9026h 21m3.98q2026-08-15 22:03
106,1048h 14m3.51q2026-08-15 09:27
123,7445h 55m3.66q2026-08-14 20:11
Runs
5
Best Wave
6,104
Coins / Hour
541.082T

Code

import { listImportableBattleRuns } from 'thetowersdk/save'
import { formatNumberForDisplay } from 'thetowersdk/formatting'

// Every run the game kept, as it stored them.
const runs = listImportableBattleRuns(parsedRoot)

const rows = runs.map((run) => ({
  tier: run.tier,
  wave: run.wave,
  duration: run.durationSeconds,
  coins: run.coinsEarned,
  at: run.dateTime,
}))

// The number the table exists to produce.
const seconds = rows.reduce((sum, r) => sum + r.duration, 0)
const coins = rows.reduce((sum, r) => sum + r.coins, 0)
console.log(formatNumberForDisplay((coins / seconds) * 3600), 'coins/hour')

Save File Docs →

Reading Save Files

playerInfo.dat holds a player's whole account: research, modules, cards, ultimate weapons, vault, and every run the game kept. Decode it once, then run as many extractors over the result as you like — each returns typed values you can drop straight into a tracker. The decoded object stays available too, so any field is reachable.

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)

Save File Docs →

Pulling Saves Automatically

Getting the file off the device is a separate job from reading it. adb-bridge locates the save and serves it to your page over a local WebSocket — from an Android phone or emulator over ADB, or from the native Mac App Store build straight out of its app container. Ask it to watch and it re-sends on every write, so a tracker updates while the player plays.

How It Works

  1. The player runs npx adb-bridge. On Android it installs Google's platform-tools on first run if adb is missing.
  2. It finds the save: a connected device or emulator over ADB, or on macOS the local app container under ~/Library/Containers — no device and no ADB needed.
  3. Your page connects to 127.0.0.1 and receives the bytes — no upload, no file picker.
  4. You decode those bytes exactly as you would a file read from disk.

Reads only, no root, and one install covers multiple games.

Code

// Terminal: npx adb-bridge
// Browser: connect and receive the save, then again on every write.
const socket = new WebSocket('ws://127.0.0.1:8787')

socket.onmessage = async (event) => {
  const bytes = new Uint8Array(await event.data.arrayBuffer())

  const { parsedRoot } = decodePlayerInfoSaveBytes(bytes)
  const labs = readLabsFromSaveRoot(parsedRoot)

  render(`${labs.researchedCount} researched, ${labs.maxedCount} maxed`)
}

// Ask for the current save; the bridge re-sends whenever the game writes.
socket.onopen = () => socket.send(JSON.stringify({ type: 'watch', game: 'the-tower' }))
npx adb-bridge

Save File Docs → · adb-bridge →

Charts

Every catalog entry with a level progression is a series, which covers 800 of them across labs, workshop, cards, ultimate weapons, bots and guardians. Add rows to the data and every view regenerates — no image to redraw by hand. Below, that generator renders a cost table as an image.

Live

Select a stat to generate its table.

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)]
})

Charts Docs →

Building Spreadsheets

The catalogs are already rows and columns, so a planning sheet can be generated rather than maintained by hand — every lab, level and cost written into the grid and rebuilt when the game changes.

Generate The Data

Flatten a catalog into rows and write them to CSV, xlsx, or the Google Sheets API.

import { LAB_CATALOG } from 'thetowersdk/data'

// One row per level: what a sheet actually needs.
const rows = LAB_CATALOG.flatMap((lab) =>
  lab.levels.map((l) => [lab.name, l.level, l.cost, l.duration])
)

// Write it wherever your sheet lives — CSV, xlsx, or the Sheets API.
await sheets.spreadsheets.values.update({
  spreadsheetId,
  range: 'Labs!A2',
  valueInputOption: 'RAW',
  requestBody: { values: rows },
})

Generate The Formulas

Write real spreadsheet formulas into the cells, so the sheet keeps calculating as its reader changes inputs. The sheet oracle evaluates any formula in place and returns what it computes.

// Write formulas, not just numbers — the sheet keeps calculating.
const formulas = LAB_CATALOG.map((lab, i) => [
  lab.name,
  `=SUMIF(Levels!A:A,A${i + 2},Levels!C:C)`,   // total coins to max
  `=B${i + 2}/Inputs!$B$1`,                    // hours at your coin rate
])

// Check one against the sheet before shipping it.
eval_formula({ formula: '=SUMIF(Levels!A:A,"Attack Speed",Levels!C:C)' })

Sheet Oracle Setup →

Building Bots

The package is framework-agnostic, so a bot imports the same catalogs and formulas a website does. The Run Tracker ships three Discord bots built this way.

One Calculation Layer

Parsing, cost math and run shapes live in the package; embeds, components and modals stay in the bot. A command answers with the same number the site shows, because it is the same function.

Interaction Conventions

One router, a single owner for component ids, ownership filtered by user as well as id, and token guards before state is touched.

Building A Bot →

Optional

Build With An Assistant

Register the MCP server and your editor's assistant can query the catalogs, decode a save, look up a mechanic on the wiki, and read a live community spreadsheet while it writes your tool.

MCP Server

Thirty-seven tools for Cursor, Claude or Copilot. It can run the shipped calculators and get the real number, work in an instrumented sandbox, and trace what it produced. list_exports and describe_schema for the API, decode_save for a real account, wiki_page for mechanics, and sdk_graph_render for a Mermaid diagram.

The Tower Oracle

A knowledge graph of game mechanics and how they interact. oracle_traps returns the known ways a mechanic has been misread, oracle_expand resolves acronyms from a closed set, and oracle_contradictions lists disagreeing claims ranked by source authority.

The Sheet Oracle

Reads a live community spreadsheet through MCP. eval_formula evaluates a formula in the sheet and returns what it computes; list_lambdas gives the named functions and their parameter order.

The Wiki

wiki_search finds the page for a mechanic and wiki_page returns it as Markdown, so an assistant can read how something behaves in the community's own words while it writes against the catalogs.

Build Your Own TowerAI

towerai is the TowerAI assistant core, published alongside this package. Install it, fill its knowledge base with the mechanics you care about, and it answers from your curation. Chunks hold prose for meaning while catalogs supply the numbers, so costs never go stale inside a sentence.

Live

Ask about a mechanic. This calls the live assistant — the same one behind the tracker site and the bot's /ask — which answers from the shared TowerAI knowledge base.

Code

// Your endpoint holds the model key and the knowledge base.
// The browser only ever sends a question and receives an answer.
async function ask(question) {
  const response = await fetch('/api/ask', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ question })
  })

  const { ok, answer } = await response.json()
  if (!ok) throw new Error('The assistant could not answer.')
  return answer
}

const answer = await ask('How do I sync Golden Bot with Death Wave?')

// The reply is markdown, so render it the way you render any markdown.
element.innerHTML = renderMarkdown(answer)
npm install towerai

Install

Everything on this page, and what each piece is for. Only the first is required.

TheTowerSDK

Required

Game catalogs, save reading, formulas, charts, and wiki ingestion. Works in Node and in the browser, needs no key and no network, and everything else here builds on it.

Add to your project

npm install thetowersdk

Install Docs →

TowerAI

Optional

The assistant core: fill its knowledge base with the mechanics you care about and it answers from your curation. You supply a model to generate the wording — Groq has a free tier, and the docs walk through getting a key.

Add to your project

npm install towerai

TowerAI Docs →

MCP Server

Ships with the SDK

Already inside the package, so there is nothing extra to install. Once registered, your assistant gains thirty-seven tools — running the shipped calculators for real numbers, an instrumented sandbox to work in, save decoding, wiki lookup, and tracing to check its own output. Using Cursor or Copilot? The MCP docs have the JSON config.

Register once — Claude Code

claude mcp add thetowersdk -- node ./node_modules/thetowersdk/mcp/server.mjs

MCP Docs →

adb-bridge

Optional

A standalone program, not a dependency, so it runs straight from npx. Pulls a real save from an Android device, an emulator, or the native Mac build and serves it locally.

Run on the player’s machine

npx adb-bridge

Save File Docs →

In Production

Flagship Demonstration

The Tower Run Tracker is a full suite of calculators and trackers built on TheTowerSDK — the same catalogs, formulas and save readers documented above, running against real accounts.