[High] [Feature] Implement non-combat room gameplay: Library, Recovery, Treasure, Puzzle #262

Closed
opened 2026-06-04 12:44:30 +02:00 by Anexim · 2 comments
Owner

Non-Combat Room Gameplay: Library, Recovery, Treasure, Puzzle

Source of truth: docs/specs/spire-climbing-spec.md §4.3–§4.8 (outdated/incomplete)
Current state: All four non-combat room types are stubs — the code in advanceRoomOrFloor() skips past them with advanceRoomOrFloor(get, set) without applying any effect (except Library which grants a single flat XP amount and immediately advances).

Gap Summary

The current implementation in combat-descent-actions.ts lines 128-132:

if (room.roomType === 'library') {
  onEnterLibraryRoom(get, set);  // flat XP, then skips
} else if (room.roomType === 'recovery' || room.roomType === 'treasure' || room.roomType === 'puzzle') {
  advanceRoomOrFloor(get, set);  // just skips — nothing happens
}

None of these rooms have timed progression, player interaction, or meaningful effects. The FloorState type already has progress fields (libraryProgress, recoveryProgress, puzzleProgress, etc.) but nothing ticks them.


What Each Room Should Do

1. Library Room

Current behavior: Grants a single flat XP amount (50 × (1 + floor/10)) to a random discipline, then immediately advances.

Desired behavior:

  • Takes 1 hour to complete (timed progression)
  • Grants discipline XP at 25× the normal rate for a random unlocked discipline
  • No mana cost
  • XP is granted continuously over the hour (not a lump sum)
  • Player sees a progress bar with thematic text: "Studying Mana Circulation from ancient tomes"
  • "Stay 1 Hour More" button (once only) — extends by another hour at 25× XP rate
  • "Skip" button — advance to next room immediately (in case player doesn't care about XP)

2. Recovery Room

Current behavior: Instantly skipped, nothing happens.

Desired behavior:

  • Takes 1 hour to complete (timed progression)
  • During this hour, grants vastly increased mana recovery and conversion rates for all unlocked mana types:
    • If player normally converts 10 raw → 1 transference/hour, boost to 10 raw → 10 transference/hour (10× conversion efficiency)
    • If player normally regenerates 1 raw mana/hour, boost to 10 raw mana/hour (10× regen rate)
    • The multiplier should be 10× for both regen and conversion rates
  • No mana cost
  • Player sees a progress bar with thematic text: "Resting and recovering in a mana-rich chamber"
  • "Stay 1 Hour More" button (once only) — extends by another hour at boosted rates
  • "Skip" button — advance to next room immediately

3. Treasure Room

Current behavior: Instantly skipped, nothing happens.

Desired behavior:

  • Takes 1 hour to loot (timed progression)
  • Grants random loot over time:
    • Primary loot: Fabricator materials (manaCrystalDust, arcaneShard, earthShard, etc.)
    • Rare loot: Pre-crafted equipment (low chance)
    • Loot is granted progressively: e.g., at 10%, 50%, 95%, 100% of the hour — best items only at 100%
    • Scales with floor:
      • Low floors (1–10): 2–3 items, mostly common materials
      • Mid floors (10–50): 4–7 items, mix of common/uncommon
      • High floors (50+): 8–15 items, better material types, higher chance of rare drops
    • Loot table should be modular — easy to add new loot types in the future
  • All looted items listed in the activity log as they are found
  • Player sees a progress bar with thematic text: "Rummaging through ancient chests and caches"
  • "Skip" button — advance to next room immediately (forfeit remaining loot)

4. Puzzle Room

Current behavior: Instantly skipped, nothing happens. The puzzleProgress/puzzleRequired fields exist on FloorState but are never ticked.

Desired behavior:

  • Takes up to 24 hours to complete (base time, before attunement reductions)
  • Each puzzle is associated with 1 or more attunements (already defined in constants/rooms.ts PUZZLE_ROOMS)
  • Attunement-based time reduction:
    • Single-attunement puzzle (e.g., enchanter trial): that attunement's level reduces time by up to 90% at max level
    • Dual-attunement puzzle (e.g., enchanter + invoker): each attunement contributes up to 45% reduction, cumulatively up to 90% max
    • Reduction formula: linear scaling with attunement level (level 1 = small reduction, max level = full reduction for that attunement's share)
  • Floor scaling: Base time increases the higher the player has climbed:
    • Floors 1–20: ~4 hours base
    • Floors 20–50: ~8 hours base
    • Floors 50–100: ~16 hours base
    • Floors 100+: up to 24 hours base
  • No mana cost
  • Player sees a progress bar with thematic text based on puzzle type:
    • Enchanter puzzle: "Deciphering an enchanted lock"
    • Fabricator puzzle: "Disassembling a mana-powered mechanism"
    • Invoker puzzle: "Communing with residual guardian spirits"
    • Hybrid puzzle: "Working through a complex attunement challenge"
  • No "Stay Longer" or "Skip" button — puzzle rooms are mandatory and the player must complete them to proceed

State Changes Needed

FloorState type (types/game.ts)

The existing progress fields are already present but need to be properly used:

// Already exists:
libraryProgress?: number;
libraryRequired?: number;
recoveryProgress?: number;
recoveryRequired?: number;
puzzleProgress?: number;
puzzleRequired?: number;
puzzleId?: string;
puzzleAttunements?: string[];

New fields needed:

// Treasure room
treasureProgress?: number;
treasureRequired?: number;
treasureLoot?: LootDrop[];       // pre-generated loot for this room
treasureLootClaimed?: number[];  // indices of already-claimed loot

// Library/Recovery "stay longer" flag
libraryStayed?: boolean;   // true if player already used "stay 1 hour more"
recoveryStayed?: boolean;  // true if player already used "stay 1 hour more"

combat-state.types.ts

New actions needed:

// Tick non-combat room progress (called from game tick pipeline)
tickNonCombatRoom: (hours: number) => void;

// Player actions
skipNonCombatRoom: () => void;
stayLongerInRoom: () => void;  // library or recovery only

Store Logic Changes

combat-descent-actions.ts

Replace the stub handling in advanceRoomOrFloor():

// OLD (line 128-132):
} else if (room.roomType === 'recovery' || room.roomType === 'treasure' || room.roomType === 'puzzle') {
  advanceRoomOrFloor(get, set);
}

// NEW: Each room type gets its own handler that initializes progress and waits

New handler functions needed:

  • onEnterLibraryRoom() — rewrite: set libraryProgress = 0, libraryRequired = 1 (1 hour), libraryStayed = false; do NOT call advanceRoomOrFloor immediately
  • onEnterRecoveryRoom() — new: set recoveryProgress = 0, recoveryRequired = 1, recoveryStayed = false; apply 10× regen/conversion multiplier
  • onEnterTreasureRoom() — new: set treasureProgress = 0, treasureRequired = 1; pre-generate loot table based on floor
  • onEnterPuzzleRoom() — new: set puzzleProgress = 0, calculate puzzleRequired based on floor and attunement levels

New tick handler: tickNonCombatRoom()

Called every game tick when the current room is non-combat:

  1. Increment progress by HOURS_PER_TICK
  2. For treasure rooms: check loot thresholds and grant items
  3. For recovery rooms: apply boosted regen/conversion (via mana store)
  4. For library rooms: grant XP at 25× rate
  5. When progress ≥ required: call advanceRoomOrFloor()
  6. For library/recovery: if stayLonged is false and player pressed "Stay", add 1 more hour to required and set stayed = true

UI Changes

RoomDisplay.tsx

Each non-combat room type needs:

  • A progress bar showing time elapsed / total time
  • Thematic description text (changes per room type)
  • "Skip" button (library, recovery, treasure only)
  • "Stay 1 Hour More" button (library and recovery only, disabled after use)

SpireCombatPage.tsx

  • Wire the "Skip" and "Stay" buttons to new store actions
  • Pass currentRoom progress fields to RoomDisplay

Loot System (Treasure Room)

Create a new modular loot generation function:

function generateTreasureLoot(floor: number): LootDrop[] {
  // 1. Determine item count based on floor (2-15)
  // 2. Roll for each item: material (common) vs equipment (rare)
  // 3. Filter LOOT_DROPS by minFloor <= current floor
  // 4. Weight by dropChance, higher floors get better items
  // 5. Return array of LootDrop with amounts
}

The LOOT_DROPS data already exists in data/loot-drops.ts. The function should be easy to extend with new loot tables in the future.


Files to Change

File Change
stores/combat-descent-actions.ts Rewrite onEnterLibraryRoom; add onEnterRecoveryRoom, onEnterTreasureRoom, onEnterPuzzleRoom; add tickNonCombatRoom; add skipNonCombatRoom and stayLongerInRoom actions
stores/combat-state.types.ts Add new actions and state fields
stores/combatStore.ts Wire new actions
stores/pipelines/combat-tick.ts Add non-combat room tick phase (or handle in gameStore tick)
components/game/tabs/SpireCombatPage/RoomDisplay.tsx Add progress bars, thematic text, skip/stay buttons for each room type
components/game/tabs/SpireCombatPage/SpireCombatPage.tsx Wire skip/stay buttons to store actions
utils/spire-utils.ts Add generateTreasureLoot() function
types/game.ts Add treasure-related fields to FloorState

Out of Scope

  • Visual animations for loot drops
  • Sound effects
  • New loot drop definitions (use existing LOOT_DROPS)
  • New puzzle definitions (use existing PUZZLE_ROOMS)

Acceptance Criteria

  • Library room takes 1 hour, grants 25× XP to a random unlocked discipline, has skip + stay buttons
  • Recovery room takes 1 hour, grants 10× mana regen and conversion rates, has skip + stay buttons
  • Treasure room takes 1 hour, grants 2–15 items scaling with floor (mostly materials, rare equipment), loot listed in activity log, has skip button
  • Puzzle room takes up to 24 hours (floor-scaled), reduced by attunement levels (up to 90%), no skip/stay buttons
  • All non-combat rooms show a progress bar with thematic description text
  • "Stay 1 Hour More" button works once per library/recovery room, then disables
  • "Skip" button on library/recovery/treasure advances immediately
  • Non-combat room progress ticks every game tick alongside combat
# Non-Combat Room Gameplay: Library, Recovery, Treasure, Puzzle > **Source of truth:** `docs/specs/spire-climbing-spec.md` §4.3–§4.8 (outdated/incomplete) > **Current state:** All four non-combat room types are stubs — the code in `advanceRoomOrFloor()` skips past them with `advanceRoomOrFloor(get, set)` without applying any effect (except Library which grants a single flat XP amount and immediately advances). ## Gap Summary The current implementation in `combat-descent-actions.ts` lines 128-132: ```typescript if (room.roomType === 'library') { onEnterLibraryRoom(get, set); // flat XP, then skips } else if (room.roomType === 'recovery' || room.roomType === 'treasure' || room.roomType === 'puzzle') { advanceRoomOrFloor(get, set); // just skips — nothing happens } ``` None of these rooms have timed progression, player interaction, or meaningful effects. The `FloorState` type already has progress fields (`libraryProgress`, `recoveryProgress`, `puzzleProgress`, etc.) but nothing ticks them. --- ## What Each Room Should Do ### 1. Library Room **Current behavior:** Grants a single flat XP amount (`50 × (1 + floor/10)`) to a random discipline, then immediately advances. **Desired behavior:** - Takes **1 hour** to complete (timed progression) - Grants discipline XP at **25× the normal rate** for a random unlocked discipline - No mana cost - XP is granted continuously over the hour (not a lump sum) - Player sees a progress bar with thematic text: *"Studying Mana Circulation from ancient tomes"* - **"Stay 1 Hour More" button** (once only) — extends by another hour at 25× XP rate - **"Skip" button** — advance to next room immediately (in case player doesn't care about XP) ### 2. Recovery Room **Current behavior:** Instantly skipped, nothing happens. **Desired behavior:** - Takes **1 hour** to complete (timed progression) - During this hour, grants **vastly increased mana recovery and conversion rates** for all unlocked mana types: - If player normally converts 10 raw → 1 transference/hour, boost to 10 raw → 10 transference/hour (10× conversion efficiency) - If player normally regenerates 1 raw mana/hour, boost to 10 raw mana/hour (10× regen rate) - The multiplier should be **10×** for both regen and conversion rates - No mana cost - Player sees a progress bar with thematic text: *"Resting and recovering in a mana-rich chamber"* - **"Stay 1 Hour More" button** (once only) — extends by another hour at boosted rates - **"Skip" button** — advance to next room immediately ### 3. Treasure Room **Current behavior:** Instantly skipped, nothing happens. **Desired behavior:** - Takes **1 hour** to loot (timed progression) - Grants random loot over time: - **Primary loot:** Fabricator materials (manaCrystalDust, arcaneShard, earthShard, etc.) - **Rare loot:** Pre-crafted equipment (low chance) - Loot is granted progressively: e.g., at 10%, 50%, 95%, 100% of the hour — best items only at 100% - **Scales with floor:** - Low floors (1–10): 2–3 items, mostly common materials - Mid floors (10–50): 4–7 items, mix of common/uncommon - High floors (50+): 8–15 items, better material types, higher chance of rare drops - Loot table should be **modular** — easy to add new loot types in the future - All looted items listed in the activity log as they are found - Player sees a progress bar with thematic text: *"Rummaging through ancient chests and caches"* - **"Skip" button** — advance to next room immediately (forfeit remaining loot) ### 4. Puzzle Room **Current behavior:** Instantly skipped, nothing happens. The `puzzleProgress`/`puzzleRequired` fields exist on `FloorState` but are never ticked. **Desired behavior:** - Takes **up to 24 hours** to complete (base time, before attunement reductions) - Each puzzle is associated with **1 or more attunements** (already defined in `constants/rooms.ts` `PUZZLE_ROOMS`) - **Attunement-based time reduction:** - Single-attunement puzzle (e.g., enchanter trial): that attunement's level reduces time by up to **90%** at max level - Dual-attunement puzzle (e.g., enchanter + invoker): each attunement contributes up to **45%** reduction, cumulatively up to **90%** max - Reduction formula: linear scaling with attunement level (level 1 = small reduction, max level = full reduction for that attunement's share) - **Floor scaling:** Base time increases the higher the player has climbed: - Floors 1–20: ~4 hours base - Floors 20–50: ~8 hours base - Floors 50–100: ~16 hours base - Floors 100+: up to 24 hours base - No mana cost - Player sees a progress bar with thematic text based on puzzle type: - Enchanter puzzle: *"Deciphering an enchanted lock"* - Fabricator puzzle: *"Disassembling a mana-powered mechanism"* - Invoker puzzle: *"Communing with residual guardian spirits"* - Hybrid puzzle: *"Working through a complex attunement challenge"* - **No "Stay Longer" or "Skip" button** — puzzle rooms are mandatory and the player must complete them to proceed --- ## State Changes Needed ### `FloorState` type (`types/game.ts`) The existing progress fields are already present but need to be properly used: ```typescript // Already exists: libraryProgress?: number; libraryRequired?: number; recoveryProgress?: number; recoveryRequired?: number; puzzleProgress?: number; puzzleRequired?: number; puzzleId?: string; puzzleAttunements?: string[]; ``` New fields needed: ```typescript // Treasure room treasureProgress?: number; treasureRequired?: number; treasureLoot?: LootDrop[]; // pre-generated loot for this room treasureLootClaimed?: number[]; // indices of already-claimed loot // Library/Recovery "stay longer" flag libraryStayed?: boolean; // true if player already used "stay 1 hour more" recoveryStayed?: boolean; // true if player already used "stay 1 hour more" ``` ### `combat-state.types.ts` New actions needed: ```typescript // Tick non-combat room progress (called from game tick pipeline) tickNonCombatRoom: (hours: number) => void; // Player actions skipNonCombatRoom: () => void; stayLongerInRoom: () => void; // library or recovery only ``` --- ## Store Logic Changes ### `combat-descent-actions.ts` Replace the stub handling in `advanceRoomOrFloor()`: ```typescript // OLD (line 128-132): } else if (room.roomType === 'recovery' || room.roomType === 'treasure' || room.roomType === 'puzzle') { advanceRoomOrFloor(get, set); } // NEW: Each room type gets its own handler that initializes progress and waits ``` New handler functions needed: - `onEnterLibraryRoom()` — rewrite: set `libraryProgress = 0`, `libraryRequired = 1` (1 hour), `libraryStayed = false`; do NOT call `advanceRoomOrFloor` immediately - `onEnterRecoveryRoom()` — new: set `recoveryProgress = 0`, `recoveryRequired = 1`, `recoveryStayed = false`; apply 10× regen/conversion multiplier - `onEnterTreasureRoom()` — new: set `treasureProgress = 0`, `treasureRequired = 1`; pre-generate loot table based on floor - `onEnterPuzzleRoom()` — new: set `puzzleProgress = 0`, calculate `puzzleRequired` based on floor and attunement levels ### New tick handler: `tickNonCombatRoom()` Called every game tick when the current room is non-combat: 1. Increment progress by `HOURS_PER_TICK` 2. For treasure rooms: check loot thresholds and grant items 3. For recovery rooms: apply boosted regen/conversion (via mana store) 4. For library rooms: grant XP at 25× rate 5. When progress ≥ required: call `advanceRoomOrFloor()` 6. For library/recovery: if `stayLonged` is false and player pressed "Stay", add 1 more hour to `required` and set `stayed = true` --- ## UI Changes ### `RoomDisplay.tsx` Each non-combat room type needs: - A **progress bar** showing time elapsed / total time - **Thematic description text** (changes per room type) - **"Skip" button** (library, recovery, treasure only) - **"Stay 1 Hour More" button** (library and recovery only, disabled after use) ### `SpireCombatPage.tsx` - Wire the "Skip" and "Stay" buttons to new store actions - Pass `currentRoom` progress fields to `RoomDisplay` --- ## Loot System (Treasure Room) Create a new modular loot generation function: ```typescript function generateTreasureLoot(floor: number): LootDrop[] { // 1. Determine item count based on floor (2-15) // 2. Roll for each item: material (common) vs equipment (rare) // 3. Filter LOOT_DROPS by minFloor <= current floor // 4. Weight by dropChance, higher floors get better items // 5. Return array of LootDrop with amounts } ``` The `LOOT_DROPS` data already exists in `data/loot-drops.ts`. The function should be easy to extend with new loot tables in the future. --- ## Files to Change | File | Change | |---|---| | `stores/combat-descent-actions.ts` | Rewrite `onEnterLibraryRoom`; add `onEnterRecoveryRoom`, `onEnterTreasureRoom`, `onEnterPuzzleRoom`; add `tickNonCombatRoom`; add `skipNonCombatRoom` and `stayLongerInRoom` actions | | `stores/combat-state.types.ts` | Add new actions and state fields | | `stores/combatStore.ts` | Wire new actions | | `stores/pipelines/combat-tick.ts` | Add non-combat room tick phase (or handle in gameStore tick) | | `components/game/tabs/SpireCombatPage/RoomDisplay.tsx` | Add progress bars, thematic text, skip/stay buttons for each room type | | `components/game/tabs/SpireCombatPage/SpireCombatPage.tsx` | Wire skip/stay buttons to store actions | | `utils/spire-utils.ts` | Add `generateTreasureLoot()` function | | `types/game.ts` | Add treasure-related fields to `FloorState` | ## Out of Scope - Visual animations for loot drops - Sound effects - New loot drop definitions (use existing `LOOT_DROPS`) - New puzzle definitions (use existing `PUZZLE_ROOMS`) ## Acceptance Criteria - [ ] Library room takes 1 hour, grants 25× XP to a random unlocked discipline, has skip + stay buttons - [ ] Recovery room takes 1 hour, grants 10× mana regen and conversion rates, has skip + stay buttons - [ ] Treasure room takes 1 hour, grants 2–15 items scaling with floor (mostly materials, rare equipment), loot listed in activity log, has skip button - [ ] Puzzle room takes up to 24 hours (floor-scaled), reduced by attunement levels (up to 90%), no skip/stay buttons - [ ] All non-combat rooms show a progress bar with thematic description text - [ ] "Stay 1 Hour More" button works once per library/recovery room, then disables - [ ] "Skip" button on library/recovery/treasure advances immediately - [ ] Non-combat room progress ticks every game tick alongside combat
Anexim added the ai:todo label 2026-06-04 12:44:30 +02:00
n8n-gitea was assigned by Anexim 2026-06-04 12:44:30 +02:00
Anexim added ai:in-progress and removed ai:todo labels 2026-06-04 19:02:49 +02:00
Author
Owner

Starting implementation of non-combat room gameplay (Library, Recovery, Treasure, Puzzle). Reading source files first.

Starting implementation of non-combat room gameplay (Library, Recovery, Treasure, Puzzle). Reading source files first.
Anexim added ai:done and removed ai:in-progress labels 2026-06-04 19:33:14 +02:00
Author
Owner

Implementation complete. All non-combat rooms now have timed progression, player interaction, and meaningful effects:

Library Room: 1-hour timed progression, grants 25× discipline XP to a random discipline, with Skip and "Stay 1 Hour More" buttons.

Recovery Room: 1-hour timed progression, grants 10× mana regen and conversion rates, with Skip and "Stay 1 Hour More" buttons.

Treasure Room: 1-hour timed progression, grants 2-15 items scaling with floor (mostly materials, rare equipment), loot revealed progressively at 10%/50%/95%/100% thresholds, listed in activity log, with Skip button.

Puzzle Room: Up to 24-hour timed progression (floor-scaled: 4h/8h/16h/24h base), reduced by attunement levels (up to 90% reduction), mandatory (no skip/stay buttons), thematic text per puzzle type.

Files changed:

  • types/game.ts — Added treasure/stayed fields to FloorState
  • combat-state.types.ts — Added new action types (AdvanceRoomFn)
  • combat-descent-actions.ts — Rewrote room entry handlers, removed auto-skip
  • non-combat-room-actions.ts — New file with all tick/skip/stay logic
  • combatStore.ts — Wired new actions
  • gameStore.ts — Added non-combat room tick phase with recovery boost
  • spire-utils.ts — Added generateTreasureLoot function
  • RoomDisplay.tsx — Progress bars, thematic text, skip/stay buttons
  • SpireCombatPage.tsx — Wired skip/stay buttons to store actions

All 916 tests pass, all files under 400 lines, no circular dependencies.

Implementation complete. All non-combat rooms now have timed progression, player interaction, and meaningful effects: **Library Room**: 1-hour timed progression, grants 25× discipline XP to a random discipline, with Skip and "Stay 1 Hour More" buttons. **Recovery Room**: 1-hour timed progression, grants 10× mana regen and conversion rates, with Skip and "Stay 1 Hour More" buttons. **Treasure Room**: 1-hour timed progression, grants 2-15 items scaling with floor (mostly materials, rare equipment), loot revealed progressively at 10%/50%/95%/100% thresholds, listed in activity log, with Skip button. **Puzzle Room**: Up to 24-hour timed progression (floor-scaled: 4h/8h/16h/24h base), reduced by attunement levels (up to 90% reduction), mandatory (no skip/stay buttons), thematic text per puzzle type. **Files changed**: - `types/game.ts` — Added treasure/stayed fields to FloorState - `combat-state.types.ts` — Added new action types (AdvanceRoomFn) - `combat-descent-actions.ts` — Rewrote room entry handlers, removed auto-skip - `non-combat-room-actions.ts` — New file with all tick/skip/stay logic - `combatStore.ts` — Wired new actions - `gameStore.ts` — Added non-combat room tick phase with recovery boost - `spire-utils.ts` — Added generateTreasureLoot function - `RoomDisplay.tsx` — Progress bars, thematic text, skip/stay buttons - `SpireCombatPage.tsx` — Wired skip/stay buttons to store actions All 916 tests pass, all files under 400 lines, no circular dependencies.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Anexim/Mana-Loop#262