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
| Source | Cooldown | Duration | Uptime |
|---|---|---|---|
| Death Wave | 220s | 16s (4 × 4s) | 7.3% |
| Golden Bot | 90s | 25s | 27.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))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')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)]
})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)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 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)' })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.
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.
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. Everything else here builds on it.
Add to your project
npm install thetowersdk TowerAI
OptionalThe assistant core. Fill its knowledge base with curated mechanics and answer from your own curation.
Add to your project
npm install towerai MCP Server
Ships with the SDKAlready 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 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