import { Component, type ErrorInfo, type ReactNode } from "react"; import { restartActiveDungeonSession } from "../game/dungeonSession"; import { useGameStore } from "../game/store"; interface Props { children: ReactNode } interface State { error: Error | null; retryCount: number; retryScheduled: boolean; } const MAX_AUTOMATIC_RETRIES = 1; const AUTOMATIC_RETRY_DELAY_MS = 500; const RETRY_BUDGET_RESET_MS = 60_000; export class SceneErrorBoundary extends Component { state: State = { error: null, retryCount: 0, retryScheduled: false }; private retryTimer: number | null = null; private retryBudgetTimer: number | null = null; static getDerivedStateFromError(error: Error): Partial { return { error }; } componentDidCatch(error: Error, info: ErrorInfo) { console.error("Healer Man scene failed", error, info.componentStack); useGameStore.getState().setAssetStatus("error", error.message); if (this.retryBudgetTimer !== null) { window.clearTimeout(this.retryBudgetTimer); this.retryBudgetTimer = null; } if (this.state.retryCount < MAX_AUTOMATIC_RETRIES) this.scheduleRetry(); } componentWillUnmount(): void { if (this.retryTimer !== null) window.clearTimeout(this.retryTimer); if (this.retryBudgetTimer !== null) window.clearTimeout(this.retryBudgetTimer); } private scheduleRetry(): void { if (this.retryTimer !== null) return; this.setState({ retryScheduled: true }); this.retryTimer = window.setTimeout(() => { this.retryTimer = null; this.retryScene(); }, AUTOMATIC_RETRY_DELAY_MS); } private retryScene = (): void => { const retryCount = this.state.retryCount + 1; try { if (!restartActiveDungeonSession()) { this.setState({ retryScheduled: false }); return; } this.setState({ error: null, retryCount, retryScheduled: false }); this.retryBudgetTimer = window.setTimeout(() => { this.retryBudgetTimer = null; if (!this.state.error) this.setState({ retryCount: 0 }); }, RETRY_BUDGET_RESET_MS); } catch (error) { const nextError = error instanceof Error ? error : new Error("The dungeon retry failed."); console.error("Healer Man scene retry failed", nextError); useGameStore.getState().setAssetStatus("error", nextError.message); this.setState({ error: nextError, retryCount, retryScheduled: false }); } } render() { if (!this.state.error) return this.props.children; return (

Dungeon load interrupted

{this.state.retryScheduled ? "Recovering the expedition..." : "The cavern could not be assembled."}

{this.state.retryScheduled ? "Clearing the failed asset cache and rebuilding the dungeon in smaller batches." : this.state.error.message}

{!this.state.retryScheduled && ( )}
); } }