2026 10 18 classic dungeon temples design - xopherdeep/do-it-for-the-xp GitHub Wiki

Classic Dungeon Temples: the nine shipped temples pay homage to Zelda's dungeons (design)

Date: 2026-10-18. Status: design approved in chat by the owner, section by section; this spec awaits review. Builds on: docs/Quest-Rooms-Spec.md (combat rooms hold quests, sections 5, 10 and 11), 2026-10-13-temple-layout-hardening-design.md (real wall/bombable sides, roomGrants.ts, the solver and audit) and the Wind Temple the owner laid out by hand after The Legend of Zelda's Level 1 (commit 3199f6b5).

Why

The owner wants the main temples to be a homage to the original Zelda dungeon maps: a mapped cell block filled with our own RPG quests. The Wind Temple already follows Level 1 (the Eagle) in outline. The other eight are generated from seeds today and look like nothing in particular. Each temple is a themed chore base (Water is the bathroom, Shadow the kitchen, and so on), and a full dungeon of beasts should feel like a challenge even though a hero only has to beat the rooms that block the way.

Decisions (owner, 2026-10-18)

  • Wind keeps the Eagle; the other eight become A Link to the Past (LTTP) dungeons. LTTP fits our engine: its Big Key is our Boss Key, its small keys stay in their dungeon, it has real floors with stairs and holes (our stairs and pits), and its Big Chest holds the dungeon item (our relic). The Zelda 1 lock clashes (no Boss Key, keys carried between dungeons, Bombs on the main path) mostly disappear.
  • Every enemy room holds a themed chore; only the must-clear ones block the way. A must-clear room (a shutter room, an enemy key drop, a miniboss, the boss) seals on entry and holds a chore of its own. A side room with enemies holds a chore too, but the hero can walk past it. (In a monster room Fight is already a choice; only lockOnEnter seals.)
  • Side rooms reuse the theme's beasts the way Zelda reuses Stalfos. Must-clear rooms each get a different chore.
  • Zelda bends to our rules, never the reverse. Every door into the boss room is a Boss door (the engine's rule); keys stay inside a temple; a temple must be finishable without Bombs from outside.
  • Bombs: temple shops already sell them by default (roomInteractions/shop.ts). A Bomb chest sits before any cracked wall on the only way forward. Crafting Bombs waits for the Workbench (board #1438).
  • Fight rooms can carry loot. When a fight is won, the beast drops what the room holds (a key, the Boss Key, gold, items) through the chest reveal. Random RPG drop tables are a follow-up with crafting (#1438).
  • One cell is one LTTP screen. A 2x2 hall becomes a 2x2 block of open cells, like LTTP's own pause map.
  • Small nudges are welcome. The owner's 4 extra Eagle rooms stay; the shop, save point, camp and Master Room are ours. The maps are a homage, not a tracing exercise, and can change later.
  • Chores wake floor by floor (owner, 2026-10-19). Wake on arrival (#1435) turns a shipped temple's monster rooms into real quests the first time a hero walks in. For the big LTTP temples that would wake 15 or more chores at once, each with a schedule and ambushes. Instead, walking in wakes the entrance floor; every other floor wakes the first time the hero reaches it. Side rooms reuse the beasts of the must-clear rooms on their own floor, so a floor wakes only its must-clear chores.
  • No "new layout shipped" notice. The owner did not edit the shipped temples; unedited copies refresh on their own.
  • Level 3 of Zelda 1 (shaped like 卍) was discussed for Water and dropped with the move to LTTP.

Sources (pinned, MIT)

Source Commit What we read
aerinon/ALttPDoorRandomizer (branch DoorDev) 6f390947159b6d2162b52a60aebfd969ccf9b922 Doors.py (every door: supertile, wall, slot, kind), Regions.py (areas, their doors and chest locations), source/dungeon/EnemyList.py (vanilla enemies per supertile with tile x/y and area)
TheVGLC/TheVGLC, The Legend of Zelda/ 0e86a8f31f20ecad4eaa5741ff061af88767f7fb Graph Processed/LoZ_1.dot (Level 1 rooms tagged enemy/key/item/puzzle/boss/triforce, doors tagged locked/bombable/shutter), Original/tloz1_1.png (the Eagle map)

The three randomizer files and the one VGLC graph are copied under tools/temples/classic/sources/ with both MIT licence texts and the commit ids. The app never reads them; only the converter does.

The temples

Temple (id unchanged) Region Theme Source Size (rough)
Wind (wind-temple) plains quick easy chores Zelda 1 Level 1, the Eagle 17 Eagle rooms + the owner's 4
Earth (earth-temple) forest plants, yard, pets LTTP Skull Woods ~30 areas
Water (water-temple) islands bathroom LTTP Swamp Palace ~50
Shadow (shadow-temple) swamps kitchen LTTP Palace of Darkness ~54
Fire (fire-temple) mountains cooking, big jobs LTTP Turtle Rock ~57
Light (light-temple) desert school, homework LTTP Desert Palace ~31
Ice (ice-temple) tundra laundry, fridge LTTP Ice Palace ~51
Storm (storm-temple) clouds bedroom, clutter LTTP Tower of Hera ~27
Moon (moon-temple) moon bedtime, self-care LTTP Ganon's Tower ~92

"Areas" are the randomizer's regions (ledges and sub-rooms), counted roughly; the converter reports the real cell counts. Ganon's Tower is the biggest by far; if it plays too long, Misery Mire is the fallback for Moon. Temple ids, names, regions and themes do not change, so routes, the world map and docs/Quest-Rooms-Spec.md section 10 still hold.

The converter (LTTP to JSON)

tools/temples/classic/convert.py reads the pinned sources and writes one file per dungeon to tools/temples/classic/dungeons/<temple-id>.json. The JSON files are committed; nothing at build time or run time needs Python.

Cells. A supertile is 2x2 screens. Each screen is one cell. A supertile's position comes from its id (row id >> 4, column id & 0xf), which is how LTTP itself finds a neighbour (walking north from 0xc9 lands in 0xb9). Screens that belong to one room are joined by open sides; screens of different rooms inside one supertile are split by walls. An enemy belongs to the screen its tile x/y falls in (the split is at 0x20).

Floors. Rooms joined by plain doors share a floor. Stairs (Up/Dn doors) move one floor up or down; holes (regions' drop-down exits) move down. The converter numbers floors from that graph (B2, B1, 1F, 2F...) and lays each floor out on its own grid, trimmed to the cells in use.

Doors. Each side between two cells is one of open, small-key, big-key, bomb, shutter, item (a gap LTTP gates with an item: hookshot, boots, torches, flippers) or wall, both ends agreeing.

Contents per cell. enemies (count), chest (map, compass, big-key, big-chest, small-key, other), keyDrop (small or big, carried by an enemy), potKey, boss, puzzle (switch, torch, peg), stairs, hole, entrance.

Where the tables cannot place something (an area that spans screens in an odd way, a chest with no screen), the converter stops with a message naming it, and a hand-written tools/temples/classic/overrides/<temple-id>.json settles it. The converter prints, per dungeon: cells per floor, must-clear rooms, side rooms, keys against locked doors.

Wind is converted the same way from LoZ_1.dot: its rooms are matched onto the Eagle's 17 cells and the bow basement, keeping every graph edge an orthogonal neighbour or the stairway (a node that fits no cell is reported, never guessed). The match is checked into dungeons/wind-temple.json; the owner's 4 east rooms are added as an override, unchanged from today's wind-temple.ts.

Dressing (JSON to temple)

tools/temples/classic/dress.ts (run with esbuild the way tools/temples/regenerate.ts is) turns each JSON into src/lib/engine/temples/<temple-id>.ts, with a header saying it is generated and from what. regenerate.ts stops writing the eight temples (the Temple Creator's Generate keeps using the generator).

Rooms

Converted cell Our room
entrance entrance
shutter doors + enemies, or an enemy keyDrop must-clear monster, lockOnEnter, its own chore; a key drop becomes the room's drops
a shutter room guarding the Big Key chest or the Big Chest miniboss, lockOnEnter
any other cell with enemies side monster, no seal, a reused theme beast
boss boss, lockOnEnter, the theme's boss chore; the master room (Pegasus chest) in a free cell behind it
big-key chest loot { chest: 'dungeon', dungeon: 'master-key' }
big-chest loot holding the temple's relic (today's relic per temple, e.g. Gaia's Hammer)
map / compass chest loot map / compass
small-key chest loot key
potKey key room (a key lying in the open)
other chest loot gold or consumables; a Bomb chest where a cracked wall needs one
puzzle puzzle (our glyph riddle); the door it opened in LTTP is a plain door
empty cells our extras: one shop nearest the entrance, one save, one camp per floor; the rest empty
stairs stairs-up / stairs-down
hole pit when it drops one floor; a teleport when it drops further

One cell holds one room type. A fight that holds a key is one cell with drops, never a key moved next door.

Doors. open is a door; small-key is locked; big-key is masterlocked; every side into the boss room is masterlocked whatever LTTP has; bomb is bombable; shutter is a plain door whose room seals (lockOnEnter); item is a plain door; wall is a wall.

Cracked walls on the way. When the solver (which treats bombable as a wall) cannot finish a temple, each cracked wall on the only way forward gets a Bomb chest in a reachable cell before it. The shop already sells Bombs.

Chores. The quest plan per temple moves from regenerate.ts into dress.ts and grows to cover every must-clear room with a different beast of the theme. Difficulty: must-clear monsters 1-2, minibosses 3-5, the boss 8-13; the easiest go nearest the entrance (path distance, stable sort), as in docs/Quest-Rooms-Spec.md section 11. Side rooms cycle through the beasts of the must-clear monster rooms on their own floor (a floor with none borrows the nearest floor's), so they never add a chore of their own. Where a theme has fewer beasts than must-clear rooms, the room repeats one (marked in the plan) until the art lands.

Waking floor by floor

src/lib/services/temples/wakeOnEntry.ts today wakes every monster room of a shipped temple, once per hero per temple, remembered in questEffects.templesWokenAt[templeId]. It changes to once per hero per floor:

  • Walking in wakes the monster rooms of the entrance floor. Arriving on another floor (stairs, a pit, a teleport) wakes that floor's, the first time.
  • The memory key becomes ${templeId}@${floor}. A legacy key templeId counts as the entrance floor woken.
  • Everything else stays as #1435 built it: one tq- quest per beast, a beast another hero woke takes this hero in as an assignee, the family's own quests are never joined, a Game Master's profile wakes nothing, minibosses and bosses never wake by themselves, and the Temple Quests tab can still wake any template by hand.
  • A floor that wakes beasts says so with an in-world reveal ("Three beasts stir on 2F"), never an Ionic alert, in the temple's own accent (the in-world UI rules). A floor with nothing new to wake says nothing.
  • A one-floor temple (Wind) wakes everything on entry, exactly as today.

Fight-room drops (engine)

  • content.drops on a monster, miniboss or boss room has the shape of a chest's content (the fields chestLoot in src/lib/services/temples/ChestLoot.ts reads): a dungeon item (key, master-key, map, compass, a relic), items, gold.
  • On a win the beast drops it: the hero takes it through the chest reveal on returning to the room.
  • When the room has no fight for this hero (cleared, done at home, pending, away, not theirs) the drop lies in the room and the sheet offers "Take it". A key is never stuck behind someone else's chore.
  • Once per run, filed in TempleState.openedChests under its own key (${level}:${row},${col}:drop) so it never collides with "Search the room". A restored daily chore does not drop a second key; a new run (blood moon) restocks.
  • src/lib/services/temples/roomGrants.ts counts drops (smallKeysGranted, grantsBossKey, chestDungeonItem), so chest setup, the audit, the solver, previews and counts agree that a temple is finishable. Its header note that fight rooms never hand out keys changes.
  • The Temple Creator gets a Drops picker on fight rooms, reusing the chest's loot picker (RoomLootConfig.vue).

Rollout to families

planSeedSync (src/lib/engine/core/dungeons/importAllTemples.ts) already refreshes a stored copy the Game Master never edited and keeps an edited one. Switching a temple's layout clears every hero's saved run in that temple: they start at the entrance, chests restock, small keys and the Boss Key are gone, Pegasi already earned stay (as the Old House rebuild does).

Beast art

The converter's report gives, per theme, the must-clear rooms against the beasts the theme has. The gap becomes one art task for tools/art/beast_portraits.py. Art never blocks a temple: a must-clear room repeats a beast until its own is painted. Original beasts 001-093 are family art and are never overwritten.

Build order

  1. Fight-room drops: engine, roomGrants.ts, solver, audit, the room sheet, the Temple Creator picker. Stands alone.
  2. Converter and sources: convert.py, the pinned sources with licences, overrides, the eight LTTP JSON files and the report. Stands alone (runs beside 1).
  3. Dress step and the eight temples, one temple at a time, smallest first: Tower of Hera (Storm), Skull Woods (Earth), Desert Palace (Light), then Swamp, Palace of Darkness, Ice, Turtle Rock, Ganon's Tower. Needs 1 and 2.
  4. Wind's 1:1 pass: the Eagle's contents and doors from LoZ_1.dot, the owner's 4 rooms kept. Needs 1 and the converter's Wind match.
  5. Beast art list from the converter's report.
  6. Floor-by-floor waking: wakeOnEntry.ts, the arrival hook in the temple screen, the reveal. Stands alone; it must land before the first LTTP temple ships (step 3).

Testing

  • Drops: jest specs for the win drop, the drop lying there when there is no fight for this hero, once per run (and restocked by a new run), the separate spent key, and roomGrants counting drops.
  • Each converted temple: every token has a room, sides agree at both ends, the entrance is a real cell, every beast is a library beast of the temple's theme, every door into the boss room is a Boss door, and tests/unit/ShippedTemplesAudit.spec.ts solves it without Bombs from outside. tests/unit/WindTemple.spec.ts is the model.
  • Converter: the JSON files are checked by a jest spec of their own shape (cells, floors, both-ends doors).
  • Locally: one spec at a time with chemx test <spec>; the full suite runs on the PR (CI).
  • Waking: jest specs for the entrance floor waking on entry, another floor waking on first arrival (stairs and pit), no second wake on a floor, the legacy key read as the entrance floor, and a one-floor temple waking whole.
  • One Cypress run through Tower of Hera (the smallest), entrance to Master Room.

Out of scope

  • Random beast drop tables and Workbench crafting (#1438).
  • Zelda 1's Second Quest.
  • A "new layout shipped" notice for edited copies.
  • LTTP's item-gated paths (they open as plain doors) and its puzzles' mechanics (they become glyph riddles).
⚠️ **GitHub.com Fallback** ⚠️