# mplus-routes Mythic+ route planner for Ascension classic-vanilla dungeons. Static web app targeted at `mplus.exil.es`. Uses **Keystone.guru** map tiles + enemy data, courtesy of RaiderIO (verbal permission, see attribution below). ## Stack - **Maps:** keystone.guru z=4 tile pyramid stitched to 6144×4096 WebP per dungeon-floor (~150–600 KB each). - **Data:** keystone.guru `mapcontext/data///split_floors.js` — enemies (with positions, classification, NPC linkage), enemy packs (polygon outlines), enemy patrols (polylines), map icons (skulls, doors, comments, gateways), NPC names from companion `en_US.js`. - **Frontend:** vanilla HTML/CSS/JS, single page, no build step. Pan/zoom via CSS transform on the canvas stage; SVG overlay in image-pixel space. - **Hosting:** static. ~120 MB of WebPs total, committed to the repo. Drop behind nginx; no backend. ## Layout ``` mplus-routes/ ├── data/ │ ├── kg_dungeons.json registry: tile_key, data_slug, mapping_id per dungeon │ └── kg/ raw kg tiles + data (gitignored) │ ├── _summary.json │ └── / │ ├── split_floors.js │ ├── lang.js │ └── floor/z4/_.png ├── tools/ │ ├── kg_fetch.py fetch tiles + data files for every dungeon in the registry │ ├── kg_resync_summary.py rebuild data/kg/_summary.json from disk (after partial fetches) │ ├── kg_stitch.py assemble 16×16 tile grids → web/assets/maps/[_floor].webp │ ├── kg_build_data.py combine into web/assets/dungeons.json │ └── check_kg_alignment.py server-side alignment sanity check (PIL renders into /tmp/) └── web/ ├── index.html ├── style.css ├── app.js └── assets/ ├── dungeons.json └── maps/ per-dungeon WebPs (~120 MB, committed) ``` ## Build ```bash python3 -m venv .venv .venv/bin/pip install Pillow .venv/bin/python tools/kg_fetch.py --workers 32 --zoom 4 # fetch tiles + data (~1.3 GB raw) .venv/bin/python tools/kg_stitch.py --workers 4 # → web/assets/maps/*.webp (~120 MB) .venv/bin/python tools/kg_build_data.py # → web/assets/dungeons.json ``` ## Run locally ```bash cd web && python3 -m http.server 8765 # → http://localhost:8765/ ``` ## Deployment Ansible role + playbook for `mplus.exil.es` live in `/home/sub/repos/sub-net/ansible/roles/mplus_routes` and `playbooks/setup_mplus_routes.yml`. Targets `exiles01:/srv/www/mplus.exil.es/` behind the public HAProxy SNI router. The wildcard `*.exil.es` cert covers TLS automatically. First-time bring-up: 1. Add `mplus.exil.es` A/AAAA records in NetBox (→ public HAProxy VIPs) 2. Push the Ansible commit, trigger `Ansible_DeployPublicHaproxy` 3. Run `playbooks/setup_mplus_routes.yml` — `web/assets/maps/` ships in the repo, no pre-build step needed. ## Coverage 29 classic dungeons in the picker: | Dungeon | Enemies | Packs | Map icons | |---|---:|---:|---:| | Ragefire Chasm | 139 | 9 | 1 | | The Deadmines | 218 | 26 | 13 | | Wailing Caverns | 261 | 18 | 8 | | Shadowfang Keep | 140 | 6 | 5 | | The Stockade | 94 | 21 | 5 | | Blackfathom Deeps | 240 | 20 | 21 | | Gnomeregan | 282 | 44 | 25 | | Razorfen Kraul | 199 | 39 | 12 | | Razorfen Downs | 219 | 53 | 12 | | SM (4 wings) | 69+96+87+79 | 22+25+20+25 | 7+2+4+4 | | Uldaman | 261 | 33 | 12 | | Zul'Farrak | 302 | 40 | 6 | | Maraudon | 548 | 107 | 4 | | Blackrock Depths | 819 | 169 | 33 | | Lower Blackrock Spire | 380 | 98 | 17 | | Upper Blackrock Spire | 207 | 56 | 20 | | Stratholme | 377 | 77 | 19 | | Scholomance | 344 | 64 | 14 | | Dire Maul (3 wings) | 229+300+241 | 58+44+48 | 7+7+10 | | Naxxramas (40) | 574 | 92 | 2 | | Molten Core | 241 | 39 | 1 | | Blackwing Lair | 79 | 15 | 1 | | Zul'Gurub | 0 | 0 | 0 | | Sunken Temple | 0 | 0 | 0 | ZG and Sunken Temple have map tiles but no enemy data — kg's mapping_id search didn't find populated data for them at time of build. Picker still lets you draw routes on the map manually. ## Attribution Map tiles and enemy data derived from [Keystone.guru](https://keystone.guru/) (RaiderIO, Inc.). Used with permission from the maintainers for the Ascension community deployment at `mplus.exil.es`. World of Warcraft trademarks belong to Blizzard Entertainment; this site is not affiliated with Blizzard. ## Refresh data sources Both kg's tiles and data update over time. To refresh: - Re-run the wide mapping-id probe (`tools/` doesn't ship one — easiest is to fetch the kg homepage and find current ids, or re-run a probe like the one in `data/kg/_summary.json` headers note). - Update `data/kg_dungeons.json` with new mapping_ids. - Re-run `kg_fetch.py` → `kg_stitch.py` → `kg_build_data.py`. ## Known gaps - 2/29 dungeons (Zul'Gurub, Sunken Temple) are present without enemy data. - Multi-floor dungeons (LBRS 7, BWL 4, Naxx 6, etc.) have all floors mapped individually; the floor tabs in the UI let you switch between them. - No trash-pull authoring beyond kg's data — packs are inherited as-is. For custom-routing ("skip this pack, take this one"), use the route+pull tools in the toolbar.