Quest Rooms Spec - xopherdeep/do-it-for-the-xp GitHub Wiki

Quest Rooms: temples full of real chores

Design spec, 2026-10-12. Every decision, including all nine themes, was approved by the owner on 2026-10-12 (see section 15). No code ships with this document.

1. Summary

A temple's combat rooms hold quests instead of loose beasts. Each quest already brings its beast, its checklist (the hero's attacks), its schedule, its difficulty and its rewards. Clearing a room completes the quest, and completing the quest from the board clears the room. A room's beast returns when the quest is due again. The game never gets harder over time. When a hero dodges a chore, the economy that already exists pays for it: running from a quest fight costs one strike and leaves a grudge on that beast, a hero who hits 0 HP is carried to the Hospital, treated, and billed, and stays grounded (no privileges) until the bill is paid at the Gold Bank. Chores are how the gold comes in.

2. Goals and non-goals

Goals

  • The temple and the quest board run on one due-date rule: the room state comes from the same board item, so the two can never disagree.
  • Temple size and room tier come from difficulties the parent already sets.
  • All nine shipped temples are rebuilt around chore themes, using quests drawn from the 100-beast library.
  • Running away always works but always hurts. Doing the chore is always the cheapest path.
  • The strike clock matches the real size of the chore.
  • Classic beast rooms stay available for families who want them.

Non-goals

  • No growth over time, no decay, and no new meter. HP, MP, GP and Powers are the only levers.
  • No co-op battle. A party shares a room's state, but one hero fights at a time.
  • No engine for "Rotate Out" turns; until one exists, rotate quests play as individual quests.
  • Generated temples (gen-*) are not made quest-aware in this pass. That is a follow-up.
  • Random free encounters do not change, except that an overdue quest can take one encounter's place (5.9).

3. The owner's decisions (2026-10-12)

  1. Temple combat rooms hold quests, not beasts. A quest carries its beast (beastId), checklist, schedule, difficulty and rewards. Beast rooms are retired from the editor's default flow but stay available as "Beast room (classic)".
  2. Tier comes from difficulty on the Fibonacci slider (the quest form's "Why" tab; DIFFICULTY_NAMES; updatePoints sets xp=d100, gp=d10, ap=d): 1-2 is a monster room, 3-5 a miniboss, 8-13 a boss. A temple's size is the sum of its quests' difficulties. A full temple is 21+.
  3. A room restores on its quest's schedule. Clearing the room completes the quest, and completing the quest on the board clears the room. When the quest is due again (daily means the next day, weekly the next week), the beast is back. The board and the temple use one due-date rule.
  4. Room state is per hero but shared in a party. A quest assigned to several heroes attacks every assigned hero ("everyone gets hit with dishes if it's assigned to all").
  5. A quest with no beast gets the one the library suggests (librarySuggest.ts). The GM can change it.
  6. No growth mechanic. The quest's beast attacks the hero at its due interval (wall-clock encounters, ambush on return, the deadline clock). Avoiding chores is punished through HP and MP only.
    • Run-away mechanic (approved; the two numbers are tunable):
      1. Leaving a fight always works and always costs one full strike, the parting strike. A run can no longer fail at random, because a failed run could trap a kid who cannot do the chore right now. Thief, Ninja and agility keep their identity by softening or dodging the parting strike. Free fights have no parting strike.
      2. Grudge. Each flee puts one grudge mark on that quest's beast for that hero, up to 3. Each mark adds +25% strike damage in later fights with that beast (tunable). The marks show on the beast. Completing the quest clears them. Nothing grows by itself over time.
      3. 0 HP means the Hospital (owner, 2026-10-12: "when they get to 0 they are in the hospital, essentially grounded, and they get fined a hospital bill they have to pay"). The hero is admitted automatically, treated to full HP and MP, and billed REVIVE_COST. The bill comes out of the wallet first, then savings, and the rest is owed at the bank. While any of the bill is unpaid the hero is grounded: no privileges. Fights, quests and the board still work. Paying the bill off at the Gold Bank lifts the grounding.
      4. Doing the chore is always the cheapest path. Finishing before the clock runs out costs no damage.
      5. The strike timer must be accurate. A one-dish Dish Goblin gets a short clock. A whole-sink boss gets a long one.
  7. Rebuild the nine shipped temples around themes, using quests from the beast library. These themes are confirmed: Wind = quick easy chores, Earth = plants and yard, Water = bathroom, Shadow = kitchen. The owner confirmed the rest on 2026-10-12: Fire = cooking and big jobs, Ice = laundry and fridge/freezer, Light = school, Moon = bedtime and self-care, Storm = bedroom, clutter and toys. A beast's library element is an art choice (10 beasts per element), so the theme is its own field.

4. What the code does today

Area Where Today What changes
Quest record src/lib/databases/AchievementDb.ts (Achievement) beastId, type (asNeeded/individual/collaborate/rotate/compete), assignee[], schedule fields, timesOfDay, difficulty, xp/gp/ap, requiresApproval, draft + estimatedMinutes?
Due-date rule src/lib/engine/quests/QuestSchedule.ts getQuestWindow (once/daily/weekly/custom; weekly with repeatOnDays gives one-day windows), getQuestProgressFor counts this hero's non-rejected log entries in the window; pending entries count toward the allowance party-aware counting; deadline on a window's last day
Board src/lib/engine/quests/QuestEngine.ts getQuestBoardForUser open / done / notDue + pending + overdue; drafts hidden; Gale's Wind hides everything also feeds the temple
List rows src/lib/services/quests/QuestList.ts assignee, due label, status "Done by Mia" for shared quests
Completion src/lib/services/quests/QuestCompletionService.ts completeQuest logs pending or approved (Tesla's Bolt trusted hour skips approval); recordBattleCompletion logs an already-paid battle win + clear grudge
Log src/lib/databases/QuestLogDb.ts QuestLogEntry {questId, userId, completedAt, status, source} none
Alerts src/lib/services/notifications/questWatch.ts due-soon and overdue notes every 5 minutes names the ambushing beast
Encounters src/lib/services/battle/encounterClock.ts, src/lib/store/stores/battle.ts, src/router/_Guards/battle.guard.ts a random free fight every 120-240 wall-clock steps on town, map and region routes; pendingAmbush waits for an away hero an overdue quest's beast takes the slot
Temple layouts src/lib/engine/temples/*-temple.ts (eight generated by tools/temples/regenerate.ts PLANS; wind-temple.ts hand-written), templeRegistry.ts monster rooms carry monsterType and xp/gp/ap; Wind's boss holds beast-trash-troll rooms carry quest templates
Room beast src/lib/services/temples/RoomBeast.ts pickRoomBeast authored beast, else monsterType name match, else the temple element's beasts by (row+col) dormant rooms use the template beast
Run state src/lib/engine/core/dungeons/TempleSystem.ts, templeSystem/runState.ts, heroRuns.ts, floors.ts per-hero clearedRooms set keyed level:row,col; lockOnEnter seals on entry only when the room is unvisited (floors.ts moveToPosition) quest-room keys derived from the board
Blood moon templeReset.ts, templeSystem/bloodMoons.ts, bloodMoonSchedule.ts forgets cleared rooms, keys and doors, refills chests quest rooms follow their schedules instead
Seed sync src/lib/engine/core/dungeons/importAllTemples.ts layoutSignature covers entrance, maze and rooms; unedited copies refresh, edited copies are kept shipped rebuild reaches unedited copies
Live refresh src/lib/engine/temples/templeChanges.ts, templeFreshness.ts (commit 30bb2b68) editor saves play without a reload none
Audit src/lib/services/temples/templeAudit.ts entrance, exit, boss, boss key, master room (+ optional map, compass) + optional size check
Room sheet src/lib/services/temples/RoomInteractions.ts (combat case) cleared means "Nothing left here but the smell", else Fight quest statuses
Battle from a room src/app/Console/BattleField/battle/encounterLoader.ts loadTempleEncounter uses room beasts and authored rewards, with no quest; loadQuestEncounter uses beast + checklist + questRewards a temple room can carry a quest
Outcome src/app/Console/BattleField/composables/useBattleOutcome.ts approval hold and log only when ctx.kind === "quest"; temple wins call markRoomCleared; a defeat leaves LIMP_HOME_HP = 1 and goes to the game-over splash (src/views/XpGameOver/XpGameOver.vue); a temple trap does the same (roomInteractions/hero.ts hurtHero, useRoomOutcomes.ts) quest-aware; 0 HP admits to the Hospital
Strikes src/lib/services/battle/battleService/strikeMath.ts strikeDamage ≈ 12% max HP × attack/10 × roll, × strikeFactor (≤1), × brace; escapeChanceAgainst 75% / Thief-Ninja 90% / miniboss 50% / boss 25% + agility, cap 95% + grudge; escape odds retire
Run src/lib/services/battle/BattleService.ts handleRun a success costs nothing; a failure costs one free strike and keeps the hero in the fight; free fights always escape parting strike
Clock battleService/attackClock.ts per-chore minutes = enemy.attackCooldown (default 3) × cooldownScale (Fire Wand 1.5) + class minutes (Monk +1, Martial Artist +2); clock = remaining chores × minutes; every finished chore restarts it; Ice Wand +10 min, Chronos freezes 15 min (BattleEffects.ts) quest sets the base
Powers src/lib/services/abilities/AbilityService.ts (useAbility refuses only on mp), docs/superpowers/specs/2026-10-11-powers-privilege-ledger-design.md R3 no HP or debt gate; battle casts never go through useAbility + grounded refusal
Healing src/lib/services/hometown/Lodging.ts heal 50 GP, revive 100 GP (full HP and MP), hotel 10 GP, free bench 25% once a day admission reuses the revive
Purse and bank src/lib/services/gp/wallet.ts (GpPurse {wallet, savings, debt, limit}, payDownDebt from the wallet), GPService.ts, stores/user/resources.ts, GoldBank/GoldBank.ts + ATMModal.vue, UserHud/MyGoldPoints debt exists and can be paid down, but no gameplay ever creates it + hospitalBill
Party src/app/Console/MyPortal/MyParty.vue, quest assignee[] the family is the party; no party record none
Library src/lib/services/core/beastLibrary.ts (from tools/art/beast_roster.json), librarySuggest.ts room, age, element; suggestBeasts, adoptLibraryBeast (stable ids) + theme
Starter world src/lib/services/core/StarterWorld.ts 12 open-bounty quests, difficulty 1-3, own reward table binds to rooms by beast
Editor XpTempleCreator/hooks/useTempleRoomEditor.ts (ROOM_CATEGORIES), components/RoomTypeModal.vue, RoomMonsterConfig.vue, XpRoomEditorPage.vue, hooks/grid/roomSymbols.ts (promoteToUniqueSymbol; shared M001/MINI tokens) Combat = monster, miniboss, boss, trap; beasts picked via xp-bestiary-select quest room flow

5. Rules (pure modules, each with a jest spec)

5.1 Tier and temple size (src/lib/services/temples/questRooms.ts)

  • questTier(d): d ≤ 2 is a monster, 3 ≤ d ≤ 5 a miniboss, d ≥ 8 a boss. Legacy values in between round down.
  • templeSize(rooms) is the sum of the difficulties of the quests the rooms resolve to. A dormant room counts its template difficulty and shows it greyed as "if woken". TEMPLE_FULL_SIZE = 21. The size is informational: a meter and an optional audit check that never block a save.
  • In a fight the tier sets enemy.type = "miniboss" or isBoss = true (boss theme and victory song through Encounter.boss). It also sets the beast's attack factor: 1 for a monster, 1.2 for a miniboss, 1.5 for a boss (tunable). This replaces the old "harder to escape a boss" odds.

5.2 Which quest a room holds

resolveRoomQuest(room, heroId, visibleQuests):

  1. content.questId (the GM's pick) resolves to that quest if it exists and is not a draft.
  2. Otherwise content.quest (a shipped template) resolves to the first live quest visible to the hero whose beast matches template.beastId. A quest's beast is its beastId, or the top suggestBeasts(name) hit when it has none (decision 5). Open quests win over pending ones, and pending over done. This is how existing family quests (for example the starter "Banish the Dish Goblin") fill shipped rooms without a layout edit or duplicate quests.
  3. Otherwise the room is dormant. It plays as a classic beast room with the template's beast (adopted into the bestiary on first use) and today's temple rewards.

A classic room (content.beasts, with no questId and no quest) is never resolved. A questId that no longer resolves and has no template leaves an empty lair (no fight), and the editor flags it.

List rooms. A room may hold several quests in content.questIds; a single questId reads as a list of one. Each hero meets the one open for them (open before pending before done before away, ties to list order). When none of them is theirs, the first live one sleeps there (notYours); when none is live, the template fills the room, else it is an empty lair. The Game Master's readers (the quest tab, the size meter and the editor card) read a list room by its headline quest: the biggest live one, ties to list order. A sealed (lockOnEnter) room holds one quest, because a hero coming back from a won fight is resealed in an uncleared fight room. Code: src/lib/services/temples/questRooms.ts (roomQuestIds, headlineQuest, resolveRoomQuest).

5.3 A room's status for a hero

The status reads the hero's board item: getQuestBoardForUser(hero, now, _, questEffects).

Status When Room says Doors Fight
lurking board open "A Monster approaches!" + quest name + due label + grudge marks seals if lockOnEnter yes
pending done and pending "Tied up. The Game Master is checking." open no
cleared done "Nothing left but the smell. Back tomorrow" (the next window) + Search open no
away notDue (not this day, snoozed, Gale's Wind, ended) "The lair is empty. Back Sat." open no
not yours quest not visible to the hero "Mia's Dish Goblin sleeps here." open no
dormant nothing resolved classic classic classic

The occurrence key ${questId}@${dayKeyOf(window.start)} is derived and never stored. A room restores when the window holding its last completion closes: a daily quest the next day, a weekly quest the next 7-day window, a repeatOnDays quest the next listed day, a custom quest the next period. A once quest never restores. A quest done several times a window (timesOfDay) lurks until its allowance is used, exactly as the board reads it. A rejected claim stops counting, so the beast is back in the same window.

5.4 Shared and individual quests

isSharedQuest(q) is true for type collaborate (Team Effort) and compete (Rat Race). For a shared quest, getQuestProgressFor counts completions by any assignee, so one hero's clear clears the room and the board row for every assignee ("Done by Mia"). Only the hero who did it is paid. All other types count per hero, as today. Section 8 explains why.

5.5 The strike clock matches the chore (src/lib/services/quests/questClock.ts)

  • The quest gains estimatedMinutes?. When it is absent, defaultMinutes(difficulty) fills it in (tunable):

    d 1 2 3 5 8 13 21 34 55+
    min 3 5 8 12 20 30 45 60 90
  • Every fight that carries a quest (board battles, quest rooms, ambushes) sets enemy.attackCooldown = minutes / max(1, checklist length). The first clock is then the whole estimate, and each finished chore restarts it for the remaining share. A hero who works at the estimated pace is never struck (decision 6.4). Chores already ticked off earlier today (restoreCompletedTasks) shorten the clock pro rata.

  • The existing modifiers apply unchanged on top. Class minutes (Monk +1, Martial Artist +2) are added per chore, the Fire Wand multiplies later clocks by 1.5, the Ice Wand adds 10 minutes to the running clock, and Chronos freezes it for 15 minutes. These are paid or earned edges. On a 3-minute chore they make the beast harmless, and that is fine.

  • Fights with no quest (free and classic) keep the beast's attackCooldown, or 3 minutes.

5.6 Running away: the parting strike

handleRun always succeeds. escapeChanceAgainst and the 75/90/50/25 table retire.

  • Free fights (alwaysEscapes: random overworld beasts, room-editor test fights) have no parting strike.
  • Every other fight: before "Got away safely!", one strike: strikeDamage(maxHp, attack, braced, roll, braceFactor, strikeFactor, grudge). It never takes HP below 0. A parting strike that empties HP admits the hero to the Hospital (5.8) on the way out: "You stagger away... and wake up in the Hospital."
  • Softened (braced = true, so braceFactor applies: half for most classes, a quarter for a Warrior's wall) when the hero was already bracing from Defend, or when the class has runChance set (Thief and Ninja slip away). The two do not stack.
  • Dodged entirely in four cases: the clock is frozen (Chronos); a Thor's Hammer skip is owed (it is used up); the Ninja's skipFirstStrike is still owed; or a roll falls under min(0.5, runChanceBonus) (trained agility, 1% a point; the 0.5 cap is tunable).
  • Leaving a fight that carries a quest adds one grudge mark (5.7), even when the strike was dodged.

5.7 Grudge (src/lib/services/battle/questGrudge.ts)

  • Each hero profile gets questGrudges?: Record<questId, { marks: 0..3, lastFledAt?, lastAmbushAt? }>, persisted with persistUser.
  • Leaving a quest fight sets marks = min(3, marks + 1). grudgeFactor = 1 + 0.25 × marks, so the maximum is 1.75 (tunable). It multiplies every strike of that quest's beast: clock strikes and parting strikes, in board battles, quest rooms and ambushes.
  • It goes in as a sibling multiplier, a new grudge parameter on strikeDamage that defaults to 1 and is clamped to [1, 1.75]. BattlePassive.strikeFactor is the wrong place: it is the hero's trained-strength share, clamped to 1 or below, and its specs pin that.
  • Cleared when a completion for that hero and quest is logged and not rejected: a board claim (pending or approved), a battle win, or a GM log. For a shared quest it is cleared for every assignee. A later rejection does not bring the marks back.
  • Shown as one to three red marks on the enemy card and the room sheet ("It remembers you ran"). The board row gets a small icon.
  • Nothing else changes the marks: no decay and no growth over time.

5.8 The Hospital, the bill and grounding

Admission. Whenever a hero's HP reaches 0 they are admitted at once and never left at 0. This covers a lost fight, a parting strike that empties HP, and a temple trap. Every path calls one service, admitToHospital(userId, cause) (new src/lib/services/hometown/hospitalAdmission.ts):

  1. Treatment. HP and MP are set to full, the same gains as quoteCare("revive"). LIMP_HOME_HP and the limp-home line retire.
  2. The bill. HOSPITAL_BILL = REVIVE_COST (100 GP, tunable). The pure chargeHospitalBill(purse, bill) takes it from the wallet first, then savings, and adds whatever is left to hospitalBill.
    • The bill is flat; it does not scale with level. Quest pay comes from difficulty (gp = d × 10), not level, so a level-scaled bill would grow while income stays the same. That would be the growth mechanic decision 6 rules out.
    • Bills stack: a second admission adds another 100.
  3. The moment. The arena (or the temple) fades on one line, "You were carried to the Hospital." Then the route goes to hospital-hub with ?admitted=1, where the nurse reads the bill: "Treatment: ₲100. ₲40 from your wallet, ₲60 still owed. You're grounded until the bill is paid at the Gold Bank." The game-over splash is no longer used by fights or traps.
  4. Notices. A hero notification and a Game Master notification, using a new XpNotificationKind "hospital" modelled on power-cast in AbilityService.tellTheFamily. A line goes in the family-chat room: "Mia was carried to the Hospital. Bill ₲100 (₲60 owed)."

Hospital debt is its own field. The purse gets hospitalBill. Reusing debt would make grounding depend on money that is owed for other reasons. A separate field keeps one meaning: owed > 0 means grounded. normalizePurse, Purse, stores/user/resources.ts and utils/User/stats.ts carry it, and isGrounded(purse) = hospitalBill > 0.

Grounded.

  • useAbility/castPower refuse with a new reason, grounded, checked first: "Grounded: pay your hospital bill at the Gold Bank." Battle spells never pass through useAbility, so they keep working.
  • Fights, quests, the board, temples and shops all work as usual. Chores are how the gold comes in.

Paying it off.

  • The Gold Bank's pay-down button (GoldBank.ts clickPayDebt, ATMModal.vue) pays the hospital bill first, then any other debt, from the wallet as payDownDebt does today. Savings come out through the existing Withdraw.
  • payHospitalBill(purse, amount) sits beside payDownDebt in wallet.ts.
  • Paying the bill to 0 lifts the grounding at once. A toast reads "Bill paid. You're free!" and the Game Masters get a "Mia paid the hospital bill" notification.

What the hero sees.

  • A GROUNDED chip on the HUD (UserHud/components/XpMainHud.vue) showing "Bill ₲60". Tapping it goes to the Gold Bank. In temples, where there is no HUD, XpVitalsBar.vue shows the same chip.
  • My Powers cards keep their Cast buttons disabled with the grounded reason.
  • The Gold Bank and MyGoldPoints show the bill as its own line above debt.

What a parent sees.

  • The "hospital" notification and the chat line at admission and at payoff.
  • A "Grounded · ₲60 owed" chip on the hero's card in the Game Master's dashboard profile switcher (XpDashboard/components/XpImpersonateProfile.vue).

The quest-completion revive and the Hotel and bench refusals from the earlier draft are dropped. Clearing grudges on completion (5.7) stays.

5.9 Quest ambushes at the due interval (src/lib/services/battle/questAmbush.ts)

  • When a random encounter springs, pickQuestAmbush(board, effects, grudges, now) looks for a quest that is all of these:
    • open and either overdue (isQuestOverdue; Chronos' pause and Gale's Wind already count) or anytime (no due time);
    • not pending;
    • assigned to or visible to the hero;
    • not ambushed by this hero in the last 60 minutes (lastAmbushAt, tunable).
  • The quest with the oldest deadline goes first. An anytime quest counts as due now, so it goes after every overdue one.
  • If there is one, the encounter becomes questBattleRoute(hero, quest.id) instead of a free fight. If the hero is away, pendingAmbush gains questId, and the OS alert names the beast. If there is none, the free encounter goes ahead as today.
  • Winning the fight completes the chore; Run Away means "not right now". Free fights (no waiting chore) pay 0 XP and 0 GP.
  • X's Invisibility still blocks every encounter.
  • Window deadline fix: a quest whose window spans several days is due at dueByTime on the window's last day. Today a weekly quest with a due time reads overdue every evening of its week. The board, questWatch and ambushes all share this one rule.
  • A quest with no due time ambushes at any random encounter while it is open. The quest form says so.

6. Data model changes

Record (file) Field Notes
Achievement (AchievementDb.ts) estimatedMinutes?: number strike clock; default by difficulty (5.5)
RoomContent (engine/core/dungeons/types.ts) questId?: string the GM's pick in the editor
RoomContent quest?: RoomQuestTemplate { beastId, difficulty, basicSchedule, repeatOnDays?, timesOfDay?, dueByTime?, customPeriodType?, estimatedMinutes? }; shipped default: binding key, dormant beast, wake template
LibraryBeast + roster JSON theme: TempleTheme `'wind'
Beast (BestiaryDb.ts) theme?: TempleTheme copied by adoptLibraryBeast; custom beasts may set it later
hero profile (stores/user.ts) questGrudges? 5.7
GpPurse (gp/wallet.ts, stats.gp) hospitalBill?: number 5.8; owed > 0 means grounded
XpNotificationKind (notifications/Notifications.ts) "hospital" admission and payoff
pendingAmbush (stores/battle.ts) questId? 5.9
Encounter (encounterLoader.ts) quest and roomKey together; grudge a temple room can carry a quest
BattleService setGrudge(f); parting strike when !alwaysEscapes 5.6
new templeThemes.ts TEMPLE_THEMES labels and blurbs for the editor and tab

TempleState and TempleProgressEntry get no new field. Quest-room keys in clearedRooms are re-derived on every sync, so a stale saved key does no harm.

7. Engine flow

  1. Entering a temple. initTemple/loadProgress loads the hero's board, then syncQuestRooms(templeId, hero, board, now) (new templeSystem/questRooms.ts) puts every quest room that is not lurking into clearedRooms and takes lurking ones out. Dormant and classic rooms are left alone. The sync runs again on return from a battle, on each room entry (with the cached board), and whenever the quest watch runs while the hero is inside.
  2. Seals. floors.ts seals on entry when lockOnEnter && !isRoomCleared, replacing !hasVisited. A restored beast seals again and the map keeps the room.
  3. Stepping in. useRoomBeast.ts + roomActions show the 5.3 status. A lurking room offers Fight.
  4. The fight. templeBattleRoute → loadTempleEncounter resolves the quest. If there is one, it builds the fight as loadQuestEncounter does: beast, checklist as attacks (the quest name when there is none), questRewards(quest), the tier, the clock (5.5) and the grudge (5.7). The encounter carries both quest and roomKey. A dormant room fights as today.
  5. Win = complete. If the quest requires approval, completeQuest(quest, hero, "battle") logs a pending claim and pays nothing. Otherwise commitRewards(rewardsForHero(questRewards)) + recordBattleCompletion. Both paths call markRoomCleared (unseal) and clear the grudge. The room's authored xp/gp/ap is ignored when a quest is present.
  6. Held claims. The room reads pending and lets the hero through. An approval pays at the quest's own rate (approveCompletion). A rejection makes the beast lurk again on the next sync.
  7. Defeat. admitToHospital(hero, "battle"): full HP and MP, the bill, then the carried-to-the-Hospital moment and hospital-hub. The temple run is saved, and the room's beast still lurks. The room seals again on the next entry.
  8. Run. The hero always leaves (5.6): parting strike, plus one grudge mark if the fight carried a quest. If the strike empties HP, the hero is admitted (5.8). A sealed room stays sealed, so the way on is still through the chore.
  9. Board ↔ temple. A claim on the board logs an entry, and the next sync shows the room cleared. A room win logs an entry, and the board shows the quest done. A quest battle from the board clears the room too.
  10. Restore. The window rolls over, the board item turns open, the sync takes the key out, and the beast is back.
  11. Blood moons. resetTempleProgress still forgets keys, doors, chests and classic and dormant rooms. Quest rooms ignore the moon: the sync puts back whatever their schedules say. Flee marks survive. XpBloodMoonModal.vue copy: "Quest rooms follow their chores; the blood moon brings back beast rooms, chests, keys and doors."
  12. Ambush. Overworld only (5.9). Temples have no random encounters.

8. Party rules

  • A quest attacks every hero it is visible to: its assignees, or everyone for an Open Bounty. Each hero's temple run, statuses and grudge are their own.
  • Heroes who are not assigned see a sleeping beast and pass ("not yours").

Open question: when one hero clears a shared quest, is it cleared for the party (a shared chore), or does each assigned hero fight it (an individual chore)?

Recommendation: let the quest's existing assignment type decide, so nothing new is asked of the parent:

type Name on the form Temple rule
collaborate Team Effort shared: one clear clears it for all assignees; the clearer is paid; grudges cleared for all
compete Rat Race shared: the first to clear takes the pay
individual Solo Mission each hero fights their own
rotate Rotate Out individual until rotation exists
asNeeded Open Bounty individual, visible to all (as the board pays it today)

Quests woken from a template default to individual. "Dishes assigned to all" as a Solo Mission hits every kid. As a Team Effort it is one sink for the family.

9. Editor UX

  • Room type modal (RoomTypeModal.vue, ROOM_CATEGORIES, roomTypeGuide.ts): Combat lists Quest room first (the default), then Trap. A "Classic beast rooms" disclosure keeps Monster, Miniboss and Boss exactly as today (RoomMonsterConfig.vue). "Quest room" is a guide entry, not an engine type: confirming it opens the picker.
  • Quest picker (new RoomQuestPicker.vue, a full-screen sheet in the Codex style, with controls in thumb reach):
    • Search at the top, then segments: This temple's theme (quests whose beast's theme matches; the default) | All quests | From the library.
    • Each row: beast portrait, quest name, difficulty name and number, tier chip (Monster/Miniboss/Boss), schedule (scheduleLabel), heroes (assigneeLabel), approval badge, and "no due time: never ambushes" when that applies.
    • "From the library" lists the theme's beasts that have no family quest yet. Tapping one opens a small form (heroes, daily/weekly, difficulty slider, approval) and creates the quest: name = the beast's chore, beastId adopted, type: individual.
    • The footer shows the temple size meter previewing the pick ("Size 14 → 22 · full at 21") and one Confirm button.
  • On confirm: writes content.questId, sets the room type from questTier, and promotes a shared M00x/MINI token to a unique symbol (promoteToUniqueSymbol). Picking a boss-tier quest when a boss already exists is allowed; the audit then shows "2 bosses".
  • Room config (new RoomQuestConfig.vue): the quest card, plus Change quest, Edit quest (opens the Compendium form), the lockOnEnter toggle, and Test fight (a free fight with the quest's beast; nothing is logged).
  • Temple Quests tab (tabs/XpTempleBeasts.vue becomes "Quests"):
    • The theme label ("Kitchen · Shadow") and the size meter.
    • Rooms grouped by tier: each row shows its quest or "Dormant: Dust Dervish, no quest yet", schedule, heroes, a per-hero status dot, and a tier mismatch flag ("Boss room holds a Miniboss quest (3)").
    • Wake quests: pick heroes and tick dormant templates. Creates quests tq-<templeId>-<slug> from the templates, with startsOn on this Monday, and adopts their library beasts. A beast that already has a live family quest is skipped and listed as "uses your quest".
  • Audit (templeAudit.ts): an optional size check that takes a difficulty lookup ("Size 17 of 21"). It never fails passed.
  • Quest form:
    • The "Why" tab (components/tabs/QuestRewardsTab.vue) shows the tier chip under the difficulty and an "About how long? [5] min" field. The field is prefilled from the difficulty until the parent edits it.
    • QuestTypeTab.vue prefills a beast-less quest with the library suggestion ("Suggested: Dish Goblin · Change").

10. Theme map

Temple (id) Region Theme Status Library beasts Combat rooms (monster/miniboss/boss)
Wind (wind-temple) plains quick easy chores (dust, sweep, small pickups) owner 13 6 / 2 / 1
Earth (earth-temple) forest plants, yard, pets owner (pets added) 16 5 / 1 / 1
Water (water-temple) islands bathroom owner 9 5 / 1 / 1
Shadow (shadow-temple) swamps kitchen (dishes, grease, trash) owner 12 7 / 1 / 1
Fire (fire-temple) mountains cooking and big jobs (garage, car) owner 7 4 / 1 / 1
Ice (ice-temple) tundra laundry, fridge and freezer, snow owner 11 3 / 1 / 1
Light (light-temple) desert school, homework, reading owner 10 5 / 1 / 1
Moon (moon-temple) moon bedtime and self-care owner 10 6 / 1 / 1
Storm (storm-temple) clouds bedroom, clutter, toys owner 12 5 / 1 / 1

Each of the 100 beasts gets exactly one theme in the roster (checked):

  • Wind: dust-bunny-king, cobweb-widow, dust-dervish, fluffalo, gravel-gnome, crumb-crawler, mustwisp, spill-sprite, mailbox-mimic, cushion-croc, bag-golem, fingerprint-phantom, dim-wisp.
  • Earth: weed-wyrm, meadow-minotaur, leaf-lion, dustbowl-djinn, droop-dryad, plop-golem, mud-puppy, scatter-sprite, litter-imp, compost-ogre, couch-potato, zoomie-zephyr, furball-fiend, kibble-kobold, clumpling, algae-ooze.
  • Water: the 9 bathroom beasts.
  • Shadow: dish-goblin, suds-serpent, cupboard-kobold, grease-gorgon, microwave-mimic, crumb-gremlin, gravy-ghoul, plate-pixie, stickyfoot-slime, garbage-gargoyle, trash-troll, mug-thug.
  • Fire: simmer-salamander, snack-snatcher, hangry-hob, junkpile-jotun, grime-griffin, backseat-beastie, cardboard-golem.
  • Ice: lint-lizard, laundry-lurker, wrinkle-wyvern, socktopus, hamper-hound, bedsheet-banshee, wardrobe-wraith, leftover-lich, freezer-yeti, slush-golem, pantry-phantom.
  • Light: homework-hydra, sum-sphinx, pop-quiz-poltergeist, paperwork-pixie, bookwyrm, sour-note-siren, desk-drake, backpack-boggart, lunchbox-lurker, grump-goblin.
  • Moon: fang-fairy, stinkwisp, night-owlbear, snooze-sloth, pajama-pooka, sniffle-sprite, grubgoblin, hairball-harpy, glowleech, scroll-troll.
  • Storm: clutter-chimera, under-bed-bogey, hoard-drake, toybox-troll, bed-basilisk, piece-poltergeist, shelf-elf, cable-kraken, junk-drawer-jackalope, mess-hog, shoe-shrew, cleat-creep.

11. Rebuild plan for the nine shipped temples

Superseded 2026-10-22: tools/temples/regenerate.ts is gone. The nine temples are now dressed from Zelda dungeons by tools/temples/classic/dress.ts, with each temple's quests in CLASSIC_PLANS (tools/temples/classic/dressPlans.ts); see superpowers/specs/2026-10-18-classic-dungeon-temples-design.md. The tables below are the plans the classic plans started from.

The mazes stay as they are; only combat-room content changes. Each combat room gets a quest template. monsterType and xp/gp/ap are dropped from those rooms, because quest rewards apply.

  • Monster templates go to rooms in order of path distance from the entrance, easiest first.
  • The eight generated temples get a quests list in PLANS (tools/temples/regenerate.ts), and dress() writes the templates. wind-temple.ts is edited by hand, which also drops its beast-trash-troll boss.

Template schedule rules:

  • Daily chores are due 19:00. "am" means 08:00 and "bed" means 20:30.
  • "school" is weekly on repeatOnDays mon to fri.
  • Weekly chores are due 18:00 on the window's last day.
  • Monthly is custom, one month.
  • Minutes default from difficulty.
Temple Monster rooms (difficulty, schedule) Miniboss Boss Size
Wind spill-sprite 1 daily · mustwisp 1 daily am · mailbox-mimic 1 daily · cushion-croc 1 daily · crumb-crawler 2 daily · gravel-gnome 2 weekly dust-bunny-king 3 weekly · cobweb-widow 3 weekly fluffalo 8 weekly 22
Earth kibble-kobold 1 daily am · droop-dryad 1 weekly · zoomie-zephyr 2 daily · plop-golem 2 weekly · weed-wyrm 2 weekly leaf-lion 5 weekly meadow-minotaur 8 weekly 21
Water soggy-slug 1 daily · bare-roll-imp 1 daily · toothpaste-serpent 2 daily bed · puddle-phantom 2 daily · smudge-specter 2 weekly drain-drake 5 weekly potty-poltergeist 8 weekly 21
Shadow plate-pixie 1 daily · gravy-ghoul 1 daily · mug-thug 1 daily · crumb-gremlin 1 daily · cupboard-kobold 2 daily am · dish-goblin 2 daily · garbage-gargoyle 2 weekly stickyfoot-slime 5 weekly grease-gorgon 8 weekly 23
Fire hangry-hob 1 daily am · snack-snatcher 2 daily · simmer-salamander 2 weekly · cardboard-golem 2 weekly grime-griffin 5 weekly junkpile-jotun 13 monthly 25
Ice hamper-hound 1 daily bed · socktopus 2 weekly · wrinkle-wyvern 2 weekly leftover-lich 5 weekly laundry-lurker 13 weekly 23
Light paperwork-pixie 1 school · backpack-boggart 1 school bed · bookwyrm 2 daily · sum-sphinx 2 school · desk-drake 2 weekly homework-hydra 5 school pop-quiz-poltergeist 8 weekly 21
Moon fang-fairy 2 daily am+night · scroll-troll 2 daily bed · snooze-sloth 2 daily am · pajama-pooka 1 daily am · grubgoblin 1 daily · hairball-harpy 1 daily am stinkwisp 5 daily night-owlbear 8 daily bed 22
Storm bed-basilisk 1 daily am · shoe-shrew 1 daily · mess-hog 2 daily · toybox-troll 2 daily · piece-poltergeist 2 weekly under-bed-bogey 5 weekly clutter-chimera 8 weekly 21

Library gaps (for the next beast_portraits.py batch):

  • Water (bathroom): only 9 beasts. There is no whole-bathroom deep clean for a boss, so the toilet is the boss. Little kids get only five beasts. Wanted: "Clean the whole bathroom" (boss) and a little-kid toilet check.
  • Fire: 7 beasts, the fewest. Cooking has three food beasts; the garage and the car are teen-only. Wanted: "Cook a simple meal" (boss), "Pack your lunch", and a kid-size car job.
  • Ice: only 3 monster rooms, so the boss must be 13 to reach 21. Fridge and freezer are teen-only. Either regenerate Ice with more rooms or add kid-size fridge chores.
  • Light: "Be kind today" (grump-goblin) fits no theme and is parked here. There is no "finish a book" boss.
  • Moon: the boss (bedtime) is daily, so the boss room restores every night. A weekly self-care boss ("bath night", "clip your nails") is missing.
  • Earth, Shadow, Storm, Wind: enough beasts, with spares.

12. Migration

  1. Unedited shipped temples (seedSignature matches) refresh to the rebuilt layouts on the next start (planSeedSync → refresh). Their rooms become quest rooms and stay dormant (classic fights) until a live quest matches. Starter-world quests match at once by beast, except two that have no beast and match through the library's suggestion (marked below). The audit spec ShippedTemplesAudit.spec.ts pins this list:

    • Wind: Dust Bunny King, Fluffalo
    • Earth: Weed Wyrm, Droop Dryad, Plop Golem, Zoomie Zephyr, Kibble Kobold (suggested for "Feed the Loyal Familiar")
    • Water: none
    • Shadow: Dish Goblin, Crumb Gremlin, Plate Pixie, Gravy Ghoul, Stickyfoot Slime
    • Fire: Hangry Hob, Simmer Salamander
    • Ice: Socktopus, Leftover Lich, Laundry Lurker
    • Light: Backpack Boggart, Homework Hydra, Pop Quiz Poltergeist (suggested for "Study the Ancient Tomes")
    • Moon: Fang Fairy, Pajama Pooka, Grubgoblin, Hairball Harpy, Scroll Troll, Snooze Sloth, Night Owlbear
    • Storm: Bed Basilisk, Shoe Shrew, Toybox Troll

    These starter quests keep their ids, difficulties and pay. The Quests tab shows the real size and flags tier mismatches (the starter Laundry Lurker is a 3 in Ice's boss room, Wrangle the Fluffalo a 2 in Wind's, Pacify the Night Owlbear a 1 in Moon's, Study the Ancient Tomes a 1 in Light's).

  2. Edited temples are kept as they are, with classic rooms. The editor converts them room by room.

  3. Generated temples (gen-*) stay classic (non-goal).

  4. Hero progress. Saved clearedRooms keys of rooms that turned into quest rooms are re-derived on the first sync. A beast the hero beat in a dormant room stays beaten until a blood moon, as today.

  5. Quests without a beast use the runtime suggestion plus the form prefill. No data is rewritten.

  6. Quests without estimatedMinutes get the difficulty default.

  7. Grudges and hospital bills start empty. Existing debt stays plain debt and never grounds anyone. A hero sitting at 1 HP from an old defeat is simply low.

  8. Docs to update:

    • Battle-Field-Contract-v2.md (handleRun, clock)
    • Items-And-Classes-Contract.md and Stats-Contract.md (Thief, Ninja and agility now soften or dodge the parting strike)
    • Temple-Generation-Contract.md §5 (blood moon)
    • the Powers ledger spec R3 (grounded, checked first)
    • the Thief and Ninja blurbs in ClassPassives.ts

13. Test plan

Unit (jest, through chemx test):

  • questRooms.spec: tier bounds; size with dormant rooms; resolution order (questId, template beast via visible quest, suggested beast, draft ignored, deleted with and without template); every 5.3 status; restore across daily, weekly, repeatOnDays, custom and once; rejected claim lurks again; multi-slot allowance.
  • QuestSchedule.spec / QuestList.spec: shared quests count any assignee and show "Done by"; individual quests do not; window last-day deadline for weekly quests.
  • questClock.spec: default table; per-chore spread; restored chores shorten the clock; class minutes, Fire Wand, Ice Wand and Chronos still apply.
  • BattleServiceStrikeMath.spec: the grudge multiplies and is clamped to [1, 1.75]; strikeFactor behaviour unchanged; parting strike softened by brace or runChance; dodge cap 0.5.
  • BattleService.spec: run always succeeds; free fights take no strike; frozen clock, Thor's Hammer and Ninja first strike dodge; HP floors at 0; there is no failed-run path.
  • encounterLoader.spec: a temple quest room yields quest, roomKey, beast checklist, quest rewards, tier, attackCooldown and grudge; dormant falls back to the template beast; classic unchanged.
  • BattleOutcomePassives.spec: a temple quest win logs, unseals and clears the grudge; a held win logs pending, unseals and pays nothing; an escape adds a mark only when a quest is present; a defeat admits (full HP and MP, bill charged, route hospital-hub?admitted=1); a parting strike to 0 admits.
  • TempleSystemCleared.spec: sync adds and removes keys; a restored beast reseals on entry; a blood moon leaves quest rooms to the schedule.
  • BattleGuard.spec: an overdue quest wins the encounter; 60-minute cooldown; invisibility; pendingAmbush.questId.
  • hospitalAdmission.spec (new): chargeHospitalBill takes wallet, then savings, then owes the rest; bills stack; flat at every level; isGrounded; admission heals HP and MP to full and notifies the hero and the GMs once.
  • wallet.spec: payHospitalBill from the wallet, capped by wallet and bill; the bank pays the bill before debt; plain debt never grounds.
  • AbilityService.spec / heroAbilityStatus.spec: the grounded refusal comes first, charges nothing and lifts when the bill reaches 0; battle casts are unaffected.
  • RoomInteractions.spec: a trap that empties HP admits instead of going to game-over.
  • QuestCompletion.spec: a logged completion clears the grudge (all assignees when shared) and does not touch HP.
  • beastLibrary.spec: every beast has one theme; counts per theme. librarySuggest.spec: theme filter; adopt copies the theme.
  • ShippedTemplesAudit.spec: all nine temples pass the audit; every combat room has a template whose beast is in the temple's theme; the tier matches the room type; size ≥ 21.
  • importAllTemples.spec: rebuilt layouts refresh unedited copies and keep edited ones. templeAudit.spec: the size check is optional.
  • TempleCreatorRoomContent.spec / roomTypeGuide.spec: picking a quest writes questId, sets the type from the tier and promotes shared tokens; the classic path is unchanged.

E2E (Cypress 8, 390x844, cy.seedWorld()):

  • tests/e2e/specs/quest-rooms.spec.js: wake the Shadow temple for the seeded hero; the Dish Goblin room lurks; win → the board shows the quest done and the room is cleared; cy.clock to the next day → the beast is back.
  • A second fight: run → HP drops by about one strike and one grudge mark shows on the next fight.
  • A lost fight with ₲40 in the wallet: carried to the Hospital, HP and MP full, GROUNDED chip with ₲60, a Power's Cast refused; earn GP from a quest, pay at the Gold Bank → the chip is gone and the Power casts.

14. Slice plan (board-sized; in order)

# Task Model Files
1 Themes on the library sonnet tools/art/beast_roster.json, tools/art/beast_portraits.py (emit theme), beastLibrary.ts, new services/temples/templeThemes.ts, BestiaryDb.ts, librarySuggest.ts, specs
2 Tier, size and quest-clock rules (pure) + estimatedMinutes sonnet new services/temples/questRooms.ts (tier, size), new services/quests/questClock.ts, AchievementDb.ts, QuestLabels.ts, specs
3 One due-date rule: shared quests and window last-day deadline opus QuestSchedule.ts, QuestEngine.ts, QuestList.ts, questWatch.ts, specs
4 Room quest resolution and status (pure) sonnet questRooms.ts, engine/core/dungeons/types.ts, RoomBeast.ts (dormant template beast), specs
5 Temple runs follow the board (sync, seals, blood moon) opus new templeSystem/questRooms.ts, TempleSystem.ts, heroRuns.ts, floors.ts, templeReset.ts, TempleSystemCleared.spec
6 Temple screen speaks quest rooms sonnet RoomInteractions.ts, roomInteractions/vocabulary.ts, templeGrounds/useRoomBeast.ts, useRoomOutcomes.ts, specs
7 Quest fights from rooms: encounter, win, hold, clock opus encounterLoader.ts, useBattleOutcome.ts, battleRewards.ts, BattleField.ts, QuestCompletionService.ts, specs
8 Run always works: parting strike, Thief/Ninja/agility sonnet BattleService.ts, battleService/strikeMath.ts, dialogLines.ts, useBattleOutcome.ts, ClassPassives.ts, StatEffects.ts, contract docs, specs
9 Grudge marks sonnet new services/battle/questGrudge.ts, strikeMath.ts, BattleService.ts, stores/user.ts, QuestCompletionService.ts, useBattleOutcome.ts, enemy card under BattleField/components/, specs
10 Hospital admission, bill and grounding sonnet new services/hometown/hospitalAdmission.ts, gp/wallet.ts, gp/GPService.ts, stores/user/resources.ts, utils/User/stats.ts, useBattleOutcome.ts, roomInteractions/hero.ts, useRoomOutcomes.ts, HospitalHub.ts/.vue, GoldBank.ts + ATMModal.vue, MyGoldPoints, AbilityService.ts, heroAbilityStatus.ts, MyAbilities.ts, XpMainHud.vue, XpVitalsBar.vue, XpImpersonateProfile.vue, notifications/Notifications.ts, specs
11 Quest ambushes sonnet new services/battle/questAmbush.ts, router/_Guards/battle.guard.ts, stores/battle.ts, BattleGuard.spec
12 Editor: quest room flow and picker sonnet useTempleRoomEditor.ts, roomTypeGuide.ts, RoomTypeModal.vue, XpRoomEditorPage.vue, new RoomQuestPicker.vue, new RoomQuestConfig.vue, grid/roomSymbols.ts, specs
13 Temple Quests tab, Wake quests, audit size check sonnet tabs/XpTempleBeasts.vue, new services/temples/wakeTempleQuests.ts, templeAudit.ts, XpBloodMoonModal.vue, specs
14 Quest form: suggested beast, tier chip, minutes sonnet useAchievementForm.ts, tabs/QuestTypeTab.vue, tabs/QuestRewardsTab.vue
15 Rebuild the nine shipped temples sonnet tools/temples/regenerate.ts, src/lib/engine/temples/*-temple.ts, ShippedTemplesAudit.spec.ts, importAllTemples.spec.ts
16 E2E quest-room loop sonnet (Haiku runs it) tests/e2e/specs/quest-rooms.spec.js, fixture

Dependencies:

  • 1-2 feed everything else.
  • 3 comes before 4-7.
  • 4 → 5 → 6 → 7.
  • 8-11 can run beside 12-14 once 7 lands.
  • 15 needs 1, 4 and 13 (wake flow).
  • 16 runs last.

Each slice ends with chemx verify.

15. Decisions (all approved by the owner, 2026-10-12)

  1. Shared or individual: the quest's assignment type decides. Team Effort and Rat Race are shared (one clear clears it for every assignee, only the clearing hero is paid); Solo, Rotate and Bounty are individual.
  2. Heroes not assigned see the beast asleep and pass.
  3. Parting strike (tunable): one full strike; Thief and Ninja halve it through the brace factor; agility dodges it, capped at 50%.
  4. Grudge (tunable): +25% per mark, 3 marks, so 1.75× at most.
  5. Strike clock: defaults 1→3, 2→5, 3→8, 5→12, 8→20, 13→30, 21→45, 34→60, 55+→90 minutes; parents can edit the minutes on the "Why" tab (prefilled from the difficulty).
  6. Hospital bill: a flat ₲100 (REVIVE_COST, tunable) per admission, stacking, not scaled by level; a Game Master can forgive a bill from the hero's card (logged in chat).
  7. Tier attack factor (tunable): 1 / 1.2 / 1.5 for monster, miniboss and boss.
  8. Themes: Fire = cooking and big jobs, Ice = laundry and fridge/freezer, Light = school, Moon = bedtime and self-care, Storm = bedroom, clutter and toys are confirmed (no longer drafts); a batch of about 6 new beasts for the §11 gaps is approved.
⚠️ **GitHub.com Fallback** ⚠️