Temple Interactions Contract - xopherdeep/do-it-for-the-xp GitHub Wiki

Temple interactions v2 (2026-10-10): every room type does something, every equipped item works

Files: src/app/Console/TempleGrounds/TempleGrounds.{vue,ts,scss}, src/hooks/temples/useTemple.ts, src/lib/engine/core/dungeons/TempleSystem.ts (only if a room effect needs engine state), a NEW service src/lib/services/temples/RoomInteractions.ts and src/lib/services/temples/ItemUse.ts, tests under tests/unit/. Room vocabulary: src/lib/engine/dungeons/roomTypes.ts; layouts: src/lib/engine/temples/*.ts; progress per hero: TempleSystem (cleared rooms, keys, map, compass, position). Sounds via play$fx(key) (valid keys: see src/assets/fx/index.ts: restoreHP, restoreMP, damage, useKey, openChest, takeItem, secret, teleport? (if absent use "okdesuka"), error, loot, yes, no, fillPoints). Toasts are themed already; use position: "top". Hero stats through useUserStore() (updateUserHP, updateUserGP, persistUser; add updateUserMP if missing, mirroring updateUserHP).

Room action sheets (showRoomActions in TempleGrounds.ts; move the per-type logic into RoomInteractions.ts so it is testable)

Every type gets a header in the temple's voice, one or two actions, and an effect. "Once" means once per hero per temple visit (remember in TempleSystem progress or a per-session map keyed by temple+level+position).

  • entrance: header "The way out is behind you." Actions: Leave Temple (exists; keep the world-return routing).
  • empty: "An empty room. Dust and echoes." Actions: Look around → once, 1 in 3 chance to find 1-5 GP ("You found 3 GP under a stone!") else a flavour line.
  • gate: "A great gate, sealed." If the temple's boss room is cleared OR the hero holds the master key: action Open the gate → mark the temple cleared for this hero (TempleSystem: add markTempleCleared), award the temple's reward (xp 150 / gp 50 / ap 3 unless the layout defines content.reward), play secret, show a victory toast, return to the world. Otherwise: "It will not budge. The master key, or the beast that guards it, must come first."
  • health (H__P): "A spring of clear water." Action: Drink → once: restore content.healthPoints (default 10) HP, restoreHP, toast "+10 HP". After: "The spring is still." The Bottles can bottle an unused spring instead of drinking it now (see the equipment section).
  • mana (M__P): same with MP and restoreMP; the Bottles keep it as Silver Water.
  • camp (CAMP): "A safe place to rest." Action: Make camp → once: restore HP and MP to full, restoreAll.
  • trap (XX, FPIT): ON ENTERING (not the sheet): if content.avoidable, the sheet opens first with "Jump across" (agility >= 8 or 50%: safe, else damage) / "Walk through" (damage); otherwise damage lands immediately: content.damage (default 5) HP via updateUserHP, damage, a screen shake class on the game area for 400 ms, toast "The {trapType} trap bites: -5 HP". Once per visit; a trap the Ice Wand froze from the room before counts as sprung and neither bites nor opens its sheet. At 0 HP the hero is admitted to the Hospital like a lost battle (hurtHero calls admitToHospital: full HP and MP, the flat bill, the family told; the temple says "You were carried to the Hospital." and then routes to the hospital screen with ?admitted=1). The game-over splash is no longer used.
  • puzzle (PUZL): "Glyphs cover the wall." Action: Study the glyphs → a riddle alert: 3 riddles keyed by content.puzzleType (fallback: a generic one) each with 3 choices; correct → unlocks every locked door of this room and the rooms it leads to (TempleSystem unlockDoor), secret, toast "The glyphs fade. A door unlocks."; wrong → -3 HP, "Wrong. The wall glows hot."; solved once. With Night's Light used here, the riddle shows two choices instead of three (one wrong answer fades) every time the sheet opens this visit.
  • teleport: exists. Header "A shimmering circle." Keep.
  • shop: "Can I interest you in my wares?" Action: View wares → an alert listing content.specialItem (and "Potion of Vigor: restore 20 HP, 15 GP" and "Bomb: 20 GP" always) with the hero's GP; buying deducts GP (updateUserGP negative wallet), gives the effect (potion = heal now; special item = added to user.inventory as { name, source: templeId }), plays buyItem; not enough GP → error + "Not enough GP". A Bomb goes into the pack (user.inventory, as many as the hero likes); with a full bag nothing is added and no GP is charged.
  • loot / key / master-key / map / compass: chests exist (processChestItems); keep. Header per chest content: "A chest!" / "An old chest, already opened."
  • monster / miniboss / boss: Fight exists. When cleared: header "Nothing left here but the smell." Action: Search the room → once, 1 in 2 chance of 2-8 GP.
  • stairs up/down (ST_U/ST_D): "Stairs lead {up|down}." Action: Climb → change level via TempleSystem (there is a level concept: currentLevel); if the layout has no such level, "The stairs are blocked by rubble."
  • pit (PIT_): on entering: fall to the floor below if it exists (-5 HP, damage), else -5 HP and stay.
  • void: unreachable (movement already blocks it); if reached, "Nothing but dark." no actions.
  • secret (SCRT): hidden on the map until entered; header "A hidden chamber!" with a one-time chest of 20 GP.
  • save (SAVE): "A quiet alcove." Action: Save → TempleSystem persists position (already does on every move): toast "Your journey is saved." Unknown type → header "Something strange." and only Cancel.

Equipment from the dungeon footer

Items are in-game tools, used from the two hands (2026-10-13, docs/superpowers/specs/2026-10-13-equipment-rules-design.md). Only a tool held in a hand can be used here; the footer's two hand slots are the only way in, and the hands can be swapped in the equipment modal, temples included.

  • Footer hand slots show the ITEM NAME (short: strip apostrophe names to <= 12 chars with an ellipsis) instead of "Left"/"Right"; empty slots keep "Left"/"Right" dimmed.
  • Tapping a slot calls useItem(item, context) from ItemUse.ts with { userId, templeId, level, position, room, exits }; the tool's use is its rule's temple use in src/lib/services/items/ItemRules.ts, by item name (the stored copy's description is never read).
    • navigation items (My Quests, Book Of Spells, Goods, Wallet, Portal Home) and the Pegasus items (Pegasus Boots, Wind Whistle): run their existing click from useEquipmentItems() (look the definition up by faIcon: the stored copy has no functions); the hero stays on the temple screen for the Pegasus items.
    • A tool with a temple use does it (table below). A tool with a rule but no temple use answers "{Item} is for fights." (nothing charged); a name with no rule answers "{Item}: nothing happens here."
    • MP: the rule's list price with the hero's class discount (heroMpBill: Mage 25%, Sage 50%, trained intelligence), charged only when the use works. Short on MP → error + "Not enough MP". The Bombs spend a Bomb from the pack instead.
  • Hand slot tap feedback: play$fx("select") then the effect's own sound.
  • The cracked wall's control and sheet (the Bombs' own way in): over a cracked side the movement control is a bomb (fa-bomb, useTemple().moveIcons) in the arrow's place; a plain wall has no control. Tapping it (or swiping toward it) opens the cracked wall's reveal, never a toast or an Ionic alert (TempleWallReveal.vue; what it says and offers is bombView.ts): the wall as a pixel sprite, "The wall is cracked.", and "Bomb it" only when a hand holds the Bombs and the pack holds a Bomb. Without them it says what would open it ("A Bomb would do it.", "Hold the Bombs in a hand and a Bomb would do it.", "You are out of Bombs.") and offers only Close. "Bomb it" runs the Bombs with ctx.side set to that wall; on a blown wall the sheet flashes, the blocks around the fissure fall (300-500 ms), the opening shows its light, and only then does the room shake and the toast "Boom! The north wall blows open." speak (the blast sound is the sheet's own). A refusal (a fight's seal) closes the sheet and the refusal speaks. Reduced motion gets the flash and the opening only. The footer's Bombs still work without the sheet: no tapped side, so the first cracked wall clockwise from north.
Tool MP Temple use (TEMPLE_USES in ItemUse.ts) Refusals (nothing charged)
Lantern 5 Grants the temple map and compass for this hero (grantMapAndCompass), secret, toast "The lantern shows the way: map and compass revealed." Already known: "The lantern glows. You already know the way."
Bottles 0 Bottle a spring: in a room with an unused health or mana spring, adds one Spring Water (+10 HP) or Silver Water (+10 MP) (BattlePotions) to the pack and uses the spring up, takeItem. No spring: "No spring here to bottle."; full bag: "Your bag is full." (the spring stays as it was)
Bombs a Bomb Blows open one cracked (bombable) wall that has a room beyond it, opened on both rooms and remembered in both (blowBombableSide); the tapped side goes first when the tap names one (ctx.side: the Bomb button on a cracked side), then clockwise from north. Never a locked door: a small lock, a fight's seal and the boss door (only the Boss Key opens it) are not for a Bomb. A plain wall, and a wall, void or the floor's edge beyond a cracked one, are skipped. damage and a shake, spends one Bomb from the pack. Never a fight's seal. "Out of bombs."; "Nothing to blow up here." (no cracked side with a room beyond; the Bomb is kept); a cracked wall of a room whose fight is still on: "The seal holds. Bombs will not help until the fight is won."
Gaia's Quake 10 The floor's secret rooms appear on the map (TempleSystem.revealSecrets(templeId, level)), secret, a shake. Once per floor. No map yet (asked first, error): "Find this temple's map first."; already shown: "Hidden rooms on this floor are already on your map."; none: "The ground shakes, but nothing is hidden on this floor." (MP only when something is revealed)
Hook Shot 5 If an adjacent room is a pit/void with solid ground beyond it in the same direction, jump to that room (moveToPosition skipping the hazard), teleport. "Nothing to latch onto."
X's Invisibility 15 In a free-fight monster room sealed by its fight (lockOnEnter, not a quest room) the seal opens without the fight (TempleSystem.slipOut); the room stays uncleared. A quest room: "This beast holds a quest. It sees you."; otherwise "Nothing here to slip past."
Ice Wand 10 Freeze a trap: every live trap room next to the hero (any open side) is frozen for this visit, because traps bite on entering; onEnterRoom skips a frozen trap's damage and its sheet. secret. "No trap nearby to freeze."
Night's Light 10 Moonlight on the glyphs: in an unsolved puzzle room, the riddle sheet shows two choices instead of three every time it opens this visit. "No glyphs here to read."; already lit: "The moonlight already rests here."
Fire Wand, Thor's Hammer, Silver Arrow, Sol's Flare, Tesla's Bolt, Gale's Wind, Chronos' Clock — "{Item} is for fights."

Tests

tests/unit/RoomInteractions.spec.ts: for every room type listed, roomActions(room, context) returns the header and action labels specified; effects (drink once, gate sealed vs open, puzzle right/wrong, trap damage, shop purchase and refusal) mutate the Pinia user store as specified. tests/unit/ItemUse.spec.ts: navigation runs click, every row of the table above works and refuses as listed (Lantern, Bottles bottle a spring, Bombs spend a Bomb and never open a locked door, Quake, Hook Shot, X's Invisibility, Ice Wand, Night's Light), costs come from ItemRules whatever a stored description says, and a fight tool answers "is for fights". tests/unit/BombView.spec.ts, WallSprite.spec.ts, TempleWallSheet.spec.ts, TempleMoveIcons.spec.ts: what the cracked wall's sheet shows and offers, the wall sprite's blocks, the sheet's glue to the temple, and the bomb on the movement control. tests/unit/ItemRules.spec.ts: every item has a temple or fight job or is a travel/menu item, every MP cost is 0-25. Keep pnpm typecheck clean, pnpm lint 0 errors, pnpm test green (the existing temple specs must stay green).