feat(config): add configuration parameters, kinematics, and distraction targets
This commit is contained in:
@@ -0,0 +1,145 @@
|
|||||||
|
"""
|
||||||
|
Catser Configuration and Kinematic Constants.
|
||||||
|
|
||||||
|
Stores system configurations, ActivityWatch parameters, distraction rules,
|
||||||
|
visual coat definitions, and exact physics constants ported from Workcat (cat.js).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List, Dict, Any, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# Workcat Kinematic and Animation Constants
|
||||||
|
# Derived from: apps/desktop/src/features/pet-mode/petModeData.ts & cat.js
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
GAIT_FRAME_COUNT: int = 30
|
||||||
|
WALK_SPEED: float = 20.8
|
||||||
|
WALK_STRIDE: float = 26.0
|
||||||
|
RUN_SPEED: float = 300.0
|
||||||
|
RUN_STRIDE: float = 72.0
|
||||||
|
GAIT_CADENCE: float = 0.9
|
||||||
|
GAIT_SHIFT_X: float = -6.383
|
||||||
|
GAIT_SHIFT_Y: float = 3.76
|
||||||
|
CAT_BOX_WIDTH: float = 121.2
|
||||||
|
CAT_BOX_HEIGHT: float = 92.4
|
||||||
|
BASE_WIDTH: float = 144.0
|
||||||
|
|
||||||
|
PAW_FPS: int = 24
|
||||||
|
PAW_FRAME_COUNT: int = 17
|
||||||
|
PAW_REACH_FRAME: int = 13 # End of reaching motion
|
||||||
|
PAW_CONTACT_FRAME: int = 14 # Exact contact frame where the paw strikes the target
|
||||||
|
PAW_HOLD_FRAME: int = 15 # Frame holding the extended strike pose
|
||||||
|
|
||||||
|
# Contact offset ratio relative to cat bounding box
|
||||||
|
# target_x = cat_x + width * CONTACT_RATIO_X (for right-facing cat)
|
||||||
|
# target_y = cat_y + height * CONTACT_RATIO_Y
|
||||||
|
CONTACT_RATIO_X: float = (143.0 - 11.4) / 121.2 # ~1.0858 (paw reaches past box edge)
|
||||||
|
CONTACT_RATIO_Y: float = (40.0 - 4.2) / 92.4 # ~0.3874
|
||||||
|
|
||||||
|
ALERT_HOLD_MS: int = 620 # Pause duration when target is spotted before sprint
|
||||||
|
RELEASE_SETTLE_MS: int = 320 # Settle duration after dropping before walking
|
||||||
|
DRAG_THRESHOLD_PX: int = 7 # Minimum drag distance before transitioning from pet to drag
|
||||||
|
HAPPY_MS: int = 1200 # Petting reaction duration
|
||||||
|
BLINK_DURATION_MS: int = 228
|
||||||
|
BLINK_INTERVAL_MIN_MS: int = 4420
|
||||||
|
BLINK_INTERVAL_MAX_MS: int = 7280
|
||||||
|
|
||||||
|
# Approach duration limits (ensures sprint feels dynamic regardless of distance)
|
||||||
|
APPROACH_MIN_MS: int = 620
|
||||||
|
APPROACH_MAX_MS: int = 1150
|
||||||
|
|
||||||
|
LIFE_MOTION_FPS: int = 12
|
||||||
|
LIFE_MOTION_FRAME_COUNT: int = 37
|
||||||
|
|
||||||
|
# Probabilities for life actions when standing vs sitting
|
||||||
|
ACTION_WEIGHTS: Dict[str, Dict[str, int]] = {
|
||||||
|
"standing": {"walk": 3, "stretch": 2, "sit": 6, "sleep": 1},
|
||||||
|
"sitting": {"walk": 2, "stretch": 1, "sit": 3, "sleep": 6},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Settled action intervals (ms)
|
||||||
|
LIFE_ACTION_INTERVAL_MS: int = 9000
|
||||||
|
SETTLED_ACTION_INTERVAL_MS: int = 3400
|
||||||
|
SIT_MIN_MS: int = 3200
|
||||||
|
SIT_MAX_MS: int = 9000
|
||||||
|
SLEEP_MIN_MS: int = 6400
|
||||||
|
SLEEP_MAX_MS: int = 18000
|
||||||
|
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# Cat Coat Definitions
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Coat:
|
||||||
|
"""Represents a cat coat color palette."""
|
||||||
|
name: str
|
||||||
|
label_en: str
|
||||||
|
fur_color: Tuple[int, int, int] # RGB
|
||||||
|
ink_color: Tuple[int, int, int] # RGB for face features (eyes, mouth)
|
||||||
|
filter_type: str # CSS-equivalent color filter for raster assets
|
||||||
|
|
||||||
|
|
||||||
|
COATS: Dict[str, Coat] = {
|
||||||
|
"ivory": Coat("ivory", "Ivory", (232, 229, 218), (23, 25, 22), "none"),
|
||||||
|
"charcoal": Coat("charcoal", "Charcoal", (51, 51, 47), (242, 241, 237), "charcoal"),
|
||||||
|
"grey": Coat("grey", "Grey", (163, 161, 153), (26, 27, 24), "grey"),
|
||||||
|
"apricot": Coat("apricot", "Apricot", (217, 177, 140), (60, 43, 28), "apricot"),
|
||||||
|
"sage": Coat("sage", "Sage", (147, 168, 142), (28, 36, 26), "sage"),
|
||||||
|
"plum": Coat("plum", "Plum", (122, 95, 107), (239, 230, 234), "plum"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# Application Configuration
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Config:
|
||||||
|
"""
|
||||||
|
Runtime configuration for Catser.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
aw_url: ActivityWatch server base URL (default http://localhost:5600).
|
||||||
|
poll_interval_sec: Interval in seconds to poll ActivityWatch.
|
||||||
|
auto_fallback_to_win32: Whether to fall back to native Win32 window APIs if AW is offline.
|
||||||
|
distraction_keywords: List of substrings in window titles that trigger cat action.
|
||||||
|
distraction_apps: List of process names (lowercase) that trigger cat action.
|
||||||
|
action_on_hit: Action to take on contact ('close', 'minimize', or 'test_notify').
|
||||||
|
cat_width: Screen pixel width for the cat sprite.
|
||||||
|
coat_name: Active coat color name ('ivory', 'charcoal', etc.).
|
||||||
|
sound_enabled: Whether to play sound effects on strike / purr.
|
||||||
|
cooldown_after_close_sec: Minimum seconds between closing actions on the same window.
|
||||||
|
"""
|
||||||
|
aw_url: str = "http://localhost:5600"
|
||||||
|
poll_interval_sec: float = 0.5
|
||||||
|
auto_fallback_to_win32: bool = True
|
||||||
|
distraction_keywords: List[str] = field(default_factory=lambda: [
|
||||||
|
"shorts",
|
||||||
|
"youtube shorts",
|
||||||
|
"reels",
|
||||||
|
"instagram reels",
|
||||||
|
"tiktok",
|
||||||
|
"reddit",
|
||||||
|
"twitch.tv",
|
||||||
|
])
|
||||||
|
distraction_apps: List[str] = field(default_factory=lambda: [
|
||||||
|
# Users can add specific processes e.g. "discord.exe", "steam.exe"
|
||||||
|
])
|
||||||
|
action_on_hit: str = "close" # 'close', 'minimize', or 'test_notify'
|
||||||
|
cat_width: int = 144 # Matches BASE_WIDTH for 1:1 scale
|
||||||
|
coat_name: str = "ivory"
|
||||||
|
sound_enabled: bool = True
|
||||||
|
cooldown_after_close_sec: float = 4.0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cat_height(self) -> int:
|
||||||
|
"""Calculates cat height proportionally based on aspect ratio 121.2 / 92.4."""
|
||||||
|
return int(round(self.cat_width * (CAT_BOX_HEIGHT / CAT_BOX_WIDTH)))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def coat(self) -> Coat:
|
||||||
|
"""Retrieves the active Coat instance."""
|
||||||
|
return COATS.get(self.coat_name, COATS["ivory"])
|
||||||
Reference in New Issue
Block a user