Contract: Zero game data API v1

Read-only JSON describing the game exactly as the current prototype build plays it. Generated 2026-10-01 01:57 UTC. 102 cards · 42 enemies · 3 bosses · 100 maps · 49 artifacts · 5 specialTiles · 14 formulas.

metacardsenemiesbossesmapsrangePatternswavesrouterunRulesdecksmissionseventsartifactsspecialTileslandmarksformulasallconventionsformulasdata browser

Quick start

Every endpoint is a static JSON file under /v1/ on the site hosting this page. Requests are plain GET, no key needed, and cross-origin requests are allowed (CORS), so you can fetch from a local dev server or another site.

// JavaScript
const BASE = "https://YOUR-SITE.pages.dev/v1/";
const cards = await (await fetch(BASE + "cards.json")).json();
const railgun = cards.find(c => c.code === "C-028");

# shell
curl https://YOUR-SITE.pages.dev/v1/enemies.json

Source of truth: card, enemy, event and rule data are authored in Supabase. This API is generated from the game build, so it matches what the game actually plays (including rules implemented in code). It is regenerated with every new build; check /v1/meta.json → generated. Breaking changes will move to /v2/.

Conventions

Endpoints

GET /v1/meta.json

Build info: version, generation time, endpoint list and item counts. Check generated to know which build the data came from.

Example
{
 "api": "Contract: Zero game data API",
 "version": "v1",
 "generated": "2026-10-01T01:57:47.309Z",
 "source": "Generated from the published prototype build (Isometric Edition). Card, enemy, event and rule data originate in Supabase; this API is exactly what the build uses.",
 "endpoints": [
  "/v1/cards.json",
  "/v1/enemies.json",
  "/v1/bosses.json",
  "/v1/maps.json",
  "/v1/rangePatterns.json",
  "/v1/waves.json",
  "/v1/route.json",
  "/v1/runRules.json",
  "/v1/decks.json",
  "/v1/missions.json",
  "/v1/events.json",
  "/v1/artifacts.json",
  "/v1/specialTiles.json",
  "/v1/landmarks.json",
  "/v1/formulas.json"
 ],
 "counts": {
  "cards": 102,
  "enemies": 42,
  "bosses": 3,
  "maps": 100,
  "artifacts": 49,
  "specialTiles": 5,
  "formulas": 14
 }
}

GET /v1/cards.json

Every card in the build: stats, text, range pattern and the exact tiles it covers, and its build roles from Supabase.

FieldTypeMeaning
codestringCard id, e.g. C-001. Stable across builds.
namestringDisplay name.
typestringAttacking Tower, Utility Tower, Sentry, Module, Spell or Boon.
raritystringStarting Deck, Token, Common, Uncommon, Rare.
costnumber | nullEnergy cost (null for Boons, which are unplayable).
attacknumber | nullBase attack per hit.
speednumber | nullAttacks per second (the tower fires every 1/speed seconds).
rangestringLegacy range value in range units (50 per tile), or line for whole-row towers.
rangePatternstringRange pattern name (see rangePatterns), or line.
rangeCells[dx,dy][] | 'line'Tiles the tower can hit, relative to its own tile, facing right (+x). Rotate for other facings.
keywordsstring[]Keyword tags, e.g. Sentry, starter.
textstringCard text as shown in game.
inPoolbooleanTrue if the card can appear in rewards and shops in this prototype.
sentryTypestring | nullseries, adaptive, cryo or fuse for sentry cards.
buildRolesobjectBuild key to role: enabler, payoff or both (Supabase build_roles).
buildWeightobjectBuild key to core or minor (Supabase build_weight).
tagsstring[]Supabase tags, e.g. adjacency, anti-flying.
Example
{
 "code": "C-001",
 "name": "Attacker",
 "type": "Attacking Tower",
 "rarity": "Starting Deck",
 "cost": 1,
 "attack": 10,
 "speed": 2,
 "range": "100",
 "rangePattern": "Y-6",
 "rangeCells": [
  [
   0,
   -2
  ],
  [
   1,
   -2
  ],
  [
   -1,
   -1
  ],
  [
   0,
   -1
  ],
  [
   1,
   -1
  ],
  [
   2,
   -1
  ],
  [
   -1,
   0
  ],
  [
   1,
   0
  ],
  [
   2,
   0
  ],
  [
   -1,
   1
  ],
  [
   0,
   1
  ],
  [
   1,
   1
  ],
  [
   2,
   1
  ],
  [
   0,
   2
  ],
  [
   1,
   2
  ]
 ],
 "keywords": [
  "starter"
 ],
 "text": "A basic tower. No special effect.",
 "inPool": true,
 "sentryType": null,
 "buildRoles": {},
 "buildWeight": {},
 "tags": []
}

GET /v1/enemies.json

Every enemy, bosses included: base stats, behaviour text, the engine's behaviour flags, and stats at levels 1 to 30.

FieldTypeMeaning
codestringEnemy id, e.g. E-001; bosses use E-B01 to E-B03.
namestringDisplay name.
typestringCategory, e.g. Swarm, Tank, Boss.
threatnumber1 to 10. Wave generation picks enemies within a threat range per world and difficulty.
flyingbooleanFlies straight from its spawn to its target, ignoring roads.
hp / shieldnumberBase values at level 1. Shield absorbs damage first.
speednumberRange units per second at level 1 (50 units = 1 tile).
leaknumberHQ damage when it reaches the HQ.
behaviourFlagsobjectEngine flags that drive abilities, e.g. {"blink":{"dmg":50,"d":150,"cd":5}}.
statsByLevelobject[]hp, shield and speed at levels 1, 5, 10, 15, 20, 25, 30 (see formula eStats).
Example
{
 "code": "E-001",
 "name": "Swarmling",
 "type": "Swarm",
 "threat": 1,
 "flying": false,
 "boss": false,
 "hp": 15,
 "shield": 0,
 "speed": 60,
 "leak": 1,
 "behaviour": "Spawns in groups of 6. No ability.",
 "description": "A small rift organism that attacks in groups of six. Individually weak.",
 "strategy": "Splash damage clears the pack: a Fuse on the road, Adaptive Mortar, or Chain Reaction. Arc Link chains between them.",
 "behaviourFlags": {
  "group": 6
 },
 "statsByLevel": [
  {
   "level": 1,
   "hp": 15,
   "shield": 0,
   "speed": 60
  },
  {
   "level": 5,
   "hp": 27,
   "shield": 0,
   "speed": 67.2
  },
  {
   "level": 10,
   "hp": 42,
   "shield": 0,
   "speed": 76.2
  },
  {
   "level": 15,
   "hp": 57,
   "shield": 0,
   "speed": 85.2
  },
  {
   "level": 20,
   "hp": 72,
   "shield": 0,
   "speed": 94.2
  },
  {
   "level": 25,
   "hp": 87,
   "shield": 0,
   "speed": 103.2
  },
  {
   "level": 30,
   "hp": 102,
   "shield": 0,
   "speed": 112.2
  }
 ]
}

GET /v1/bosses.json

The three bosses, one per world, with their timed rules.

Example
{
 "code": "E-B01",
 "world": 1,
 "name": "Brood Mother",
 "flying": false,
 "hp": 960,
 "shield": 0,
 "speed": 24,
 "leak": 10,
 "behaviour": "Releases a Swarmling every 2s and lays a Brood Pod every 10s. At half HP it stops laying and moves 60% faster.",
 "strategy": "Splash damage for the brood, sustained damage on the mother, and slows to keep her in the kill zone.",
 "behaviourFlags": {
  "spawner": {
   "code": "E-001",
   "n": 1,
   "cd": 2
  }
 },
 "timedRules": {
  "podEvery": 10,
  "podCode": "E-011",
  "phaseAt": 0.5,
  "phaseSpeed": 1.6
 },
 "appears": "Final wave of the world's exit stage (map step 11). Killing it: +100 gold and the stage's card picks become Rares."
}

GET /v1/maps.json

All 100 stage maps. Coordinates are tiles: [column, row], origin at the top-left.

FieldTypeMeaning
indexnumberMap id.
namestringMap name.
tiernumber1 to 10: the difficulty a map is used for.
cols / rowsnumberBoard size in tiles.
routesobject[]One per spawn: spawn label and path, a polyline of tile coordinates. The first point is off the board (the entrance); the last point is the HQ.
blocked[c,r][]Building tiles: not buildable, not walkable.
Example
{
 "index": 0,
 "name": "Switchback",
 "tier": 1,
 "cols": 11,
 "rows": 7,
 "routes": [
  {
   "spawn": "A",
   "path": [
    [
     -1,
     0
    ],
    [
     7,
     0
    ],
    [
     7,
     3
    ],
    [
     11,
     3
    ],
    [
     11,
     2
    ]
   ]
  }
 ],
 "blocked": [
  [
   5,
   2
  ],
  [
   1,
   2
  ],
  [
   0,
   4
  ],
  [
   4,
   5
  ],
  [
   3,
   2
  ],
  [
   3,
   4
  ]
 ]
}

GET /v1/rangePatterns.json

Pattern name to the list of [dx, dy] offsets a tower hits, facing right (+x). Card rangeCells are already resolved.

Example
{
 "0-1": [],
 "1-1": [
  [
   1,
   0
  ]
 ],
 "1-2": [
  [
   0,
   -1
  ],
  [
   1,
   0
  ],
  [
   0,
   1
  ]
 ],
 "1-3": [
  [
   1,
   -1
  ],
  [
   1,
   0
  ],
  [
   1,
   1
  ]
 ],
 "2-1": [
  [
   0,
   -2
  ],
  [
   0,
   -1
  ],
  [
   1,
   -1
  ],
  [
   1,
   0
  ],
  [
   2,
   0
  ],
  [
   0,
   1
  ],
  [
   1,
   1
  ],
  [
   0,
   2
  ]
 ],
 "2-2": [
  [
   1,
   0
  ],
  [
   2,
   0
  ]
 ],
 "2-3": [
  [
   0,
   -1
  ],
  [
   1,
   -1
  ],
  [
   1,
   0
  ],
  [
   2,
   0
  ],
  [
   0,
   1
  ],
  [
   1,
   1
  ]
 ],
 "2-4": [
  [
   1,
   -1
  ],
  [
   1,
   0
  ],
  [
   2,
   0
  ],
  [
   1,
   1
  ]
 ],
 "2-5": [
  [
   1,
   -1
  ],
  [
   2,
   -1
  ],
  [
   1,
   0
  ],
  [
   2,
   0
  ],
  [
   1,
   1
  ],
  [
   2,
   1
  ]
 ],
 "2-6": [
  [
   2,
   -2
  ],
  [
   1,
   -1
  ],
  [
   2,
   -1
  ],
  [
   1,
   0
  ],
  [
   2,
   0
  ],
  [
   1,
   1
  ],
  [
   2,
   1
  ],
  [
   2,
   2
  ]
 ],
 "3-1": [
  [
   0,
   -1
  ],
  [
   1,
   -1
  ],
  [
   2,
   -1
  ],
  [
   1,
   0
  ],
  [
   2,
   0
  ],
  [
   3,
   0
  ],
  [
   0,
   1
  ],
  [
   1,
   1
  ],
  [
   2,
   1
  ]
 ],
 "3-2": [
  [
   1,
   0
  ],
  [
   2,
   0
  ],
  [
   3,
   0
  ]
 ],
 "3-3": [
  [
   0,
   -1
  ],
  [
   1,
   -1
  ],
  [
   2,
   -1
  ],
  [
   3,
   -1
  ],
  [
   1,
   0
  ],
  [
   2,
   0
  ],
  [
   3,
   0
  ],
  [
   0,
   1
  ],
  [
   1,
   1
  ],
  [
   2,
   1
  ],
  [
   3,
   1
  ]
 ],
 "3-4": [
  [
   0,
   -2
  ],
  [
   1,
   -2
  ],
  [
   2,
   -2
  ],
  [
   0,
   -1
  ],
  [
   1,
   -1
  ],
  [
   2,
   -1
  ...

GET /v1/waves.json

How waves are built: waves per stage for each world and difficulty, rules, and seeded samples of generated waves (normal and Elite).

FieldTypeMeaning
rules.wavesPerStageobjectworld N, difficulty D: number of waves.
samples[].world / difficulty / elitenumber / booleanWhich stage the sample is for.
samples[].samplepack[][]One array per wave; each pack is {enemy, code, count, level}. Seeded: real runs differ but follow the same rules.
Example
{
 "rules": {
  "wavesPerStage": {
   "world1": {
    "difficulty1": 3,
    "difficulty2": 4,
    "difficulty3": 4
   }
  }
 },
 "samples": [
  {
   "world": 1,
   "difficulty": 1,
   "elite": false,
   "waves": 3,
   "sample": [
    [
     {
      "enemy": "Swarmling",
      "code": "E-001",
      "count": 6,
      "level": 1
     },
     {
      "enemy": "Swarmling",
      "code": "E-001",
      "count": 6,
      "level": 1
     },
     {
      "enemy": "Grunt",
      "code": "E-002",
      "count": 2,
      "level": 1
     }
    ],
    [
     {
      "enemy": "Bloater",
      "code": "E-012",
      "count": 3,
      "level": 1
     },
     {
      "enemy": "Brood Pod",
      "code": "E-011",
      "count": 2,
      "level": 1
     },
     {
      "enemy": "Grunt",
      "code": "E-002",
      "count": 1,
      "level": 1
     },
     {
      "enemy": "Dasher",
      "code": "E-004",
      "count": 1,
      "level": 1
     },
     {
      "enemy": "Swarmling",
      "code": "E-001",
      "count": 1,
      "level": 1
     }
    ]
   ]
  }
 ]
}

GET /v1/route.json

The route map: 12 steps per world, the three lines, stop types, and the difficulty curve from Supabase node_difficulty.

Example
{
 "rows": 12,
 "lines": [
  "express",
  "contracts",
  "risk"
 ],
 "linePlan": {
  "express": {
   "shop": 1,
   "garage": 1,
   "event": 3,
   "elite": 1,
   "skips": 3
  },
  "contracts": {
   "shop": 0,
   "garage": 1,
   "event": 2,
   "elite": 1,
   "skips": 0
  },
  "risk": {
   "shop": 2,
   "garage": 1,
   "event": 1,
   "elite": 3,
   "skips": 0
  }
 },
 "stopTypes": {
  "shop": "Shop",
  "garage": "Garage",
  "event": "Event"
 },
 "difficultyCurve": [
  {
   "world": 1,
   "map_step": 0,
   "line": "all",
   "difficulty": 1
  },
  {
   "world": 1,
   "map_step": 1,
   "line": "all",
   "difficulty": 2
  },
  {
   "world": 1,
   "map_step": 2,
   "line": "all",
   "difficulty": 3
  },
  {
   "world": 1,
   "map_step": 3,
   "line": "all",
   "difficulty": 3
  },
  {
   "world": 1,
   "map_step": 4,
   "line": "all",
   "difficulty": 4
  },
  {
   "world": 1,
   "map_step": 5,
   "line": "all",
   "difficulty": 5
  },
  {
   "world": 1,
   "map_step": 6,
   "line": "all",
   "difficulty": 6
  },
  {
   "world": 1,
   "map_step": 7,
   "line": "all",
   "difficulty": 7
  },
  {
   "world": 1,
   "map_step": 8,
   "line": "all",
   "difficulty": 8
  },
  {
   "world": 1,
   "map_step": 9,
   "line": "all",
   "difficulty": 8
  },
  {
   "world": 1,
   "map_step": 10,
   "line": "all",
   "difficulty": 9
  },
  {
   "world": 1,
   "map_step": 11,
   "line": "all",
   "difficulty": 10
  },
  {
   "world": 2,
   "map_step": 0,
   "line": "all",
   "difficulty": 1
  },
  {
   "world": 2,
   "map_step": 1,
   "line": "all",
   "difficulty": 2
  },
  {
   "world": 2,
   "
  ...

GET /v1/runRules.json

Every tunable rule with its default (Energy, hand size, HP, level scaling, rewards and so on) and every on/off switch.

FieldTypeMeaning
settings[].key / label / default / groupstring / numberRule key as used in code (CFG.key), description, default value, and group.
switches[]objectOn/off rules with their defaults.
worldPerksobject[]Perks offered between worlds.
Example
{
 "settings": [
  {
   "key": "tileSize",
   "label": "Tile size (range units)",
   "default": 50,
   "group": "Board and rules"
  },
  {
   "key": "blockedStart",
   "label": "Blocked % of tiles near the road, first stage",
   "default": 45,
   "group": "Board and rules"
  },
  {
   "key": "blockedEnd",
   "label": "Blocked % of tiles near the road, last stage",
   "default": 10,
   "group": "Board and rules"
  },
  {
   "key": "nearRoad",
   "label": "\"Near the road\" = within this range of a road tile",
   "default": 100,
   "group": "Board and rules"
  },
  {
   "key": "repairEvery",
   "label": "Repair HQ after every N stages",
   "default": 5,
   "group": "Board and rules"
  },
  {
   "key": "repairHp",
   "label": "HQ HP repaired",
   "default": 5,
   "group": "Board and rules"
  },
  {
   "key": "handDraw",
   "label": "Cards drawn per turn",
   "default": 5,
   "group": "Board and rules"
  },
  {
   "key": "energy",
   "label": "Energy per turn at run start",
   "default": 4,
   "group": "Board and rules"
  },
  {
   "key": "handLimit",
   "label": "Hand limit",
   "default": 8,
   "group": "Board and rules"
  },
  {
   "key": "baseHp",
   "label": "HQ HP",
   "default": 20,
   "group": "Board and rules"
  },
  {
   "key": "worldWaves",
   "label": "Extra waves per stage in each later world",
   "default": 1,
   "group": "Board and rules"
  },
  {
   "key": "shotsPerSpeed",
   "label": "Shots per second per 1 Speed",
   "default": 1,
   "group": "Board and rules"
  },
  {
   "key": "towerAtkPct",
   "label": "All tower and sentry attack +% (prototype test, not in
  ...

GET /v1/decks.json

The default starting deck and the built-in deck templates (card code to count).

Example
{
 "defaultStartingDeck": {
  "C-001": 2,
  "C-101": 1,
  "C-006": 1,
  "C-007": 1,
  "C-049": 1,
  "C-017": 1,
  "C-039": 1,
  "C-089": 1,
  "C-070": 1,
  "C-077": 1
 },
 "templates": [
  {
   "id": "starterA",
   "name": "Starter Deck - A (11)",
   "deck": {
    "C-001": 2,
    "C-101": 1,
    "C-006": 1,
    "C-007": 1,
    "C-049": 1,
    "C-017": 1,
    "C-039": 1,
    "C-089": 1,
    "C-070": 1,
    "C-077": 1
   },
   "random": false
  },
  {
   "id": "starter",
   "name": "Starter Deck (10)",
   "deck": {
    "C-001": 2,
    "C-003": 2,
    "C-004": 3,
    "C-049": 1,
    "C-006": 1,
    "C-007": 1
   },
   "random": false
  },
  {
   "id": "sentry",
   "name": "Sentry build (15)",
   "deck": {
    "C-001": 2,
    "C-004": 1,
    "C-014": 1,
    "C-012": 2,
    "C-037": 1,
    "C-020": 2,
    "C-050": 2,
    "C-084": 1,
    "C-048": 1,
    "C-017": 1,
    "C-040": 1
   },
   "random": false
  },
  {
   "id": "fuse",
   "name": "Fuse build (15)",
   "deck": {
    "C-001": 3,
    "C-003": 1,
    "C-002": 2,
    "C-015": 3,
    "C-038": 1,
    "C-032": 1,
    "C-029": 2,
    "C-028": 1,
    "C-020": 1
   },
   "random": false
  },
  {
   "id": "adaptive",
   "name": "Adaptive build (15)",
   "deck": {
    "C-001": 2,
    "C-004": 1,
    "C-003": 1,
    "C-010": 2,
    "C-022": 2,
    "C-079": 1,
    "C-082": 2,
    "C-090": 1,
    "C-064": 1,
    "C-062": 1,
    "C-075": 1
   },
   "random": false
  },
  {
   "id": "cryo",
   "name": "Cryo and slow build (15)",
   "deck": {
    "C-001": 1,
    "C-003": 1,
    "C-011": 2,
    "C-026": 1,
    "C-025": 1,
    "C-019": 1,

  ...

GET /v1/missions.json

Clients, the 50 missions, the objective types and their rules.

Example
{
 "clients": [
  {
   "code": "CL-01",
   "name": "Ichiro Banno",
   "role": "City councillor",
   "boon": "Kickback",
   "boonText": "Unplayable. When you draw this, gain 1 Energy and lose 3 gold."
  },
  {
   "code": "CL-02",
   "name": "Fumiko Oda",
   "role": "Retired calligraphy teacher",
   "boon": "Lucky Cat",
   "boonText": "Unplayable. When you draw this, gain 5 gold."
  },
  {
   "code": "CL-03",
   "name": "Tetsu Mogami",
   "role": "Fishmonger at the port market",
   "boon": "Cold Chain",
   "boonText": "Unplayable. When you draw this, generate 1 Cryo."
  },
  {
   "code": "CL-04",
   "name": "Haruka Sen",
   "role": "Night-shift head nurse",
   "boon": "Night Nurse",
   "boonText": "Unplayable. When you draw this, 10% chance to repair 1 HQ HP."
  },
  {
   "code": "CL-05",
   "name": "Daigo Ueno",
   "role": "Factory line foreman",
   "boon": "Line Quota",
   "boonText": "Unplayable. When you draw this, generate 1 Series."
  },
  {
   "code": "CL-06",
   "name": "Mei Tanabe",
   "role": "Startup founder",
   "boon": "Beta Build",
   "boonText": "Unplayable. When you draw this, generate 1 Adaptive."
  },
  {
   "code": "CL-07",
   "name": "Kenta Arima",
   "role": "Insurance middle manager",
   "boon": "Morning Coffee",
   "boonText": "Unplayable. When you draw this, gain 1 Energy at the start of next turn."
  },
  {
   "code": "CL-08",
   "name": "Sayo Imai",
   "role": "Local idol",
   "boon": null,
   "boonText": null
  },
  {
   "code": "CL-09",
   "name": "Goro Tsuda",
   "role": "Shrine keeper",
   "boon": null,
   "boonText": null
  },
  {
   "code": "CL
  ...

GET /v1/events.json

Route events with their options and effects.

Example
{
 "events": [
  {
   "code": "EV-01",
   "name": "Parking Structure Job",
   "setup": "A property manager needs a rift nest cleared from level B3 before the morning commute. Cash, no paperwork.",
   "options": [
    {
     "text": "Lose 4 HQ HP. Gain 60 gold.",
     "label": "Take the job",
     "effect": "hp:-4;gold:+60"
    },
    {
     "text": "Nothing happens.",
     "label": "Pass",
     "effect": "none"
    }
   ]
  },
  {
   "code": "EV-02",
   "name": "Vending Machine",
   "setup": "A vending machine by the train platform stocks ration bars, energy drinks and, for some reason, spare turret parts.",
   "options": [
    {
     "text": "Add a random Boon to your deck.",
     "label": "Pay 20 gold",
     "effect": "gold:-20;card:boon"
    },
    {
     "text": "50%: gain 30 gold. 50%: lose 3 HQ HP.",
     "label": "Kick it",
     "effect": "chance:gold:+30|hp:-3"
    }
   ]
  },
  {
   "code": "EV-03",
   "name": "Big Firm Surplus Sale",
   "setup": "A big firm is clearing out last season's stock from a warehouse in the port.",
   "options": [
    {
     "text": "Choose 1 of 3 Uncommon or Rare cards to add to your deck.",
     "label": "Pay 40 gold",
     "effect": "gold:-40;choose:3:uncommon+"
    },
    {
     "text": "Nothing happens.",
     "label": "Leave",
     "effect": "none"
    }
   ]
  },
  {
   "code": "EV-04",
   "name": "Festival Setup",
   "setup": "The street festival goes ahead tonight, rifts or not. The organisers are short of hands.",
   "options": [
    {
     "text": "Repair the van for 5 HQ HP.",
     "label": "Help set up",
     "effect": "hp:+5
  ...

GET /v1/artifacts.json

All artifacts with rarity, category and effect text.

Example
{
 "code": "AR-01",
 "name": "Spare Parts Crate",
 "rarity": "Common",
 "cat": "Sentry build",
 "effect": "At the start of each stage, generate 2 random sentries."
}

GET /v1/specialTiles.json

The five special tiles and their effects.

Example
{
 "key": "breaker",
 "name": "Breaker Pad",
 "effect": "+100% damage vs shielded enemies, -50% vs unshielded"
}

GET /v1/landmarks.json

Landmark building types and their bonus rules.

Example
{
 "types": {
  "koban": "Police Box",
  "konbini": "Convenience Store",
  "mast": "Radio Mast"
 },
 "rules": "One landmark per map on its building slot. Its bonus is revealed after wave 1 and applies to every tower and sentry on the 8 adjacent tiles. Possible bonuses: +30% attack, +1 range tile, ignores shields, slows 1s, Vulnerable 2s, +1 gold per kill."
}

GET /v1/formulas.json

Exact source code of the game's core formulas: wave budget and pools, enemy level scaling, card cost, sell value, difficulty curve, reward picks.

Example
{
 "name": "wavesCfg",
 "source": "(world,st,cfg)=>Math.max(1,Math.min(10,Math.round((cfg.stageWaves||DEFAULT_STAGE_WAVES)[st-1]||5)+cfg.worldWaves*(world-1)))"
}

GET /v1/all.json

Everything above in one file.

Formulas (exact game code)

Copied from the build. CFG values are the defaults in /v1/runRules.json; G is the live game state.

wavesCfg
(world,st,cfg)=>Math.max(1,Math.min(10,Math.round((cfg.stageWaves||DEFAULT_STAGE_WAVES)[st-1]||5)+cfg.worldWaves*(world-1)))
budgetFor
function budgetFor(world,st,w,cfg=CFG){ const p=progOf(world,st,w,cfg), shape=(cfg.nodeShape===false)?1:((NODE_SHAPE[Math.max(1,Math.min(3,world))]||[])[st-1]||1);
  return (cfg.bBase+cfg.bMul*waveCurve(w,cfg))*(1+cfg.bStage*(st-1)*p)*(1+cfg.bWorld*(world-1)*p)*shape; }
topLevelFor
function topLevelFor(world,st,w,cfg=CFG){ const p=progOf(world,st,w,cfg);
  return Math.max(1,Math.min(10*world,1+Math.round(cfg.lvlK*Math.pow(Math.max(0,w-1),cfg.lvlQ)+cfg.lvlPerStage*(st-1)*p+cfg.lvlPerWorld*(world-1)*p))); }
threatRange
function threatRange(world,st,w,cfg=CFG){
  const lo=cfg["thLo"+world]||1, hi=cfg["thHi"+world]||10;
  return [lo,Math.max(lo,Math.round(lo+(hi-lo)*progOf(world,st,w,cfg)))];
}
packCost
(code,n,L)=>(ENEMIES[code].hp+ENEMIES[code].shield)/60*n*lvlMult(L)
pickWeighted
function pickWeighted(pool){
  const wt=c=>(EB[c]&&EB[c].attack)?CFG.atkWeight:1, tot=pool.reduce((a,c)=>a+wt(c),0);
  let x=Math.random()*tot; for(const c of pool){ x-=wt(c); if(x<=0) return c; } return pool[pool.length-1];
}
planPacks
function planPacks(world,st,w,elite){
  let budget=budgetFor(world,st,w)*(elite?(world===1?CFG.eliteBudgetW1:CFG.eliteBudget):1);
  // D10 is each world's climax: stronger enemies, not just more of them (levels past the world cap, tougher types)
  const climax=st>=10, cl=climax?(world===1?CFG.climaxLevelsW1:CFG.climaxLevels)||0:0;
  const top=Math.min(10*world+cl,topLevelFor(world,st,w)+(elite?CFG.eliteLevels:0)+cl), low=Math.max(1,top-Math.max(1,CFG.lvlSpread)+1);
  let [tLo,tHi]=threatRange(world,st,w); if(climax) tHi=Math.min(10,tHi+((world===1?CFG.climaxThreatW1:CFG.climaxThreat)||0));
  let pool=ENEMIES_RAW.map(r=>r[0]).filter(c=>ENEMIES[c].threat>=tLo&&ENEMIES[c].threat<=tHi);
  if(!pool.length) pool=["E-002"];
  const cheapest=pool.slice().sort((a,b)=>packCost(a,1,low)-packCost(b,1,low))[0];
  const packs=[];
  for(let guard=0;guard<400;guard++){
    let code=pickWeighted(pool), L=packs.length?low+Math.floor(Math.random()*(top-low+1)):top, n=packSize(code,L);
    while(n>1&&packCost(code,n,L)>budget) n--;
    if(packCost(code,n,L)>budget){ code=cheapest; L=low; n=1; if(packCost(code,n,L)>budget) break; }
    budget-=packCost(code,n,L); packs.push({code,n,lvl:L});
  }
  if(!packs.length) packs.push({code:cheapest,n:1,lvl:low});
  return packs;
}
eStats
function eStats(code,L){
  const E=ENEMIES[code]; L=Math.max(1,Math.min(30,L||1));
  return {hp:E.hp*(1+CFG.lvlHp/100*(L-1)),sh:E.shield*(1+CFG.lvlShield/100*(L-1)),spd:E.speed*(1+CFG.lvlSpd/100*(L-1)),leak:E.leak};
}
cardCost
function cardCost(card){
  const cd=CARDS[card.code]; let c=cd.cost;
  if(card.code==="C-028") c=Math.max(0,c-sentries().length);
  if(card.disc) c=Math.max(0,c-card.disc);
  if(card.fpTurn===G.turn&&CARDS[card.code].type!=="Boon") c+=card.fpN||1;                       // Fine Print
  if(hasArt("AR-19")&&/Tower|Sentry/.test(cd.type)&&!G.freeTower) return 0;
  if(hasArt("AR-02")&&cd.type==="Sentry"&&!G.freeSentryUsed) c=0;
  if(hasArt("AR-27")&&cd.type==="Module"&&!G.freeModuleUsed) c=0;
  if(hasArt("AR-29")&&cd.type==="Spell"&&!G.freeSpellUsed) c=Math.max(0,c-1);
  if(c>0&&cd.type!=="Boon") c=Math.max(0,c-inHand("C-065")*boonMult());
  return c;
}
sellValue
t=>Math.floor((t.paid||0)*CFG.sellPct/100)
rowToStage
(r,rows)=>1+Math.round(9*r/(rows-1))
tableDifficulty
function tableDifficulty(world,row,rows,lineId,fallback){ const step=rows===12?row:Math.round(11*row/Math.max(1,rows-1));
  const hit=NODE_DIFFICULTY.find(r=>r.world===world&&r.map_step===step&&r.line===lineId)||NODE_DIFFICULTY.find(r=>r.world===world&&r.map_step===step&&r.line==="all");
  return hit?hit.difficulty:fallback; }
maxHp
()=>CFG.baseHp+(hasArt("AR-38")?10:0)
stageRewardPicks
function(...a){ const picks=_srp.apply(this,a); if(!G.bossKilled||!Array.isArray(picks)) return picks;
    const pool=Object.values(CARDS).filter(c=>c.rarity==="Rare"&&inProto(c.code)&&!REJECTED.has(c.code)&&(typeof usable!=="function"||usable(c.code))).map(c=>c.code);
    for(let i=pool.length-1;i>0;i--){ const j=Math.floor(Math.random()*(i+1)); [pool[i],pool[j]]=[pool[j],pool[i]]; }
    return picks.map((p,i)=>pool[i]||p); }

Data browser

Contract: Zero, Algorocks. Internal API for the development team.