🐛 Entering the spire crashes the game irreversibly #125

Closed
opened 2026-05-22 13:20:56 +02:00 by Anexim · 2 comments
Owner

Bug

Entering the spire causes an irreversible game crash. The player gets stuck in a crashed state and even refreshing the page doesn't fix it (because the corrupted state is persisted in localStorage via Zustand persist).

Root Cause Analysis

Critical Issue 1: enterSpireMode() doesn't reset state

enterSpireMode() in combatStore.ts only sets { spireMode: true }. It does NOT reset:

  • currentFloor (stays at whatever it was — could be 100)
  • floorHP / floorMaxHP (stale values)
  • currentRoom (stale room from previous climb)
  • castProgress, climbDirection, isDescending

Critical Issue 2: Stale persisted state

On first render of SpireCombatPage (before useEffect fires), currentRoom is the stale persisted value from localStorage. If this value has an unhandled roomType (like 'recovery', 'library', 'treasure' from spire-utils.ts), or is null/undefined, RoomDisplay crashes on floorState.roomType access.

Critical Issue 3: Type system bypassed

spire-utils.ts uses as unknown as FloorState casts for recovery/library/treasure rooms, bypassing TypeScript's type checking. These room types have different field names than what RoomDisplay expects.

Critical Issue 4: No null check on currentRoom

SpireCombatPage passes floorState={currentRoom} to RoomDisplay without checking if currentRoom is valid.

The Irreversible Crash Loop

  1. Player enters spire → state persisted with spireMode: true
  2. Crash occurs → ErrorBoundary catches it
  3. Player refreshes → Zustand persist restores spireMode: true → SpireCombatPage mounts → crashes again
  4. Player is stuck forever with no way to exit spire mode

Files Involved

  • src/lib/game/stores/combatStore.ts (enterSpireMode, exitSpireMode)
  • src/lib/game/stores/combat-actions.ts (spire actions)
  • src/components/game/tabs/SpireCombatPage/SpireCombatPage.tsx
  • src/components/game/tabs/SpireCombatPage/RoomDisplay.tsx
  • src/lib/game/utils/spire-utils.ts
  • src/app/page.tsx (ErrorBoundary wrapping)

Suggested Fix

  1. enterSpireMode() must fully reset spire state: floor=1, fresh room, HP reset, action='climb'
  2. Add null/valid check on currentRoom before rendering RoomDisplay
  3. Fix exitSpireMode() to reset all spire-related state
  4. Add a "force exit spire" recovery mechanism (e.g., a keyboard shortcut or a button outside the ErrorBoundary) to break the crash loop
  5. Fix the type system bypass in spire-utils.ts — properly extend FloorState type or handle different room types explicitly
## Bug Entering the spire causes an irreversible game crash. The player gets stuck in a crashed state and even refreshing the page doesn't fix it (because the corrupted state is persisted in localStorage via Zustand persist). ## Root Cause Analysis ### Critical Issue 1: `enterSpireMode()` doesn't reset state `enterSpireMode()` in `combatStore.ts` only sets `{ spireMode: true }`. It does NOT reset: - `currentFloor` (stays at whatever it was — could be 100) - `floorHP` / `floorMaxHP` (stale values) - `currentRoom` (stale room from previous climb) - `castProgress`, `climbDirection`, `isDescending` ### Critical Issue 2: Stale persisted state On first render of `SpireCombatPage` (before `useEffect` fires), `currentRoom` is the stale persisted value from localStorage. If this value has an unhandled `roomType` (like `'recovery'`, `'library'`, `'treasure'` from `spire-utils.ts`), or is `null`/`undefined`, `RoomDisplay` crashes on `floorState.roomType` access. ### Critical Issue 3: Type system bypassed `spire-utils.ts` uses `as unknown as FloorState` casts for recovery/library/treasure rooms, bypassing TypeScript's type checking. These room types have different field names than what `RoomDisplay` expects. ### Critical Issue 4: No null check on `currentRoom` `SpireCombatPage` passes `floorState={currentRoom}` to `RoomDisplay` without checking if `currentRoom` is valid. ### The Irreversible Crash Loop 1. Player enters spire → state persisted with `spireMode: true` 2. Crash occurs → ErrorBoundary catches it 3. Player refreshes → Zustand persist restores `spireMode: true` → SpireCombatPage mounts → crashes again 4. Player is stuck forever with no way to exit spire mode ## Files Involved - `src/lib/game/stores/combatStore.ts` (enterSpireMode, exitSpireMode) - `src/lib/game/stores/combat-actions.ts` (spire actions) - `src/components/game/tabs/SpireCombatPage/SpireCombatPage.tsx` - `src/components/game/tabs/SpireCombatPage/RoomDisplay.tsx` - `src/lib/game/utils/spire-utils.ts` - `src/app/page.tsx` (ErrorBoundary wrapping) ## Suggested Fix 1. **`enterSpireMode()` must fully reset spire state**: floor=1, fresh room, HP reset, action='climb' 2. **Add null/valid check on `currentRoom`** before rendering `RoomDisplay` 3. **Fix `exitSpireMode()`** to reset all spire-related state 4. **Add a "force exit spire" recovery mechanism** (e.g., a keyboard shortcut or a button outside the ErrorBoundary) to break the crash loop 5. **Fix the type system bypass** in `spire-utils.ts` — properly extend `FloorState` type or handle different room types explicitly
Anexim added the ai:todo label 2026-05-22 13:20:56 +02:00
n8n-gitea was assigned by Anexim 2026-05-22 13:20:56 +02:00
Author
Owner

Fixed. Changes made:

  1. combatStore.tsenterSpireMode() now fully resets spire state (floor=1, fresh room, HP reset, castProgress=0, clearedFloors={}, etc.)
  2. combatStore.tsexitSpireMode() now resets all spire-related state back to defaults
  3. RoomDisplay.tsx — Added null/undefined guard on floorState before rendering
  4. spire-utils.ts — Removed as unknown as FloorState casts; recovery/library/treasure rooms now return proper types
  5. types/game.ts — Extended RoomType to include 'recovery' | 'library' | 'treasure'; added recoveryProgress/Required and libraryProgress/Required fields to FloorState
  6. ErrorBoundary.tsx — Added optional onReset callback with a "Reset & Recover" button
  7. page.tsx — Spire mode ErrorBoundary now has onReset that calls exitSpireMode() to break crash loop
  8. RoomDisplay.tsx — Fixed recovery room to use recoveryProgress/recoveryRequired instead of puzzleProgress/puzzleRequired
✅ Fixed. Changes made: 1. **combatStore.ts** — `enterSpireMode()` now fully resets spire state (floor=1, fresh room, HP reset, castProgress=0, clearedFloors={}, etc.) 2. **combatStore.ts** — `exitSpireMode()` now resets all spire-related state back to defaults 3. **RoomDisplay.tsx** — Added null/undefined guard on `floorState` before rendering 4. **spire-utils.ts** — Removed `as unknown as FloorState` casts; recovery/library/treasure rooms now return proper types 5. **types/game.ts** — Extended `RoomType` to include `'recovery' | 'library' | 'treasure'`; added `recoveryProgress/Required` and `libraryProgress/Required` fields to `FloorState` 6. **ErrorBoundary.tsx** — Added optional `onReset` callback with a "Reset & Recover" button 7. **page.tsx** — Spire mode ErrorBoundary now has `onReset` that calls `exitSpireMode()` to break crash loop 8. **RoomDisplay.tsx** — Fixed recovery room to use `recoveryProgress`/`recoveryRequired` instead of `puzzleProgress`/`puzzleRequired`
Author
Owner

Fixed spire crash (enterSpireMode now resets state and exitSpireMode resets spire state). Added null guard in RoomDisplay and proper type handling for recovery/library/treasure rooms.

✅ Fixed spire crash (enterSpireMode now resets state and exitSpireMode resets spire state). Added null guard in RoomDisplay and proper type handling for recovery/library/treasure rooms.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Anexim/Mana-Loop#125