From 0e2a5a3206d3543294abd132a8dcf914a5257759 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 29 Jun 2026 14:47:37 -0500 Subject: [PATCH] =?UTF-8?q?feat(desktop):=20ground=20the=20roaming=20pet?= =?UTF-8?q?=20=E2=80=94=20sprite-paced=20walk=20+=20feet=20on=20surface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Walk speed is derived from the sprite's animation loop + on-screen size (one body-width per loop) instead of a fixed px/s, so it steps rather than glides; the pet also sinks a few px so its feet meet the surface instead of hovering. --- .../src/components/pet/floating-pet.tsx | 1 + .../src/components/pet/use-pet-roam.ts | 21 ++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/components/pet/floating-pet.tsx b/apps/desktop/src/components/pet/floating-pet.tsx index 18c8952b846..87c9026ed4e 100644 --- a/apps/desktop/src/components/pet/floating-pet.tsx +++ b/apps/desktop/src/components/pet/floating-pet.tsx @@ -390,6 +390,7 @@ export function FloatingPet() { containerRef, enabled: roamEnabled && active && !overlayActive && atRest, isInteracting: isDragging, + loopMs: info.loopMs ?? 1100, petH, petW }) diff --git a/apps/desktop/src/components/pet/use-pet-roam.ts b/apps/desktop/src/components/pet/use-pet-roam.ts index c79cc32ee16..aff24b5b7bf 100644 --- a/apps/desktop/src/components/pet/use-pet-roam.ts +++ b/apps/desktop/src/components/pet/use-pet-roam.ts @@ -28,7 +28,10 @@ const PERCH_SELECTORS = ['[data-slot="composer-surface"]', '[data-slot="profile- // the bar rather than covering it. const FLOOR_BAR_SELECTOR = '[data-slot="statusbar"]' -const WALK_SPEED_PX_S = 58 +// Foot-sync: advance this many body-widths per animation loop so the walk reads +// as steps, not a glide. Actual px/s is derived from the sprite's loop duration +// and on-screen size (see `walkSpeedPxS`). +const STRIDE_PER_LOOP = 0.8 // Downward acceleration for falls between ledges — fast enough to read as a drop. const GRAVITY_PX_S2 = 5200 // Time to spring up onto a higher ledge. @@ -44,6 +47,9 @@ const HOP_CHANCE = 0.45 // ledge (or this many px, whichever is larger), up to the room available. const STROLL_MIN_FRACTION = 0.45 const STROLL_MIN_PX = 110 +// Sprites carry a few px of transparent padding below the feet; sink the pet by +// this much so the visible feet meet the surface instead of hovering above it. +const FEET_DROP_PX = 4 // Snap distances: "on this ledge" / arrived at a walk target. const GROUND_EPS = 2 const ARRIVE_EPS = 1.5 @@ -118,6 +124,8 @@ interface PetRoamOptions { isInteracting: () => boolean petW: number petH: number + /** Sprite animation loop duration (ms) — paces the walk to the leg cadence. */ + loopMs: number /** Persist the resting position back to React state when the loop settles. */ commit: (point: Point) => void } @@ -143,7 +151,7 @@ interface PetRoamOptions { * the shared `$petState`, and `$petRoamDir` (-1/0/1) lets the floating pet pick * the directional run row + mirror for the travel direction. */ -export function usePetRoam({ enabled, containerRef, isInteracting, petW, petH, commit }: PetRoamOptions): void { +export function usePetRoam({ enabled, containerRef, isInteracting, petW, petH, loopMs, commit }: PetRoamOptions): void { useEffect(() => { if (!enabled) { $petMotion.set(null) @@ -158,7 +166,10 @@ export function usePetRoam({ enabled, containerRef, isInteracting, petW, petH, c return } - const groundTop = (ledge: Ledge): number => ledge.y - petH + // Pace the stride to the sprite: one body-width per animation loop. + const walkSpeedPxS = (petW * STRIDE_PER_LOOP) / (loopMs / 1000) + + const groundTop = (ledge: Ledge): number => ledge.y - petH + FEET_DROP_PX // A stroll destination on `ledge` that actually goes somewhere: lean toward // the side with more room (so the pet crosses the app rather than shuffling @@ -330,7 +341,7 @@ export function usePetRoam({ enabled, containerRef, isInteracting, petW, petH, c case 'walk': { const remaining = walkTargetX - cur.x - const stepDist = WALK_SPEED_PX_S * dt + const stepDist = walkSpeedPxS * dt if (Math.abs(remaining) <= Math.max(ARRIVE_EPS, stepDist)) { cur.x = walkTargetX @@ -403,5 +414,5 @@ export function usePetRoam({ enabled, containerRef, isInteracting, petW, petH, c // the loop stops re-asserting it. commit({ ...cur }) } - }, [enabled, petW, petH, containerRef, isInteracting, commit]) + }, [enabled, petW, petH, loopMs, containerRef, isInteracting, commit]) }