- classification 5 (rare-elite) now renders with silver-blue skull pin and shows up in the boss list with a 'rare' tag (was falling through to default trash style) - new Note tool: click map → enter text → drops yellow info pin; hover for tooltip, double-click to edit, drag to move, right-click to remove. Notes are included in the share-URL hash and history. - Clear button now confirms before wiping the current floor's waypoints/pulls/notes
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/<slug>/<mapping_id>/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 companionen_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. ~470 MB of WebPs total. 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
│ └── <tile_key>/
│ ├── split_floors.js
│ ├── lang.js
│ └── floor<n>/z4/<x>_<y>.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/<key>[_floor<n>].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 (gitignored — too big without LFS)
Build
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 (~470 MB)
.venv/bin/python tools/kg_build_data.py # → web/assets/dungeons.json
Run locally
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:
- Add
mplus.exil.esA/AAAA records in NetBox (→ public HAProxy VIPs) - Push the Ansible commit, trigger
Ansible_DeployPublicHaproxy - Materialize
web/assets/maps/on the deploy host (run the build pipeline above) - Run
playbooks/setup_mplus_routes.yml
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 (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 indata/kg/_summary.jsonheaders note). - Update
data/kg_dungeons.jsonwith 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.