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