Skip to content

Temple Generation Contract

Xopher edited this page Oct 22, 2026 · 5 revisions

Temple Generation Contract

Status: draft 1, 2026-10-10. Owner: the game design. Implementers: src/lib/engine/dungeons/generator/.

A temple is a Zelda-style dungeon the hero clears while doing chores. Today every temple is hand-authored (src/lib/engine/temples/*.ts). This contract says what a generated temple must look like, which rules make it impossible to get stuck, and how a temple resets. The hand-authored temples must satisfy the same rules, so the solver doubles as a lint for them.

1. Vocabulary

Term Meaning
Temple Dungeon from src/lib/engine/core/dungeons/types.ts: id, name, entrance [floorIndex,row,col], maze { floor: char[][] }, rooms { char: Room }. Generated temples use the same shape so registerCustomTemples accepts them.
Room One cell. type is one of the roomTypes.ts kinds. locked { north,south,east,west } marks locked door sides. content holds the chest, beast or shop.
Door A passable edge between two non-wall cells. A locked door needs a small key; the boss door needs the master key.
Small key Consumable. Found in key chests (K___) or dropped by a monster room. One key opens one locked door, after which the door stays open.
Master key (boss key) Not consumable within a run. Opens the boss door only. Found behind the mini-boss or in a chest placed after the mini-boss on the critical path.
Critical path The ordered rooms the solver must pass: entrance → (keys/locks) → mini-boss → master key → boss door → boss → Pegasus chest → gate.
Key item Permanent equipment the temple hands out (map, compass, the temple's tool such as Lantern/Hook Shot/Bombs, the Pegasus). Kept across resets.
Blood moon The temple's reset. Beasts return, chests with consumables refill, small keys and the master key are forgotten, doors re-lock. Key items and the "cleared" badge stay.
Theme What the temple stands for in the family: a room of the house or a category of chores (Kitchen, Yard, Homework). Monster rooms of the temple draw quests from that theme.

2. Rules (R-numbers are the test names)

  • R1 Connected. Every non-wall, non-void room on every floor is reachable from the entrance when all doors are open.
  • R2 Keys before locks. Walking from the entrance and opening doors only with keys already collected, the hero can always reach another key or the next critical room. Formally: the solver (section 4) finds a path to the gate.
    • Walls and cracked walls (R2 note). A side drawn as a wall (room.barriers, on either end of the way) is no door to the solver. A side drawn bombable is a door the solver opens only once the hero has reached a Bomb source inside the temple (isBombSource in roomGrants.ts: a shop room, or a chest or fight-room drop holding a Bomb), so a temple is still finishable without Bombs brought from outside; a cracked side into a boss room stays a Boss door. The engine's doorKind and the solver's graph both ask barrierBetween (authoredDoors.ts).
  • R3 Key count. The number of small keys placed in the temple ≥ the number of locked doors on the critical path. Extra locked side doors are allowed only if the keys for them are reachable without spending a critical key (the solver checks the worst order: it may spend keys on side doors first, so the generator must place one spare key per optional lock or place optional locks after the surplus).
  • R4 Boss key placement. The master key is in a chest (a loot room whose chest is a Dungeon Item chest, chest: 'dungeon') or a Key room whose keyId is master, reachable only after the mini-boss room. It is never behind the boss door. It is never in the mini-boss room's own chest fields: a fight room has no chest in play (the engine makes chests for loot rooms only), so such a key would never be handed out; a fight room hands a key out only through its drops (content.drops, counted by roomGrants.ts; play hands drops out from Classic Dungeon Temples plan Task 3). The audit and the solver count only what play grants (roomGrants.ts, shared with DungeonManager.initializeChests): a key written into a room that grants none is not a key.
  • R5 Auto-fights. Entering a mini-boss or boss room starts the fight at once (content.lockOnEnter: true, the room seals, the fight is the room's bound quest). Winning opens the doors. Losing returns the hero to the entrance with the room uncleared.
  • R6 Boss door. Exactly one boss room per temple, with exactly one door from the rest of the temple. Every door into the boss room is the boss door, whatever the room's flags say (the engine's rule, isBossDoor): only the master key opens it, small keys cannot. locked.master: true on the boss room is kept for reading, not read: the generator emits it and the Temple Creator's Boss Lock writes it, but neither the engine nor the solver looks at it. The Pegasus chest and the gate lie beyond the boss room.
  • R7 Pegasus. Exactly one Pegasus chest per temple, after the boss. Collecting it marks the temple cleared and unlocks the next region's travel.
  • R8 Floors. Stairs come in pairs (U on floor n matches D on floor n+1 at the same row/col). Every teleport circle leads somewhere: the author's target if it names one, else the next circle on its floor (wrapping), else, alone on its floor, the temple's next circle (teleportTargetFor, one rule for the engine and the solver); a circle with no other circle anywhere leads nowhere, which the solver reports. Circles are not pairs, so a ride one way need not come back. A pit drops to the same row/col one floor down, which must be a non-wall room; a pit may instead name its landing (targetLevel/targetCoords), honoured only when that floor exists and lies strictly below the pit (pitDropOf in floorOrder.ts, one rule for the engine and the solver), so an old own-floor seed falls the old way. A flight of stairs may be locked (content.locked: 'small' | 'master' on the room it climbs FROM): its first climb in a run spends a small key, or needs the Boss Key without spending it; the landing climbs back freely; the solver links that stairs door with the same lock and the Temple Creator's counts add a small-locked climb as one lock.
  • R9 No dead keys. Every small key is reachable before the boss door (a key behind the boss door is a generation error).
  • R10 Size. rows × cols per floor in 4..9, floors in 1..3; room count per floor ≥ 6; at most 40 % combat rooms; one shop at most; one save room per floor at most; ≥ 1 heal or camp room per temple.
  • R11 Determinism. generateTemple(seed, options) is a pure function of its arguments. Same seed, same temple. The seed is stored on the temple (seed) so a family can share one.
  • R12 Hand-authored temples pass. The solver runs over src/lib/engine/temples/*.ts in a jest spec; any failure is a bug in the layout, not in the solver.

3. Generator (seeded)

  1. Plan the skeleton. Pick floors, rows, cols from the options or the seed. Carve a spanning tree on each floor with a drunk-walk/DFS from the entrance (the maze look), then add 10–20 % loops so the map isn't a pure tree.
  2. Lay the critical path. Choose the boss room as the farthest leaf from the entrance (by tree distance, across floors). Choose the mini-boss as a room on the entrance→boss path at 55–75 % of the distance. The Pegasus chest is a leaf adjacent to the boss room, created if needed. The gate is the entrance (the hero walks out) unless the options ask for a separate GATE.
  3. Lock the boss door. The boss room gets locked.master (a note, see R6: the engine bars every door into a boss room whether or not the flag is there).
  4. Place the master key. Always in a loot room (a Dungeon Item chest { chest: 'dungeon', dungeon: 'master-key' }) of its own past the mini-boss on the critical path: a leaf whose tree-ancestor is beyond the mini-boss and before the boss, else any free room past the mini-boss. The mini-boss room itself holds no chest (R4); choosing the mini-boss therefore needs a room past it. The old bossKeyChest option is ignored (the shipped temples still record it in their header).
  5. Place small locks and keys. For k = options.locks (1..3): pick an edge on the critical path between entrance and mini-boss; lock it; put a key chest in the subtree before that edge (never behind it). Repeat so each lock has its own key earlier than it. Optional side locks: lock a side-branch edge and add one extra key chest before it (R3).
  6. Dress the rest. Walk the remaining rooms: 25–35 % monster (q__q/Q__Q, bound to quests of the theme), 10 % loot (consumables), 5 % trap, 5 % puzzle, one shop, heal/camp near the mini-boss, save rooms near stairs, map and compass chests in the first third.
  7. Verify. Run the solver. If it fails (it shouldn't), re-run step 5 with the next sub-seed, at most 8 times, then throw: a thrown generator is a test failure, never a shipped temple.

Options: { theme, floors?, size?: "small"|"medium"|"large", locks?: 0..3, element?: TempleElement, quests?: QuestRef[] }.

4. Solver (the airtight part)

State = (floor, row, col, smallKeys, hasMasterKey, clearedMiniBoss, openedDoors). Breadth-first search from the entrance. Transitions: move through an open door; move through a locked door if smallKeys > 0 (spend one, mark the door opened for every state); move through the boss door if hasMasterKey; entering a key chest adds a key; entering the master-key chest sets hasMasterKey (only if its room is reachable, which with R4 implies the mini-boss was passed); stairs, teleports and pits move the hero. Result: solvable and the critical order found. The solver explores the worst key order too: locked side doors are tried first so a layout that only works when the hero is clever is rejected (R3).

The solver is also the map-hint engine: it can tell the compass which locked door the hero can open right now.

5. Reset (blood moon)

resetTemple(progress, templeMeta) returns new progress with: clearedRooms emptied, openedDoors emptied, small keys 0, master key false, chests of consumables marked unopened; keyItems (map, compass, the temple tool, the Pegasus) untouched; timesCleared kept. The reset fires when the Game Master schedules it (weekly by default) or when the theme's quests roll over; the screen shows "The blood moon rises over the Wind Temple."

6. Themes

A temple binds to a theme (themeId): a house room or a chore category. Monster rooms draw their fight from the theme's open quests; when the theme has none, the room is a free fight paying AP only (the existing rule). The theme also picks the floor art, the music (BGM.temples) and the element.

7. Acceptance

  • tests/unit/TempleSolver.spec.ts: R1–R9 on fixtures (one solvable, one with a key behind its lock, one with the master key behind the boss door, one with an unreachable room). R12 over every authored temple.
  • tests/unit/TempleGenerator.spec.ts: 200 seeds × small/medium/large all solvable; determinism; option bounds.
  • The Temple Creator in the Admin console gains a "Generate" button (later) that calls the generator and shows the solver's critical path on the preview.

Table of Contents


Home - See Pages

Developer Docs

Start Screen

User Management

My Portal

My Home

The City (Hometown)

The World Map

The Dungeon

Game Master

Settings

Support & Glossary

About XP

Clone this wiki locally