Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@
const clickBurstsRefLocal = useRef(clickBursts);
const collisionEffectsRef = useRef(collisionEffects);
const hoveredSunIdRef = useRef(hoveredSunId);
const focusedSunIdRef = useRef(focusedSunId);

// Keep refs in sync with state (but don't trigger useMemo recalculation)
useEffect(() => {
Expand All @@ -551,6 +552,9 @@
useEffect(() => {
hoveredSunIdRef.current = hoveredSunId;
}, [hoveredSunId]);
useEffect(() => {
focusedSunIdRef.current = focusedSunId;
}, [focusedSunId]);

// Memoize animation loop parameters to prevent unnecessary re-renders
// PERFORMANCE: Only include STATIC dependencies that rarely change
Expand Down Expand Up @@ -606,6 +610,7 @@
fpsValuesRef,
hoveredSunIdRef, // Use ref for synchronous access in animation loop
focusedSunId,
focusedSunIdRef, // Live ref — loop reads this so focus-mode clears on zoom-out
camera: internalCamera,
setCamera: setInternalCamera,
isMouseOverProjectTooltipRef: tooltipRefs.isMouseOverProjectTooltipRef,
Expand Down Expand Up @@ -686,7 +691,7 @@
cameraAnimationRef.current = null;
}
};
}, []);

Check warning on line 694 in apps/web/src/features/layout/components/Starfield/Starfield.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format Check

React Hook useEffect has a missing dependency: 'cameraAnimationRef'. Either include it or remove the dependency array

// NOTE: Starfield initialization delay is now handled by usePerformanceTier hook
// NOTE: _handleEmployeeOrbitSpeedChange removed - unused debug handler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,21 @@ export const animate = (
// Use cameraRef for synchronous access (avoids stale state issues)
// Fall back to props.camera for backward compatibility
const cameraValues = props.cameraRef?.current ?? props.camera;

// Read the focused sun from the live ref, NOT the memoised prop. The params
// memo that feeds this loop lags focus changes, so `props.focusedSunId` stays
// stale after a zoom-out — even though React has already cleared focus and the
// camera has recentred. That left the render stuck in "focused mode" (dimmed
// background, vignette, only the focused sun's planets), so the field looked
// collapsed to the focused sun's side. The ref is synced synchronously on every
// focus change (mirroring hoveredSunIdRef), so it reflects the live value.
// Use the ref directly when present — its `null` means "no focus" and must NOT
// fall through to the stale prop; only fall back to the prop if the ref is
// absent (defensive).
const liveFocusedSunId = props.focusedSunIdRef
? props.focusedSunIdRef.current
: (props.focusedSunId ?? null);

if (cameraValues) {
ctx.save();
// Calculate the center point to zoom towards (in canvas coordinates)
Expand Down Expand Up @@ -231,7 +246,7 @@ export const animate = (
// Draw background stars with reduced opacity when focused on a sun
// This makes the focused area more prominent
startTiming("drawStars");
if (props.focusedSunId) {
if (liveFocusedSunId) {
ctx.save();
ctx.globalAlpha = STAR_RENDERING_CONFIG.focusedBackgroundAlpha;
drawStars(ctx, currentStars);
Expand All @@ -244,7 +259,7 @@ export const animate = (

// Draw star birthplace indicators at the edges where stars respawn
// Hide these when focused on a sun for cleaner view
if (!props.focusedSunId) {
if (!liveFocusedSunId) {
drawStarBirthplaces(ctx, canvas.width, canvas.height);
}

Expand Down Expand Up @@ -316,7 +331,7 @@ export const animate = (
props.isDarkMode,
liveHoveredSunId,
deltaTime,
props.focusedSunId,
liveFocusedSunId,
currentPlanets,
);
endTiming("drawSuns");
Expand Down Expand Up @@ -490,7 +505,7 @@ export const animate = (
// Adding it here caused double-wrapping and potential oscillation at edges

// Draw black holes if enabled (hide when focused on a sun for cleaner view)
if (props.enableBlackHole && !props.focusedSunId) {
if (props.enableBlackHole && !liveFocusedSunId) {
currentBlackHoles.forEach((blackHole: BlackHole) => {
drawBlackHole(ctx, blackHole, deltaTime, props.particleSpeed * 0.01);
});
Expand All @@ -500,16 +515,16 @@ export const animate = (
// Filter planets if a sun is focused (show only planets orbiting that sun)
// Use cached filtered array to avoid allocation every frame
let planetsToRender: Planet[];
if (props.focusedSunId) {
if (liveFocusedSunId) {
// Only recalculate if focusedSunId changed or planets array changed
if (
cachedFocusedSunId !== props.focusedSunId ||
cachedFocusedSunId !== liveFocusedSunId ||
cachedPlanetsLength !== currentPlanets.length
) {
cachedFilteredPlanets = currentPlanets.filter(
(planet) => planet.orbitParentId === props.focusedSunId,
(planet) => planet.orbitParentId === liveFocusedSunId,
);
cachedFocusedSunId = props.focusedSunId;
cachedFocusedSunId = liveFocusedSunId;
cachedPlanetsLength = currentPlanets.length;
}
planetsToRender = cachedFilteredPlanets;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export interface AnimationProps {
hoveredObjectId?: string | null;
hoveredSunIdRef?: MutableRefObject<string | null>; // Ref for synchronous hoveredSunId access
focusedSunId?: string | null;
focusedSunIdRef?: MutableRefObject<string | null>; // Ref for synchronous focusedSunId access (loop reads this, not the memoised prop)
debugSettings?: DebugSettings; // Add debug settings
isMouseOverProjectTooltipRef?: MutableRefObject<boolean>; // Track if mouse is over project tooltip
isMouseOverSunTooltipRef?: MutableRefObject<boolean>; // Track if mouse is over sun tooltip
Expand Down
Loading