# Sacred Pile — TD Stats Reference Reference data for level publishing. Pair with the contract at `GET /td/api/ai` (request/response/error codes). All numeric values are rendered live from the server manifests at startup. ## Run mechanics - The player picks a difficulty (`easy` / `hard` / `epic`) at run start. Difficulty changes starting lives and starting souls; level data is identical across difficulties. - Mobs spawn at `grid.spawn` and walk the BFS-resolved path to `grid.exit` along the connected `#` tiles. - A mob that reaches `exit` deducts one life. - The run ends in loss when lives reach 0. - The run ends in win when all waves have spawned and the last mob has died or leaked. - Towers occupy `S` tiles. A given `S` tile holds at most one tower at a time. Towers can be sold for `sellbackRatio × invested_soul`. ## Difficulty starting resources | difficulty | starting lives | starting souls | |---|---|---| | easy | 20 | 500 | | hard | 10 | 250 | | epic | 5 | 150 | ## Towers | id | name | role | tier1 cost/dmg/range/rateMs | tier2 cost/dmg/range/rateMs | tier3 cost/dmg/range/rateMs | |---|---|---|---|---|---| | shitter | Shitter | single-target | 50 / 15 / 150 / 1000 | 200 / 50 / 175 / 800 | 2250 / 225 / 200 / 600 | | turdner | Turdner | heavy-long | 100 / 10 / 175 / 1500 | 650 / 30 / 187.5 / 1250 | 7250 / 90 / 200 / 1100 | | pooptal | Pooptal | splash-root | 250 / 75 / 300 / 10000 | 750 / 225 / 325 / 9000 | 6750 / 2025 / 350 / 8000 | Field semantics: - `cost` — soul spent to place (tier 1) or upgrade (tiers 2/3). - `damage` — per shot. - `range` — pixels. The play area is 16×9 tiles at ~80 px per tile in the engine's reference frame; `range / 80` ≈ tiles reachable. - `fireRateMs` — milliseconds between shots. DPS = `damage × 1000 / fireRateMs`. ## Mobs | mobId | HP | speed | soulDrop | group | |---|---|---|---|---| | `poopMinion` | 30 | 100 | 5 | regular | | `turdMinion` | 18 | 132 | 4 | regular | | `dungBeetle` | 15 | 75 | 2 | regular | | `dingleberry` | 12 | 110 | 3 | regular | | `sewerRat` | 30 | 150 | 7 | regular | | `ratKing` | 150 | 100 | 30 | regular | | `superRat` | 100 | 88 | 20 | regular | | `mutant` | 75 | 95 | 15 | regular | | `poopbloodDroplet` | 40 | 125 | 10 | regular | | `poopEye` | 15 | 250 | 14 | regular | | `zombieRat` | 80 | 200 | 15 | regular | | `turdTitan` | 2500 | 55 | 1000 | boss | | `shiteven` | 10000 | 65 | 3000 | boss | | `reedTurd` | 1000 | 70 | 300 | boss | | `ethanDingleberry` | 2000 | 65 | 600 | boss | | `dylanPoopblood` | 1500 | 75 | 400 | boss | | `poopMeutant` | 1500 | 78 | 400 | boss | | `septicLord` | 3500 | 90 | 700 | boss | | `shittator` | 1250 | 70 | 450 | boss | Field semantics: - `hp` — total damage required to kill. - `speed` — engine speed units. 100 ≈ 1 tile per second of traversal. - `soulDrop` — soul granted to the player on death. - `group` — taxonomic label (e.g. `regular`, `boss`); cosmetic on this surface. ## Soul economy - Wave-clear bonus: `50 + 50 × (waveIndex)` souls (1-indexed waves: wave 1 = 50, wave 2 = 100, etc.) - Sellback ratio: 0.5 (selling a tower refunds 50% of its invested soul) - Kills earn the mob's `soulDrop` (see mob table) on death ## Per-wave mob scaling At wave N (0-indexed), the engine spawns each mob with stats scaled from the base values in the mob table: - `hp = baseHp × (1 + N × perWave) × exp(0.1 × N)` for non-boss mobs - `hp = baseHp × (1 + N × perWave)` for mobs with `group: boss` (linear only — no exponential) - `soulDrop = baseSoulDrop × (1 + N × perWave)` (linear only) `perWave` per difficulty: easy = 0.15, hard = 0.2, epic = 0.25. HP and soulDrop are rounded to integers at spawn. ## Server warning codes The publish response includes `warnings`: an array of `{code, waveIndex, waveHp, maxClearableFromStartingSouls, ratio, hint}` objects, one per flagged wave. Warnings do not block the publish. Defined codes: - `easy_clear` — wave HP is much smaller than what starting souls alone could deal in the traversal window. - `underpowered_wave` — wave HP is much larger than what starting souls alone could deal in the traversal window. ### Model (pure-fact) For each wave: - `waveHp` = Σ over `entries` of `mob.hp × (1 + N × perWave) × exp(0.10 × N) × count` for non-boss mobs (and without the `exp` factor for boss-group mobs), where `N` is the 0-indexed wave number and `perWave` is the easy-difficulty multiplier. - `maxClearableFromStartingSouls` = `floor(startingSouls_easy / cheapestTier1Cost) × cheapestTier1DPS × traversalSec`, where `traversalSec` = `pathLength × (100 / avgMobSpeed)`. - `ratio` = `waveHp / maxClearableFromStartingSouls`. The model uses **starting souls only** — it does not model soul accumulated from wave-clear bonuses or kill drops. As a result, late waves naturally produce higher ratios than early waves; this is expected. A late-wave `underpowered_wave` warning means "would be unwinnable if the player had no accumulated soul," not "is unwinnable." The estimator fires only on extreme cases: - `easy_clear`: `ratio < 0.02` - `underpowered_wave`: `ratio > 3.0` Mid-range ratios produce no warning.