Fix dizzy-eye SMIL restart during nausea recovery drain

Replace blobbi reference equality with stable visual-identity primitive
comparisons in MemoizedBlobbiVisual memo and SVG renderer useMemo deps.
This prevents SVG DOM rebuilds (which restart SMIL animateTransform)
when the upstream companion object gets a new reference during the
imperative gradient-drain loop.
This commit is contained in:
filemon
2026-05-03 02:10:28 -03:00
parent 65b6c2afb6
commit 62cc2611ea
3 changed files with 20 additions and 6 deletions
@@ -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 &&
+5 -4
View File
@@ -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]);
+5 -1
View File
@@ -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]);