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