3333ROOT = Path (__file__ ).resolve ().parents [1 ]
3434OUT = ROOT / "docs" / "demo_real_raw.mp4"
3535OUT_W , OUT_H = 1920 , 1080
36- FPS = 10
36+ FPS = 20
3737
3838PET_SCALE = 1.25 # physical sprite is 480x520 on this 200% display
3939
@@ -51,26 +51,55 @@ def find_pet_center() -> tuple[int, int] | None:
5151 return ((rect .left + rect .right ) // 2 , (rect .top + rect .bottom ) // 2 )
5252
5353CODE_LINES = [
54- "# day 6 with my desktop pet" ,
55- "class Boba:" ,
56- " def __init__(self):" ,
57- " self.energy = 0.75" ,
58- " self.boredom = 0.25" ,
54+ "# reflexarc - a nervous system for a tiny desktop creature" ,
55+ "import time" ,
56+ "from dataclasses import dataclass" ,
5957 "" ,
60- " def live(self, senses):" ,
61- " while True:" ,
62- " drive = self.homeostasis(senses)" ,
63- " act = self.instinct(drive)" ,
64- " self.sprite.play(act)" ,
58+ "@dataclass" ,
59+ "class Drives:" ,
60+ " energy: float = 0.75" ,
61+ " boredom: float = 0.25" ,
62+ " mood: float = 0.65" ,
63+ " attachment: float = 0.40" ,
6564 "" ,
66- "def homeostatic_drive(machine_state) :" ,
67- " energy -= 0.03 * hours " ,
68- " boredom += 0.55 * idle_hours " ,
69- " mood = ema(mood, target, tau) " ,
70- " return dict(energy, boredom, mood )" ,
65+ "class Senses :" ,
66+ " def sample(self): " ,
67+ " idle = get_last_input_seconds() " ,
68+ " cpu = read_cpu_percent() / 100 " ,
69+ " return dict(idle=idle, cpu=cpu, hour=local_hour() )" ,
7170 "" ,
72- "# the desk pet watches the user work..." ,
73- "# typing fast makes it work too" ,
71+ "def homeostasis(drives, obs, dt):" ,
72+ " hours = dt / 3600" ,
73+ " drives.energy -= 0.03 * hours" ,
74+ " drives.boredom += 0.55 * hours if obs[\' idle\' ] > 120 else 0" ,
75+ " target = 0.5 + 0.2 * (drives.energy - 0.5)" ,
76+ " drives.mood += (target - drives.mood) * min(1, dt / 900)" ,
77+ " return drives" ,
78+ "" ,
79+ "def instinct(drives):" ,
80+ " scores = {}" ,
81+ " scores[\' rest\' ] = 2.4 * max(0, 0.62 - drives.energy) ** 1.5" ,
82+ " scores[\' walk\' ] = 1.7 * drives.boredom * drives.energy" ,
83+ " scores[\' watch\' ] = 1.3 * drives.attachment" ,
84+ " return max(scores, key=scores.get)" ,
85+ "" ,
86+ "def live(senses, sprite):" ,
87+ " drives = Drives()" ,
88+ " last = time.time()" ,
89+ " while True:" ,
90+ " now = time.time()" ,
91+ " obs = senses.sample()" ,
92+ " homeostasis(drives, obs, now - last)" ,
93+ " act = instinct(drives)" ,
94+ " sprite.play(act)" ,
95+ " last = now" ,
96+ " time.sleep(0.25)" ,
97+ "" ,
98+ "if __name__ == \' __main__\' :" ,
99+ " live(Senses(), sprite=PETDEX_SPRITE)" ,
100+ "" ,
101+ "# the strange part: it is not animated, it is alive" ,
102+ "# every state above is earned by what THIS machine is doing" ,
74103]
75104
76105
@@ -82,6 +111,71 @@ def burn(stop_flag) -> None: # runs in child processes
82111 x = 1.0001
83112
84113
114+ _FONT_CACHE : dict = {}
115+
116+
117+ def _font (size : int ):
118+ from PIL import ImageFont
119+ if size not in _FONT_CACHE :
120+ f = None
121+ for name in ("segoeui.ttf" , "arial.ttf" ):
122+ try :
123+ f = ImageFont .truetype (name , size )
124+ break
125+ except OSError :
126+ continue
127+ _FONT_CACHE [size ] = f or ImageFont .load_default ()
128+ return _FONT_CACHE [size ]
129+
130+
131+ def _draw_cursor_and_overlays (rgb : np .ndarray ) -> np .ndarray :
132+ """mss does not capture the mouse cursor, so we draw it ourselves.
133+ Also paints a live CPU meter so the 'stress' phase has visual proof."""
134+ from PIL import Image , ImageDraw
135+ import ctypes , ctypes .wintypes
136+ try :
137+ import psutil
138+ cpu = psutil .cpu_percent (interval = None ) / 100.0
139+ except Exception :
140+ cpu = 0.0
141+
142+ img = Image .fromarray (rgb )
143+ draw = ImageDraw .Draw (img , "RGBA" )
144+
145+ # --- CPU meter (top-right) ---
146+ x0 , y0 , w , h = img .width - 320 , 24 , 296 , 54
147+ draw .rounded_rectangle ([x0 , y0 , x0 + w , y0 + h ], radius = 10 ,
148+ fill = (12 , 12 , 22 , 180 ))
149+ pct = max (0.0 , min (1.0 , cpu ))
150+ bar_w = int ((w - 16 ) * pct )
151+ color = (90 , 200 , 120 ) if pct < 0.6 else ((240 , 190 , 60 ) if pct < 0.85
152+ else (235 , 80 , 80 ))
153+ draw .rounded_rectangle ([x0 + 8 , y0 + h - 20 , x0 + 8 + bar_w , y0 + h - 8 ],
154+ radius = 6 , fill = color )
155+ draw .text ((x0 + 10 , y0 + 6 ), f"CPU { int (pct * 100 )} %" ,
156+ font = _font (20 ), fill = (235 , 235 , 245 ))
157+
158+ # --- cursor ---
159+ user32 = ctypes .windll .user32
160+ pt = ctypes .wintypes .POINT ()
161+ if user32 .GetCursorPos (ctypes .byref (pt )):
162+ cx , cy = pt .x // 2 , pt .y // 2 # physical -> 1080p half scale
163+ down = bool (user32 .GetAsyncKeyState (0x01 ) & 0x8000 )
164+ if down :
165+ draw .ellipse ([cx - 26 , cy - 26 , cx + 26 , cy + 26 ],
166+ outline = (255 , 210 , 90 , 220 ), width = 5 )
167+ pts = [(cx , cy ), (cx + 4 , cy + 26 ), (cx + 11 , cy + 18 ),
168+ (cx + 20 , cy + 30 ), (cx + 27 , cy + 25 ), (cx + 18 , cy + 14 ),
169+ (cx + 28 , cy + 11 )]
170+ draw .polygon ([(px , py ) for px , py in pts ],
171+ fill = (255 , 255 , 255 , 235 ))
172+ draw .line ([(cx , cy ), (cx + 4 , cy + 26 ), (cx + 11 , cy + 18 ),
173+ (cx + 20 , cy + 30 ), (cx + 27 , cy + 25 ), (cx + 18 , cy + 14 ),
174+ (cx + 28 , cy + 11 ), (cx , cy )],
175+ fill = (20 , 20 , 20 , 255 ), width = 2 )
176+ return np .asarray (img )
177+
178+
85179def record_loop (stop_event : threading .Event ) -> None :
86180 with mss .mss () as sct :
87181 mon = sct .monitors [1 ]
@@ -92,16 +186,15 @@ def record_loop(stop_event: threading.Event) -> None:
92186 interval = 1.0 / FPS
93187 start = time .time ()
94188 written = 0
95- last_rgb = None
96189 while not stop_event .is_set ():
97190 frame = np .asarray (sct .grab (mon )) # BGRA
98191 frame = cv2 .resize (frame , (OUT_W , OUT_H ),
99192 interpolation = cv2 .INTER_AREA )
100- last_rgb = cv2 .cvtColor (frame , cv2 .COLOR_BGRA2RGB )
101- # keep video time == real time: pad if we fell behind
193+ rgb = cv2 .cvtColor (frame , cv2 .COLOR_BGRA2RGB )
194+ rgb = _draw_cursor_and_overlays ( rgb )
102195 due = int ((time .time () - start ) * FPS ) + 1
103196 while written < due :
104- writer .append_data (last_rgb )
197+ writer .append_data (rgb )
105198 written += 1
106199 sleep = (start + written * interval ) - time .time ()
107200 if sleep > 0 :
@@ -130,17 +223,19 @@ def cb(hwnd, lparam):
130223 user32 .EnumWindows (cb , 0 )
131224
132225
226+ _TYPE_POS = {"i" : 0 }
227+
228+
133229def type_code (seconds : float ) -> None :
134- """Type real keystrokes (only ASCII) with human-ish rhythm."""
230+ """Type real keystrokes (only ASCII) with human-ish rhythm. The cursor
231+ continues where the previous call stopped, so no line repeats in a row."""
135232 end = time .time () + seconds
136- lines = list (CODE_LINES )
137233 while time .time () < end :
138- for line in lines :
139- if time .time () >= end :
140- return
141- pyautogui .typewrite (line , interval = 0.035 )
142- pyautogui .press ("enter" )
143- time .sleep (0.35 )
234+ line = CODE_LINES [_TYPE_POS ["i" ] % len (CODE_LINES )]
235+ _TYPE_POS ["i" ] += 1
236+ pyautogui .typewrite (line , interval = 0.035 )
237+ pyautogui .press ("enter" )
238+ time .sleep (0.35 )
144239
145240
146241def main () -> None :
0 commit comments