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
| 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))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
| Tier | Wave | Duration | Coins | Date |
|---|---|---|---|---|
| 11 | 5,241 | 7h 33m | 4.12q | 2026-08-16 21:40 |
| 11 | 4,988 | 7h 05m | 3.74q | 2026-08-16 08:15 |
| 12 | 3,902 | 6h 21m | 3.98q | 2026-08-15 22:03 |
| 10 | 6,104 | 8h 14m | 3.51q | 2026-08-15 09:27 |
| 12 | 3,744 | 5h 55m | 3.66q | 2026-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')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)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
- The player runs
npx adb-bridge. On Android it installs Google's platform-tools on first run ifadbis missing. - 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. - Your page connects to
127.0.0.1and receives the bytes — no upload, no file picker. - 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 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)]
})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)' })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.
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
RequiredGame 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 TowerAI
OptionalThe 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 MCP Server
Ships with the SDKAlready 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 adb-bridge
OptionalA 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 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.