diff --git a/apps/web/src/arcade-games/know-your-world/components/MapRenderer.tsx b/apps/web/src/arcade-games/know-your-world/components/MapRenderer.tsx index dcf0d75a..465a712d 100644 --- a/apps/web/src/arcade-games/know-your-world/components/MapRenderer.tsx +++ b/apps/web/src/arcade-games/know-your-world/components/MapRenderer.tsx @@ -53,14 +53,14 @@ import { import { CelebrationOverlay } from './CelebrationOverlay' import { DevCropTool } from './DevCropTool' -// Debug flag: show technical info in magnifier (dev only) -const SHOW_MAGNIFIER_DEBUG_INFO = process.env.NODE_ENV === 'development' +// Debug flag: show technical info in magnifier (gated by isVisualDebugEnabled at runtime) +const SHOW_MAGNIFIER_DEBUG_INFO = true -// Debug flag: show bounding boxes with importance scores (dev only) -const SHOW_DEBUG_BOUNDING_BOXES = process.env.NODE_ENV === 'development' +// Debug flag: show bounding boxes with importance scores (gated by isVisualDebugEnabled at runtime) +const SHOW_DEBUG_BOUNDING_BOXES = true -// Debug flag: show safe zone rectangles (leftover area and crop region) - dev only -const SHOW_SAFE_ZONE_DEBUG = process.env.NODE_ENV === 'development' +// Debug flag: show safe zone rectangles (gated by isVisualDebugEnabled at runtime) +const SHOW_SAFE_ZONE_DEBUG = true // Precision mode threshold: screen pixel ratio that triggers pointer lock recommendation const PRECISION_MODE_THRESHOLD = 20 @@ -589,7 +589,7 @@ export function MapRenderer({ const { resolvedTheme } = useTheme() const isDark = resolvedTheme === 'dark' - // Visual debug mode from global context (only enabled in dev AND when user toggles it on) + // Visual debug mode from global context (enabled when user toggles it on, in dev or with ?debug=1) const { isVisualDebugEnabled } = useVisualDebugSafe() // Effective debug flags - combine prop with context @@ -1444,7 +1444,15 @@ export function MapRenderer({ }) return result - }, [otherPlayerCursors, viewerId, gameMode, currentPlayer, localPlayerId, playerMetadata, memberPlayers]) + }, [ + otherPlayerCursors, + viewerId, + gameMode, + currentPlayer, + localPlayerId, + playerMetadata, + memberPlayers, + ]) // State for give-up zoom animation target values const [giveUpZoomTarget, setGiveUpZoomTarget] = useState({ @@ -5846,6 +5854,101 @@ export function MapRenderer({ )} + {/* Hot/Cold Debug Panel - shows enable conditions and current state */} + {isVisualDebugEnabled && ( +
+
+ 🔥 Hot/Cold Debug +
+ + {/* Enable conditions */} +
+
+ Enable Conditions: +
+
+ {assistanceAllowsHotCold ? '✓' : '✗'} Assistance allows: {assistanceLevel} +
+
+ {hotColdEnabled ? '✓' : '✗'} User toggle: {hotColdEnabled ? 'ON' : 'OFF'} +
+
+ {hasAnyFinePointer ? '✓' : '✗'} Fine pointer (desktop) +
+
+ {showMagnifier ? '✓' : '✗'} Magnifier active +
+
+ {isMobileMapDragging ? '✓' : '✗'} Mobile dragging +
+ {gameMode === 'turn-based' && ( +
+ {currentPlayer === localPlayerId ? '✓' : '✗'} Is my turn +
+ )} +
+ + {/* Overall status */} +
+ Status: + {assistanceAllowsHotCold && + hotColdEnabled && + (hasAnyFinePointer || showMagnifier || isMobileMapDragging) && + (gameMode !== 'turn-based' || currentPlayer === localPlayerId) + ? '🟢 ENABLED' + : '🔴 DISABLED'} +
+ + {/* Current feedback */} +
+ Current feedback: + {hotColdFeedbackType || 'none'} +
+ + {/* Target info */} +
+ Target region: + {currentPrompt || 'none'} +
+
+ )} + {/* Other players' cursors - show in multiplayer when not exclusively our turn */} {/* Cursor rendering debug - only log when cursor count changes */} {svgRef.current && @@ -5872,7 +5975,10 @@ export function MapRenderer({ } } if (!player) { - console.log('[CursorShare] ⚠️ No player found in playerMetadata or memberPlayers for playerId:', position.playerId) + console.log( + '[CursorShare] ⚠️ No player found in playerMetadata or memberPlayers for playerId:', + position.playerId + ) return null } diff --git a/apps/web/src/components/AppNavBar.tsx b/apps/web/src/components/AppNavBar.tsx index af252678..f23ea2e7 100644 --- a/apps/web/src/components/AppNavBar.tsx +++ b/apps/web/src/components/AppNavBar.tsx @@ -73,7 +73,7 @@ function MenuContent({ }) { const isDark = resolvedTheme === 'dark' const { open: openDeploymentInfo } = useDeploymentInfo() - const { isVisualDebugEnabled, toggleVisualDebug, isDevelopment } = useVisualDebug() + const { isVisualDebugEnabled, toggleVisualDebug, isDebugAllowed } = useVisualDebug() const linkStyle = { display: 'flex', @@ -317,8 +317,8 @@ function MenuContent({ - {/* Developer Section - only in development */} - {isDevelopment && ( + {/* Developer Section - shown in dev or when ?debug=1 is used */} + {isDebugAllowed && ( <>
Developer
@@ -441,8 +441,8 @@ function MenuContent({ - {/* Developer Section - only in development */} - {isDevelopment && ( + {/* Developer Section - shown in dev or when ?debug=1 is used */} + {isDebugAllowed && ( <>
Developer
diff --git a/apps/web/src/contexts/VisualDebugContext.tsx b/apps/web/src/contexts/VisualDebugContext.tsx index d27c6d86..6a0e5c82 100644 --- a/apps/web/src/contexts/VisualDebugContext.tsx +++ b/apps/web/src/contexts/VisualDebugContext.tsx @@ -3,23 +3,48 @@ import { createContext, type ReactNode, useCallback, useContext, useEffect, useState } from 'react' const STORAGE_KEY = 'visual-debug-enabled' +const PRODUCTION_DEBUG_KEY = 'allow-production-debug' interface VisualDebugContextType { - /** Whether visual debug elements are enabled (only functional in development) */ + /** Whether visual debug elements are enabled */ isVisualDebugEnabled: boolean /** Toggle visual debug mode on/off */ toggleVisualDebug: () => void - /** Whether we're in development mode (visual debug toggle only shows in dev) */ + /** Whether we're in development mode */ isDevelopment: boolean + /** Whether debug mode is allowed (dev mode OR production debug unlocked) */ + isDebugAllowed: boolean } const VisualDebugContext = createContext(null) export function VisualDebugProvider({ children }: { children: ReactNode }) { const [isEnabled, setIsEnabled] = useState(false) + const [productionDebugAllowed, setProductionDebugAllowed] = useState(false) const isDevelopment = process.env.NODE_ENV === 'development' - // Load from localStorage on mount + // Check for production debug unlock via URL param or localStorage + useEffect(() => { + if (typeof window === 'undefined') return + + // Check URL param: ?debug=1 or ?debug=true + const urlParams = new URLSearchParams(window.location.search) + const debugParam = urlParams.get('debug') + if (debugParam === '1' || debugParam === 'true') { + // Unlock production debug permanently + localStorage.setItem(PRODUCTION_DEBUG_KEY, 'true') + setProductionDebugAllowed(true) + return + } + + // Check localStorage for previously unlocked + const stored = localStorage.getItem(PRODUCTION_DEBUG_KEY) + if (stored === 'true') { + setProductionDebugAllowed(true) + } + }, []) + + // Load debug enabled state from localStorage on mount useEffect(() => { if (typeof window === 'undefined') return const stored = localStorage.getItem(STORAGE_KEY) @@ -38,8 +63,11 @@ export function VisualDebugProvider({ children }: { children: ReactNode }) { setIsEnabled((prev) => !prev) }, []) - // Only enable visual debug in development mode - const isVisualDebugEnabled = isDevelopment && isEnabled + // Debug is allowed in development OR if production debug is unlocked + const isDebugAllowed = isDevelopment || productionDebugAllowed + + // Enable visual debug if allowed AND user has toggled it on + const isVisualDebugEnabled = isDebugAllowed && isEnabled return ( {children} @@ -69,10 +98,12 @@ export function useVisualDebug(): VisualDebugContextType { export function useVisualDebugSafe(): VisualDebugContextType { const context = useContext(VisualDebugContext) if (!context) { + const isDev = process.env.NODE_ENV === 'development' return { isVisualDebugEnabled: false, toggleVisualDebug: () => {}, - isDevelopment: process.env.NODE_ENV === 'development', + isDevelopment: isDev, + isDebugAllowed: isDev, } } return context