Spire descent system is broken: needs multi-room floors, proper descent traversal, and floor reset mechanics #254

Closed
opened 2026-06-02 19:17:35 +02:00 by Anexim · 2 comments
Owner

Spire Descent System — Design Spec & Current Gaps

Overview

The spire climbing/descending system has significant gaps between the intended design and the current implementation. This issue documents the expected behavior, what exists today, and what needs to be built.


Expected Behavior

Entering the Spire

  • Player enters the spire and starts on Floor 1, Room 0 / X (where X = total rooms on that floor).
  • Each floor has a variable number of rooms (already supported by getRoomsForFloor() in spire-utils.ts).
  • The player cannot exit the spire from anywhere except Floor 1, Room 0.

Room Types

  • Most rooms should be normal combat rooms.
  • Other room types exist: swarm, speed, guardian (every 10th floor), puzzle (every 7th floor), recovery, library, treasure.
  • Room type generation logic already exists in generateSpireRoomType() in spire-utils.ts.

Combat in Rooms

  • The player casts spells using equipped gear.
  • Each spellcast costs mana, takes time (cast speed), and when complete hits the enemy.
  • AoE spells hit multiple enemies if multiple exist.
  • Armor: reduces damage by a percentage.
  • Shield/Barrier: absorbs damage before HP.
  • Elemental bonuses/penalties: super effective (1.5×), same element (1.25×), weak (0.75×), neutral (1.0×).
  • Debuff and damage-over-time effects should be supported later.

Advancing Through Rooms

  • Once all enemies in a room are defeated, the player advances to the next room on the same floor.
  • Once all rooms on a floor are cleared, the player climbs to the next floor (Room 0 of the next floor).

Exiting the Spire (Descent)

  • When the player decides to exit, they switch from climbing up to climbing down.
  • They must re-traverse every floor and room in reverse order back to Floor 1, Room 0.
  • Example: if the player reached Floor 3, Room 6 (of 7), they must go:
    • F3 → R5, R4, R3, R2, R1, R0
    • F2 → R4, R3, R2, R1, R0 (assuming 5 rooms on floor 2)
    • F1 → R4, R3, R2, R1, R0 (assuming 5 rooms on floor 1)
    • Only then can they exit.

Floor Reset on Descent

  • When descending through a floor, there is a 50% chance that the floor has "reset".
  • If reset → player must clear the room normally (combat).
  • If not reset → player skips the room and continues to the next.

What Currently Exists

What Works

  1. getRoomsForFloor(floor) in spire-utils.ts — returns variable room counts (5–15, scaling with floor). Guardian floors always have 1 room.
  2. generateSpireFloorState(floor, roomIndex, totalRooms) in spire-utils.ts — generates a room with proper type and enemies.
  3. generateSpireRoomType() — room type selection with proper weights (combat, swarm, speed, guardian, puzzle, recovery, library, treasure).
  4. Combat tick processing in combat-actions.ts — handles spell casting, mana cost, damage application, elemental bonuses, armor, barrier, shield.
  5. enterSpireMode() in combatStore.ts — initializes spire state.
  6. exitSpireMode() in combatStore.ts — exits spire, resets to floor 1.
  7. clearedFloors: Record<number, boolean> — tracks which floors have been cleared (already persisted).
  8. climbDirection: 'up' | 'down' | null — state field exists but is never read by combat logic.
  9. isDescending: boolean — anti-spam flag exists.
  10. SpireCombatPage.tsx — local roomsCleared state tracks rooms cleared on current floor.
  11. Elemental bonus system — fully implemented in combat-utils.ts via getElementalBonus().
  12. Armor/barrier/shield — supported in combat tick and damage pipeline.

What's Broken / Missing

  1. No multi-room traversal in the store: The store only tracks currentRoom: FloorState (one room per floor). There is no currentRoomIndex or roomsPerFloor in the store state. The multi-room infrastructure in spire-utils.ts exists but is not wired into the store tick loop.

  2. Combat always advances up: processCombatTick() in combat-actions.ts always does currentFloor + 1 when floor HP reaches 0. It never checks climbDirection. There is no concept of "room cleared → next room on same floor."

  3. climbDownFloor() is broken: It calls generateFloorState() (non-spire room generator from room-utils.ts) instead of generateSpireFloorState(). It also decrements by exactly 1 floor with no room-by-room descent.

  4. No descent traversal logic: There is no system to reverse-traverse all floors/rooms back to Floor 1, Room 0. The startClimbDown() sets climbDirection: 'down' but nothing reads it.

  5. No floor reset mechanic: The 50% floor reset chance on descent is not implemented anywhere.

  6. handleRoomCleared exists but is never called: In SpireCombatPage.tsx, _handleRoomCleared is defined but has no UI trigger and is not wired to the combat tick. Room clearing must happen through the store tick, but the store doesn't support multi-room floors.

  7. SpireHeader.tsx "Exit Spire" button only shows at currentFloor === 1, but with broken descent logic, the player can never properly return to Floor 1 Room 0 after climbing.

  8. Descended floors use non-spire room generation: climbDownFloor()generateFloorState() produces normal rooms without recovery/library/treasure types, which is inconsistent with the spire experience.

  9. clearedFloors is tracked but never used for descent: The field exists and is persisted, but no code reads it to determine whether a descending floor should have enemies or be empty.


Files That Need Changes

File Change
stores/combatStore.ts Add currentRoomIndex and roomsPerFloor state; fix climbDownFloor() to use spire generation; add descent traversal actions
stores/combat-state.types.ts Add new fields and action types
stores/combat-actions.ts Room-level clearing logic; check climbDirection before advancing floor vs. room
stores/pipelines/combat-tick.ts Potentially room-level floor clearing callback
utils/spire-utils.ts May need a deterministic seeded room count (for consistent descent room counts)
utils/room-utils.ts Add 50% reset chance helper
components/game/tabs/SpireCombatPage/SpireCombatPage.tsx Wire handleRoomCleared to combat tick; track room index in local or store state; handle descent UI
components/game/tabs/SpireCombatPage/SpireHeader.tsx Exit button should show when at Floor 1 AND room 0 during descent

Key Formulas (for reference)

  • Rooms per floor (non-guardian): 5 + min(10, floor/20) + random(0-2)
  • Guardian floors: exactly 1 room
  • Floor HP: 100 + floor × 50 + floor^1.7 (non-guardian)
  • Spire enemy armor: 0 below floor 10; chance = min(0.5, (floor-10)*0.01); value = 5%–30%
  • Spire enemy barrier: 0 below floor 15; chance scales; value = 10%–30%
  • Elemental bonuses: same = 1.25×, opposite = 1.5×, weak = 0.75×, neutral = 1.0×
  • Floor reset on descent: 50% chance per floor

Related Files (for context)

  • src/lib/game/utils/spire-utils.ts — multi-room generation infrastructure
  • src/lib/game/utils/floor-utils.ts — floor HP, floor element
  • src/lib/game/utils/enemy-utils.ts — enemy naming
  • src/lib/game/utils/enemy-generator.ts — modifier-based enemy system
  • src/lib/game/utils/room-utils.ts — legacy single-room generation
  • src/lib/game/constants/rooms.ts — room type constants
  • src/lib/game/constants/elements.ts — element definitions and opposites
  • src/lib/game/utils/combat-utils.ts — damage calculation, elemental bonuses
  • src/lib/game/stores/combat-actions.ts — combat tick processing
  • src/lib/game/stores/pipelines/combat-tick.ts — combat callbacks
  • src/lib/game/data/guardian-encounters.ts — guardian definitions
# Spire Descent System — Design Spec & Current Gaps ## Overview The spire climbing/descending system has significant gaps between the intended design and the current implementation. This issue documents the expected behavior, what exists today, and what needs to be built. --- ## Expected Behavior ### Entering the Spire - Player enters the spire and starts on **Floor 1, Room 0 / X** (where X = total rooms on that floor). - Each floor has a **variable number of rooms** (already supported by `getRoomsForFloor()` in `spire-utils.ts`). - The player cannot exit the spire from anywhere except Floor 1, Room 0. ### Room Types - Most rooms should be **normal combat rooms**. - Other room types exist: `swarm`, `speed`, `guardian` (every 10th floor), `puzzle` (every 7th floor), `recovery`, `library`, `treasure`. - Room type generation logic already exists in `generateSpireRoomType()` in `spire-utils.ts`. ### Combat in Rooms - The player casts spells using equipped gear. - Each spellcast costs mana, takes time (cast speed), and when complete hits the enemy. - AoE spells hit multiple enemies if multiple exist. - **Armor**: reduces damage by a percentage. - **Shield/Barrier**: absorbs damage before HP. - **Elemental bonuses/penalties**: super effective (1.5×), same element (1.25×), weak (0.75×), neutral (1.0×). - Debuff and damage-over-time effects should be supported later. ### Advancing Through Rooms - Once all enemies in a room are defeated, the player advances to the **next room** on the same floor. - Once all rooms on a floor are cleared, the player **climbs to the next floor** (Room 0 of the next floor). ### Exiting the Spire (Descent) - When the player decides to exit, they switch from climbing **up** to climbing **down**. - They must re-traverse every floor and room in **reverse order** back to Floor 1, Room 0. - Example: if the player reached Floor 3, Room 6 (of 7), they must go: - F3 → R5, R4, R3, R2, R1, R0 - F2 → R4, R3, R2, R1, R0 (assuming 5 rooms on floor 2) - F1 → R4, R3, R2, R1, R0 (assuming 5 rooms on floor 1) - Only then can they exit. ### Floor Reset on Descent - When descending through a floor, there is a **50% chance** that the floor has "reset". - If reset → player must clear the room normally (combat). - If not reset → player skips the room and continues to the next. --- ## What Currently Exists ### ✅ What Works 1. **`getRoomsForFloor(floor)`** in `spire-utils.ts` — returns variable room counts (5–15, scaling with floor). Guardian floors always have 1 room. 2. **`generateSpireFloorState(floor, roomIndex, totalRooms)`** in `spire-utils.ts` — generates a room with proper type and enemies. 3. **`generateSpireRoomType()`** — room type selection with proper weights (combat, swarm, speed, guardian, puzzle, recovery, library, treasure). 4. **Combat tick processing** in `combat-actions.ts` — handles spell casting, mana cost, damage application, elemental bonuses, armor, barrier, shield. 5. **`enterSpireMode()`** in `combatStore.ts` — initializes spire state. 6. **`exitSpireMode()`** in `combatStore.ts` — exits spire, resets to floor 1. 7. **`clearedFloors: Record<number, boolean>`** — tracks which floors have been cleared (already persisted). 8. **`climbDirection: 'up' | 'down' | null`** — state field exists but is **never read** by combat logic. 9. **`isDescending: boolean`** — anti-spam flag exists. 10. **`SpireCombatPage.tsx`** — local `roomsCleared` state tracks rooms cleared on current floor. 11. **Elemental bonus system** — fully implemented in `combat-utils.ts` via `getElementalBonus()`. 12. **Armor/barrier/shield** — supported in combat tick and damage pipeline. ### ❌ What's Broken / Missing 1. **No multi-room traversal in the store**: The store only tracks `currentRoom: FloorState` (one room per floor). There is no `currentRoomIndex` or `roomsPerFloor` in the store state. The multi-room infrastructure in `spire-utils.ts` exists but is not wired into the store tick loop. 2. **Combat always advances up**: `processCombatTick()` in `combat-actions.ts` always does `currentFloor + 1` when floor HP reaches 0. It never checks `climbDirection`. There is no concept of "room cleared → next room on same floor." 3. **`climbDownFloor()` is broken**: It calls `generateFloorState()` (non-spire room generator from `room-utils.ts`) instead of `generateSpireFloorState()`. It also decrements by exactly 1 floor with no room-by-room descent. 4. **No descent traversal logic**: There is no system to reverse-traverse all floors/rooms back to Floor 1, Room 0. The `startClimbDown()` sets `climbDirection: 'down'` but nothing reads it. 5. **No floor reset mechanic**: The 50% floor reset chance on descent is not implemented anywhere. 6. **`handleRoomCleared` exists but is never called**: In `SpireCombatPage.tsx`, `_handleRoomCleared` is defined but has no UI trigger and is not wired to the combat tick. Room clearing must happen through the store tick, but the store doesn't support multi-room floors. 7. **`SpireHeader.tsx` "Exit Spire" button** only shows at `currentFloor === 1`, but with broken descent logic, the player can never properly return to Floor 1 Room 0 after climbing. 8. **Descended floors use non-spire room generation**: `climbDownFloor()` → `generateFloorState()` produces normal rooms without recovery/library/treasure types, which is inconsistent with the spire experience. 9. **`clearedFloors` is tracked but never used for descent**: The field exists and is persisted, but no code reads it to determine whether a descending floor should have enemies or be empty. --- ## Files That Need Changes | File | Change | |---|---| | `stores/combatStore.ts` | Add `currentRoomIndex` and `roomsPerFloor` state; fix `climbDownFloor()` to use spire generation; add descent traversal actions | | `stores/combat-state.types.ts` | Add new fields and action types | | `stores/combat-actions.ts` | Room-level clearing logic; check `climbDirection` before advancing floor vs. room | | `stores/pipelines/combat-tick.ts` | Potentially room-level floor clearing callback | | `utils/spire-utils.ts` | May need a deterministic seeded room count (for consistent descent room counts) | | `utils/room-utils.ts` | Add 50% reset chance helper | | `components/game/tabs/SpireCombatPage/SpireCombatPage.tsx` | Wire `handleRoomCleared` to combat tick; track room index in local or store state; handle descent UI | | `components/game/tabs/SpireCombatPage/SpireHeader.tsx` | Exit button should show when at Floor 1 AND room 0 during descent | --- ## Key Formulas (for reference) - **Rooms per floor** (non-guardian): `5 + min(10, floor/20) + random(0-2)` - **Guardian floors**: exactly 1 room - **Floor HP**: `100 + floor × 50 + floor^1.7` (non-guardian) - **Spire enemy armor**: 0 below floor 10; chance = `min(0.5, (floor-10)*0.01)`; value = `5%–30%` - **Spire enemy barrier**: 0 below floor 15; chance scales; value = `10%–30%` - **Elemental bonuses**: same = 1.25×, opposite = 1.5×, weak = 0.75×, neutral = 1.0× - **Floor reset on descent**: 50% chance per floor --- ## Related Files (for context) - `src/lib/game/utils/spire-utils.ts` — multi-room generation infrastructure - `src/lib/game/utils/floor-utils.ts` — floor HP, floor element - `src/lib/game/utils/enemy-utils.ts` — enemy naming - `src/lib/game/utils/enemy-generator.ts` — modifier-based enemy system - `src/lib/game/utils/room-utils.ts` — legacy single-room generation - `src/lib/game/constants/rooms.ts` — room type constants - `src/lib/game/constants/elements.ts` — element definitions and opposites - `src/lib/game/utils/combat-utils.ts` — damage calculation, elemental bonuses - `src/lib/game/stores/combat-actions.ts` — combat tick processing - `src/lib/game/stores/pipelines/combat-tick.ts` — combat callbacks - `src/lib/game/data/guardian-encounters.ts` — guardian definitions
Anexim added the ai:todo label 2026-06-02 19:17:35 +02:00
n8n-gitea was assigned by Anexim 2026-06-02 19:17:35 +02:00
Author
Owner

Context gathering complete. 6 parallel sub-agents analyzed:\n- All 4 combat store files (combatStore.ts, gameLoopActions.ts, combat-actions.ts, combat-state.types.ts)\n- All 6 spire utility files (spire-utils.ts, room-utils.ts, floor-utils.ts, enemy-utils.ts, enemy-generator.ts, constants/rooms.ts)\n- All 4 combat pipeline files (combat-tick.ts, combat-utils.ts, core.ts, elements.ts)\n- All 7 SpireCombatPage UI components\n- 10 test files covering spire utilities, rooms, floors, enemies, combat actions, barriers\n- SpireSummaryTab.tsx and game type definitions\n\nFindings confirmed: multi-room infrastructure exists in spire-utils.ts but is NOT wired into the store. The field clearedFloors exists but is never read during descent. climbDirection state exists but is never checked by combat ticks. No floor-reset mechanic exists. Full details and file-by-file breakdown are in the issue body.

Context gathering complete. 6 parallel sub-agents analyzed:\n- All 4 combat store files (combatStore.ts, gameLoopActions.ts, combat-actions.ts, combat-state.types.ts)\n- All 6 spire utility files (spire-utils.ts, room-utils.ts, floor-utils.ts, enemy-utils.ts, enemy-generator.ts, constants/rooms.ts)\n- All 4 combat pipeline files (combat-tick.ts, combat-utils.ts, core.ts, elements.ts)\n- All 7 SpireCombatPage UI components\n- 10 test files covering spire utilities, rooms, floors, enemies, combat actions, barriers\n- SpireSummaryTab.tsx and game type definitions\n\nFindings confirmed: multi-room infrastructure exists in spire-utils.ts but is NOT wired into the store. The field `clearedFloors` exists but is never read during descent. `climbDirection` state exists but is never checked by combat ticks. No floor-reset mechanic exists. Full details and file-by-file breakdown are in the issue body.
Author
Owner

Closed in favor of issue #255, which supersedes this one with the full spec docs as the authoritative source of truth.

Closed in favor of issue #255, which supersedes this one with the full spec docs as the authoritative source of truth.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Anexim/Mana-Loop#254