diff --git a/src/blobbi/companion/components/BlobbiCompanionVisual.tsx b/src/blobbi/companion/components/BlobbiCompanionVisual.tsx index 1c02d652..28fb9bc3 100644 --- a/src/blobbi/companion/components/BlobbiCompanionVisual.tsx +++ b/src/blobbi/companion/components/BlobbiCompanionVisual.tsx @@ -123,7 +123,16 @@ const MemoizedBlobbiVisual = memo(function MemoizedBlobbiVisual({ }, (prev, next) => { return ( prev.stage === next.stage && - prev.blobbi === next.blobbi && + // Compare blobbi by visual-identity primitives, NOT by reference. + // This prevents SVG rebuilds (and SMIL animation restarts) when the + // upstream companion object gets a new reference with identical content + // — e.g. during nausea recovery where only angerRise.level changes. + prev.blobbi.id === next.blobbi.id && + prev.blobbi.baseColor === next.blobbi.baseColor && + prev.blobbi.secondaryColor === next.blobbi.secondaryColor && + prev.blobbi.eyeColor === next.blobbi.eyeColor && + prev.blobbi.adult?.evolutionForm === next.blobbi.adult?.evolutionForm && + prev.blobbi.seed === next.blobbi.seed && prev.recipeFingerprint === next.recipeFingerprint && prev.recipeLabel === next.recipeLabel && prev.emotion === next.emotion && diff --git a/src/blobbi/ui/BlobbiAdultSvgRenderer.tsx b/src/blobbi/ui/BlobbiAdultSvgRenderer.tsx index 9b5466ca..12e22a2f 100644 --- a/src/blobbi/ui/BlobbiAdultSvgRenderer.tsx +++ b/src/blobbi/ui/BlobbiAdultSvgRenderer.tsx @@ -94,11 +94,12 @@ export function BlobbiAdultSvgRenderer({ } return animatedSvg; - // recipeFingerprint replaces recipeProp in the dep list so that - // level-only changes do NOT trigger a full SVG rebuild. The closure - // captures the current recipeProp for the rare structural rebuilds. + // Deps use stable primitives from blobbi (not the object reference) and + // recipeFingerprint (not recipeProp) so that level-only changes and + // upstream reference churn do NOT trigger full SVG rebuilds. The closure + // captures the current blobbi/recipeProp for the rare structural rebuilds. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [blobbi, recipeFingerprint, recipeLabel, emotion, bodyEffects]); + }, [blobbi.id, blobbi.baseColor, blobbi.secondaryColor, blobbi.eyeColor, blobbi.adult?.evolutionForm, blobbi.seed, recipeFingerprint, recipeLabel, emotion, bodyEffects]); const safeSvg = useMemo(() => sanitizeBlobbiSvg(customizedSvg), [customizedSvg]); diff --git a/src/blobbi/ui/BlobbiBabySvgRenderer.tsx b/src/blobbi/ui/BlobbiBabySvgRenderer.tsx index e8438d21..ba5f96c1 100644 --- a/src/blobbi/ui/BlobbiBabySvgRenderer.tsx +++ b/src/blobbi/ui/BlobbiBabySvgRenderer.tsx @@ -90,8 +90,12 @@ export function BlobbiBabySvgRenderer({ } return animatedSvg; + // Deps use stable primitives from blobbi (not the object reference) and + // recipeFingerprint (not recipeProp) so that level-only changes and + // upstream reference churn do NOT trigger full SVG rebuilds. The closure + // captures the current blobbi/recipeProp for the rare structural rebuilds. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [blobbi, recipeFingerprint, recipeLabel, emotion, bodyEffects]); + }, [blobbi.id, blobbi.baseColor, blobbi.secondaryColor, blobbi.eyeColor, blobbi.seed, recipeFingerprint, recipeLabel, emotion, bodyEffects]); const safeSvg = useMemo(() => sanitizeBlobbiSvg(customizedSvg), [customizedSvg]);