TheTowerSDK

Build Tower Tools On Real Game Data

Game catalogs and formulas for The Tower. Build calculators, trackers, spreadsheets, charts, read save files, connect to emulators and more.

npm install thetowersdk v0.5.2

What's In The Package

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

750

Formulas

786

Chartable series

5942

Lab Stats

47

Workshop Stats

74

Module Stats

31

Cards

305

Relics

25

Bot Stats

18

Guardian Stats

36

UW Stats

94

Vault nodes

34

Perks

29

Battle conditions

1148

Milestone rewards

476

Glossary terms

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.

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.

Spreadsheet And Bot Tooling

Read live community spreadsheets cell by cell through MCP, and share one calculation layer between a website and a Discord bot so both return the same numbers.

AI Development

An MCP server, a game-knowledge oracle, and a sheet oracle, so an assistant builds against real exports, real mechanics, and real player saves.

Combat And Economy Math

Enemy scaling, ultimate weapon timing, lab costs, workshop costs, Effective Paths planners, and more — callable formulas, not cumbersome spreadsheets.

Wiki Ingestion

Pull The Tower wiki into Markdown inside your app or AI session so mechanics are looked up, not guessed.

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

Death Wave × Golden Bot

DW duration comes from Quantity × 4s per wave; results match the Uptime Calculator columns.

Live

SourceCooldownDurationUptime
Death Wave220s16s (4 × 4s)7.3%
Golden Bot90s25s27.8%

Code

import { BOT_UPGRADES_DATA, estimateBotUptimeFraction, uwStoneChartData } from 'thetowersdk/data'

const dw = Object.values(uwStoneChartData).find((w) => w.name === 'Death Wave')
const dwCd = Number(String(dw.stats.find((s) => s.name === 'Cooldown').levels[8].value).replace(/s$/i, ''))
const dwWaves = Number(String(dw.stats.find((s) => s.name === 'Quantity').levels[3].value).replace(/^x/i, ''))
const dwDur = dwWaves * 4 // Uptime Calculator wave time

const gb = BOT_UPGRADES_DATA.find((b) => b.name === 'Golden Bot')
const gbCd = gb.stats.Cooldown.levels['10']
const gbDur = gb.stats.Duration.levels['10']
const gbUptime = estimateBotUptimeFraction(gbDur, gbCd)

console.log({ dwCd, dwDur, gbCd, gbDur, gbUptime })

Enemy Stats

Base health and damage for a farm tier or tournament league base.

Live

Base enemy health
1.343S
Base 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 →

Charts

Every catalog entry with a level progression is a series, which covers 786 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 →

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 — they read without modifying it. Each returns typed values and reports what it could not interpret, so an older save degrades instead of failing. Fields the SDK does not name are still reachable on the decoded object.

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 →

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

Emit formulas rather than baked numbers and the sheet keeps calculating for its user. The sheet oracle evaluates one in place to confirm what it returns.

// 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 →

AI Development

Build With An AI — Direct It With ACS

TheTowerSDK gives an assistant the game: catalogs, saves, formulas, wiki pages. ACS directs how it works while it edits your project, and TowerAI turns the same knowledge into an assistant of your own.

MCP Server

Register it and Cursor, Claude or Copilot build against real exports. 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.

ACS

Agentic Cognition Substrate directs how an assistant works on your project: research before code, a checkpoint per slice, and a status that reaches “done” only when you say so.

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

Hey there! Ask me a question about The Tower.

Example only. AI assistants are known to make mistakes, and this demo runs on a handful of chunks written for illustration. Do not rely on it for real answers about the game.

Code

import {
  buildTrackerAiCanonicalKbChunks,
  validateCanonicalKbArray,
  buildCanonicalKbVersion,
} from 'towerai/kb'
import { LAB_CATALOG } from 'thetowersdk/data'

// The shipped chunks, then your own on top.
const base = buildTrackerAiCanonicalKbChunks()

const mine = LAB_CATALOG.map((lab) => ({
  chunk_id: `lab_cost_${lab.slug}`,
  source: 'My Notes',
  section: 'Labs',
  topic: `${lab.name} cost`,
  title: `${lab.name} cost`,
  disambiguation: 'Cost to max this lab, not research order.',
  mechanics: [lab.name],
  tags: ['labs', lab.name.toLowerCase()],
  // Numbers come from the catalog, so prose cannot go stale.
  content: `${lab.name} has ${lab.levels.length} levels.`,
}))

const knowledgeBase = [...base, ...mine]

// It tells you what is malformed instead of failing at query time.
validateCanonicalKbArray(knowledgeBase)
console.log(buildCanonicalKbVersion(knowledgeBase), knowledgeBase.length, 'chunks')
npm install towerai

Flagship Demonstration

Check out our flagship demonstration site, The Tower Run Tracker — a full suite of calculators and trackers built on TheTowerSDK.

Open The Tower Run Tracker → · What You Can Build →

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. 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 curated mechanics and answer from your own curation.

Add to your project

npm install towerai

TowerAI Docs →

MCP Server

Ships with the SDK

Already inside the package — nothing extra to install. Register it and your assistant queries real exports, saves, wiki pages, and the oracles.

Register once

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 →