Quest Rooms Spec - xopherdeep/do-it-for-the-xp GitHub Wiki
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.
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.
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).
-
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)". -
Tier comes from difficulty on the Fibonacci slider (the quest form's "Why" tab;
DIFFICULTY_NAMES;updatePointssets 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+. - 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.
- 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").
-
A quest with no beast gets the one the library suggests (
librarySuggest.ts). The GM can change it. -
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):
- 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.
- 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.
-
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. - Doing the chore is always the cheapest path. Finishing before the clock runs out costs no damage.
- The strike timer must be accurate. A one-dish Dish Goblin gets a short clock. A whole-sink boss gets a long one.
-
Run-away mechanic (approved; the two numbers are tunable):
-
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
elementis an art choice (10 beasts per element), so the theme is its own field.
| 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 |
-
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"orisBoss = true(boss theme and victory song throughEncounter.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.
resolveRoomQuest(room, heroId, visibleQuests):
-
content.questId(the GM's pick) resolves to that quest if it exists and is not a draft. - Otherwise
content.quest(a shipped template) resolves to the first live quest visible to the hero whose beast matchestemplate.beastId. A quest's beast is itsbeastId, or the topsuggestBeasts(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. - 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).
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.
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.
-
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.
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, sobraceFactorapplies: half for most classes, a quarter for a Warrior's wall) when the hero was already bracing from Defend, or when the class hasrunChanceset (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
skipFirstStrikeis still owed; or a roll falls undermin(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.
- Each hero profile gets
questGrudges?: Record<questId, { marks: 0..3, lastFledAt?, lastAmbushAt? }>, persisted withpersistUser. - 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
grudgeparameter onstrikeDamagethat defaults to 1 and is clamped to [1, 1.75].BattlePassive.strikeFactoris 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.
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):
-
Treatment. HP and MP are set to full, the same gains as
quoteCare("revive").LIMP_HOME_HPand the limp-home line retire. -
The bill.
HOSPITAL_BILL = REVIVE_COST(100 GP, tunable). The purechargeHospitalBill(purse, bill)takes it from the wallet first, then savings, and adds whatever is left tohospitalBill.- 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.
- The bill is flat; it does not scale with level. Quest pay comes from difficulty (
-
The moment. The arena (or the temple) fades on one line, "You were carried to the Hospital." Then the route goes to
hospital-hubwith?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." Thegame-oversplash is no longer used by fights or traps. -
Notices. A hero notification and a Game Master notification, using a new
XpNotificationKind"hospital"modelled onpower-castinAbilityService.tellTheFamily. A line goes in thefamily-chatroom: "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/castPowerrefuse with a new reason,grounded, checked first: "Grounded: pay your hospital bill at the Gold Bank." Battle spells never pass throughuseAbility, 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.tsclickPayDebt,ATMModal.vue) pays the hospital bill first, then any other debt, from the wallet aspayDownDebtdoes today. Savings come out through the existing Withdraw. -
payHospitalBill(purse, amount)sits besidepayDownDebtinwallet.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.vueshows the same chip. - My Powers cards keep their Cast buttons disabled with the grounded reason.
- The Gold Bank and
MyGoldPointsshow 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.
- 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).
- open and either overdue (
- 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,pendingAmbushgainsquestId, 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
dueByTimeon 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.
| 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.
-
Entering a temple.
initTemple/loadProgressloads the hero's board, thensyncQuestRooms(templeId, hero, board, now)(newtempleSystem/questRooms.ts) puts every quest room that is not lurking intoclearedRoomsand 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. -
Seals.
floors.tsseals on entry whenlockOnEnter && !isRoomCleared, replacing!hasVisited. A restored beast seals again and the map keeps the room. -
Stepping in.
useRoomBeast.ts+roomActionsshow the 5.3 status. A lurking room offers Fight. -
The fight.
templeBattleRoute→loadTempleEncounterresolves the quest. If there is one, it builds the fight asloadQuestEncounterdoes: 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 bothquestandroomKey. A dormant room fights as today. -
Win = complete. If the quest requires approval,
completeQuest(quest, hero, "battle")logs a pending claim and pays nothing. OtherwisecommitRewards(rewardsForHero(questRewards))+recordBattleCompletion. Both paths callmarkRoomCleared(unseal) and clear the grudge. The room's authoredxp/gp/apis ignored when a quest is present. -
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. -
Defeat.
admitToHospital(hero, "battle"): full HP and MP, the bill, then the carried-to-the-Hospital moment andhospital-hub. The temple run is saved, and the room's beast still lurks. The room seals again on the next entry. - 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.
- 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.
-
Restore. The window rolls over, the board item turns
open, the sync takes the key out, and the beast is back. -
Blood moons.
resetTempleProgressstill 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.vuecopy: "Quest rooms follow their chores; the blood moon brings back beast rooms, chests, keys and doors." - Ambush. Overworld only (5.9). Temples have no random encounters.
- 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.
-
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
themematches; 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,
beastIdadopted,type: individual. - The footer shows the temple size meter previewing the pick ("Size 14 → 22 · full at 21") and one Confirm button.
- Search at the top, then segments: This temple's theme (quests whose beast's
-
On confirm: writes
content.questId, sets the roomtypefromquestTier, and promotes a sharedM00x/MINItoken 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), thelockOnEntertoggle, and Test fight (a free fight with the quest's beast; nothing is logged). -
Temple Quests tab (
tabs/XpTempleBeasts.vuebecomes "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, withstartsOnon 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 optionalsizecheck that takes a difficulty lookup ("Size 17 of 21"). It never failspassed. -
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.vueprefills a beast-less quest with the library suggestion ("Suggested: Dish Goblin · Change").
- The "Why" tab (
| 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.
Superseded 2026-10-22:
tools/temples/regenerate.tsis gone. The nine temples are now dressed from Zelda dungeons bytools/temples/classic/dress.ts, with each temple's quests inCLASSIC_PLANS(tools/temples/classic/dressPlans.ts); seesuperpowers/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
questslist inPLANS(tools/temples/regenerate.ts), anddress()writes the templates.wind-temple.tsis edited by hand, which also drops itsbeast-trash-trollboss.
Template schedule rules:
- Daily chores are due 19:00. "am" means 08:00 and "bed" means 20:30.
- "school" is weekly on
repeatOnDaysmon 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.
-
Unedited shipped temples (
seedSignaturematches) 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 specShippedTemplesAudit.spec.tspins 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).
-
Edited temples are kept as they are, with classic rooms. The editor converts them room by room.
-
Generated temples (
gen-*) stay classic (non-goal). -
Hero progress. Saved
clearedRoomskeys 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. -
Quests without a beast use the runtime suggestion plus the form prefill. No data is rewritten.
-
Quests without
estimatedMinutesget the difficulty default. -
Grudges and hospital bills start empty. Existing
debtstays plain debt and never grounds anyone. A hero sitting at 1 HP from an old defeat is simply low. -
Docs to update:
-
Battle-Field-Contract-v2.md(handleRun, clock) -
Items-And-Classes-Contract.mdandStats-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
-
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];strikeFactorbehaviour unchanged; parting strike softened by brace orrunChance; 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, routehospital-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):chargeHospitalBilltakes 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:payHospitalBillfrom the wallet, capped by wallet and bill; the bank pays the bill beforedebt; plaindebtnever grounds. -
AbilityService.spec/heroAbilityStatus.spec: thegroundedrefusal 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 togame-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;adoptcopies 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 writesquestId, 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.clockto 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.
| # | 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.
- 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.
- Heroes not assigned see the beast asleep and pass.
- Parting strike (tunable): one full strike; Thief and Ninja halve it through the brace factor; agility dodges it, capped at 50%.
- Grudge (tunable): +25% per mark, 3 marks, so 1.75× at most.
- 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).
-
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). - Tier attack factor (tunable): 1 / 1.2 / 1.5 for monster, miniboss and boss.
- 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.