feat(desktop): roaming pet patrols the base of an open overlay

When a full-screen route overlay (settings/profiles/cron/agents/command-center) is up, the pet's walkable surface swaps to a single ledge at the overlay card's bottom edge — derived from OverlayView's shared inset, not measured — so it patrols there; closing the overlay restores the normal surfaces and it drops back down.
This commit is contained in:
Brooklyn Nicholson 2026-06-29 14:57:26 -05:00
parent 0e2a5a3206
commit a1e699ae55
2 changed files with 36 additions and 8 deletions

View file

@ -2,6 +2,7 @@ import { useStore } from '@nanostores/react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useGatewayRequest } from '@/app/gateway/hooks/use-gateway-request'
import { useRouteOverlayActive } from '@/app/hooks/use-route-overlay-active'
import { persistString, storedString } from '@/lib/storage'
import { $petAtRest, $petInfo, $petRoam, $petRoamDir, clearPetUnread, type PetInfo, petProfile, setPetInfo } from '@/store/pet'
import { resetPetGallery, setPetScale } from '@/store/pet-gallery'
@ -108,6 +109,7 @@ export function FloatingPet() {
const roamEnabled = useStore($petRoam)
const atRest = useStore($petAtRest)
const roamDir = useStore($petRoamDir)
const routeOverlayOpen = useRouteOverlayActive()
const [position, setPosition] = useState<Point>(loadPosition)
const containerRef = useRef<HTMLDivElement | null>(null)
@ -391,6 +393,7 @@ export function FloatingPet() {
enabled: roamEnabled && active && !overlayActive && atRest,
isInteracting: isDragging,
loopMs: info.loopMs ?? 1100,
overlayOpen: routeOverlayOpen,
petH,
petW
})

View file

@ -1,5 +1,6 @@
import { type RefObject, useEffect } from 'react'
import { TITLEBAR_HEIGHT } from '@/app/shell/titlebar'
import { $petMotion, $petRoamDir, type PetState } from '@/store/pet'
interface Point {
@ -126,6 +127,8 @@ interface PetRoamOptions {
petH: number
/** Sprite animation loop duration (ms) — paces the walk to the leg cadence. */
loopMs: number
/** A full-screen route overlay (settings/profiles/…) is up: patrol its base. */
overlayOpen: boolean
/** Persist the resting position back to React state when the loop settles. */
commit: (point: Point) => void
}
@ -151,7 +154,16 @@ 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, loopMs, commit }: PetRoamOptions): void {
export function usePetRoam({
enabled,
containerRef,
isInteracting,
petW,
petH,
loopMs,
overlayOpen,
commit
}: PetRoamOptions): void {
useEffect(() => {
if (!enabled) {
$petMotion.set(null)
@ -281,13 +293,26 @@ export function usePetRoam({ enabled, containerRef, isInteracting, petW, petH, l
return best ?? ledges[0]!
}
const planNext = () => {
const ledges = snapshotLedges(petW, petH)
curLedge = resolveLedge(ledges)
const grounded = Math.abs(cur.y - groundTop(curLedge)) <= GROUND_EPS
// While an overlay is up, it's the only walkable surface: a single ledge at
// the overlay card's bottom inner edge. The card uses `OverlayView`'s equal
// inset on every side — `titlebar-height + padding` — so derive it from that
// (never measured).
const overlayLedge = (): Ledge => {
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16
const inset = TITLEBAR_HEIGHT + (vw() >= 640 ? 0.875 : 0.625) * rem
if (!grounded) {
// Dragged into the air (or a perch vanished): fall to the surface below.
return { left: inset, right: Math.max(0, vw() - inset - petW), y: vh() - inset }
}
const planNext = () => {
// An open overlay swaps the surface set to just its bottom edge, so the pet
// patrols along it; closing it restores the normal surfaces (and the pet
// drops to whatever's below).
const ledges = overlayOpen ? [overlayLedge()] : snapshotLedges(petW, petH)
curLedge = resolveLedge(ledges)
if (Math.abs(cur.y - groundTop(curLedge)) > GROUND_EPS) {
// Dragged into the air, or the surface moved out from under it: fall.
beginVertical(curLedge)
return
@ -414,5 +439,5 @@ export function usePetRoam({ enabled, containerRef, isInteracting, petW, petH, l
// the loop stops re-asserting it.
commit({ ...cur })
}
}, [enabled, petW, petH, loopMs, containerRef, isInteracting, commit])
}, [enabled, petW, petH, loopMs, overlayOpen, containerRef, isInteracting, commit])
}