diff --git a/src/components/gameplay/PlayerMapView.js b/src/components/gameplay/PlayerMapView.js
index 9617411..2a405c3 100644
--- a/src/components/gameplay/PlayerMapView.js
+++ b/src/components/gameplay/PlayerMapView.js
@@ -11,8 +11,9 @@ const PlayerMapView = ({userLocation, visibleCaches, heading, claimDistance}) =>
if (!userLocation) return null;
- const hasHeading = heading !== null && heading !== undefined;
- const coneCoords = hasHeading ? getFovCone(userLocation, heading) : null;
+ const coneCoords = (heading !== null && heading !== undefined)
+ ? getFovCone(userLocation, heading)
+ : null;
return (
/>
))}
- {/* FOV cone when heading is available */}
+ {/* FOV heading cone */}
{coneCoords && (
strokeWidth={1}
/>
)}
- {/* Proximity circle when heading is NOT available (iOS fallback) */}
- {!hasHeading && claimDistance > 0 && (
-
- )}
);
};
diff --git a/src/components/screens/ExpandedMapScreen.js b/src/components/screens/ExpandedMapScreen.js
index 1530841..289eb6b 100644
--- a/src/components/screens/ExpandedMapScreen.js
+++ b/src/components/screens/ExpandedMapScreen.js
@@ -3,7 +3,7 @@ import {Platform, StyleSheet, View} from 'react-native';
import MapView, {Circle, Marker, Polygon} from 'react-native-maps';
import * as Location from 'expo-location';
import Screen from '../layout/Screen';
-import {getFovCone, isInClaimCone, isWithinRadius} from '../../utils/geoMath';
+import {getFovCone, isInClaimCone} from '../../utils/geoMath';
const DEFAULT_REGION = {latitude: 51.5074, longitude: -0.1278, latitudeDelta: 0.01, longitudeDelta: 0.01};
@@ -71,14 +71,10 @@ const ExpandedMapScreen = ({route}) => {
? getFovCone(userLocation, heading)
: null;
- // For players — when heading is available use FOV cone, otherwise fall back
- // to proximity-only so the expanded map works on iPhones without compass data
- const visiblePlayerCaches = (!isAdmin && userLocation)
- ? caches.filter((c) =>
- hasHeading
- ? isInClaimCone(heading, userLocation, c.coordinates, claimDistance)
- : isWithinRadius(userLocation, c.coordinates, claimDistance),
- )
+ // For players — heading is required so the player must point their device
+ // towards a cache for it to appear (same logic as the main MapScreen)
+ const visiblePlayerCaches = (!isAdmin && userLocation && hasHeading)
+ ? caches.filter((c) => isInClaimCone(heading, userLocation, c.coordinates, claimDistance))
: [];
return (
@@ -140,15 +136,6 @@ const ExpandedMapScreen = ({route}) => {
strokeWidth={1}
/>
)}
- {/* Proximity circle when heading is unavailable (iOS fallback) */}
- {!isAdmin && userLocation && !hasHeading && claimDistance > 0 && (
-
- )}
diff --git a/src/hooks/usePlayerGame.js b/src/hooks/usePlayerGame.js
index 401020d..44fbb8c 100644
--- a/src/hooks/usePlayerGame.js
+++ b/src/hooks/usePlayerGame.js
@@ -1,5 +1,5 @@
import {useEffect, useRef, useState} from 'react';
-import {isInClaimCone, isWithinRadius} from '../utils/geoMath';
+import {isInClaimCone} from '../utils/geoMath';
const usePlayerGame = (playerLocation, playerHeading, activeCaches, claimDistance) => {
// State ----------------------
@@ -11,8 +11,9 @@ const usePlayerGame = (playerLocation, playerHeading, activeCaches, claimDistanc
// Handlers -------------------
useEffect(() => {
- // No location at all — clear everything
- if (!playerLocation) {
+ // Both location AND heading are required — the player must physically
+ // point their device towards a cache to trigger the claim countdown
+ if (!playerLocation || playerHeading === null || playerHeading === undefined) {
if (prevIdsRef.current !== '') {
prevIdsRef.current = '';
setVisibleCaches([]);
@@ -21,24 +22,17 @@ const usePlayerGame = (playerLocation, playerHeading, activeCaches, claimDistanc
return;
}
- const hasHeading = playerHeading !== null && playerHeading !== undefined;
-
- // When heading is available use the full FOV cone check.
- // When heading is NOT available (common on iOS / Apple Maps where the
- // compass may be delayed or unavailable) fall back to proximity-only so
- // iPhone users can still discover and claim caches within range.
- const nearby = (activeCaches || []).filter((cache) =>
- hasHeading
- ? isInClaimCone(playerHeading, playerLocation, cache.coordinates, claimDistance)
- : isWithinRadius(playerLocation, cache.coordinates, claimDistance),
+ // Find all caches that fall inside the claim cone
+ const inCone = (activeCaches || []).filter((cache) =>
+ isInClaimCone(playerHeading, playerLocation, cache.coordinates, claimDistance),
);
// Only update state when the set of visible caches actually changes
- const newIds = nearby.map((c) => c.id).join(',');
+ const newIds = inCone.map((c) => c.id).join(',');
if (newIds !== prevIdsRef.current) {
prevIdsRef.current = newIds;
- setVisibleCaches(nearby);
- setIsClaiming(nearby.length > 0);
+ setVisibleCaches(inCone);
+ setIsClaiming(inCone.length > 0);
}
}, [playerLocation, playerHeading, activeCaches, claimDistance]);