alive-ai 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/Dockerfile +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +143 -0
  4. package/alive_ai/__init__.py +3 -0
  5. package/brain/__init__.py +59 -0
  6. package/brain/almost_said.py +154 -0
  7. package/brain/bid_detector.py +636 -0
  8. package/brain/conversation_flow.py +135 -0
  9. package/brain/curiosity.py +328 -0
  10. package/brain/default_mode.py +1438 -0
  11. package/brain/dreams.py +220 -0
  12. package/brain/embeddings/__init__.py +82 -0
  13. package/brain/emotional_memory.py +949 -0
  14. package/brain/global_activity.py +173 -0
  15. package/brain/group_dynamics.py +63 -0
  16. package/brain/linguistic.py +235 -0
  17. package/brain/llm/__init__.py +63 -0
  18. package/brain/llm/base.py +33 -0
  19. package/brain/llm/fallback_router.py +309 -0
  20. package/brain/llm/manifest.md +30 -0
  21. package/brain/llm/ollama.py +218 -0
  22. package/brain/llm/openrouter.py +151 -0
  23. package/brain/llm/provider.py +205 -0
  24. package/brain/llm/unified.py +423 -0
  25. package/brain/llm/zai.py +169 -0
  26. package/brain/manifest.md +23 -0
  27. package/brain/memory/__init__.py +123 -0
  28. package/brain/memory/episodic.py +92 -0
  29. package/brain/memory/fact_extractor.py +209 -0
  30. package/brain/memory/index.py +54 -0
  31. package/brain/memory/manager.py +151 -0
  32. package/brain/memory/summarizer.py +102 -0
  33. package/brain/memory/vector_store.py +297 -0
  34. package/brain/memory/working.py +43 -0
  35. package/brain/narrative.py +343 -0
  36. package/brain/stt/__init__.py +4 -0
  37. package/brain/stt/google_stt.py +83 -0
  38. package/brain/stt/whisper_stt.py +82 -0
  39. package/brain/subconscious/__init__.py +33 -0
  40. package/brain/subconscious/actions.py +136 -0
  41. package/brain/subconscious/evaluation.py +166 -0
  42. package/brain/subconscious/goal_system.py +90 -0
  43. package/brain/subconscious/goals.py +41 -0
  44. package/brain/subconscious/impulse_generator.py +200 -0
  45. package/brain/subconscious/impulses.py +48 -0
  46. package/brain/subconscious/learning.py +24 -0
  47. package/brain/subconscious/learning_system.py +79 -0
  48. package/brain/subconscious/loop.py +398 -0
  49. package/brain/subconscious/manifest.md +32 -0
  50. package/brain/subconscious/relationship.py +47 -0
  51. package/brain/subconscious/relationship_memory.py +83 -0
  52. package/brain/subconscious/response_analyzer.py +74 -0
  53. package/brain/subconscious/templates.py +70 -0
  54. package/brain/subconscious/thought.py +37 -0
  55. package/brain/subconscious/working_memory.py +97 -0
  56. package/cli/index.js +371 -0
  57. package/config/directives.example.json +28 -0
  58. package/config/instructions.example.md +16 -0
  59. package/config/self.example.json +74 -0
  60. package/config/settings.example.json +95 -0
  61. package/core/__init__.py +1 -0
  62. package/core/config.py +54 -0
  63. package/core/directives.py +198 -0
  64. package/core/events.py +50 -0
  65. package/core/follow_up.py +267 -0
  66. package/core/hot_reload.py +174 -0
  67. package/core/initialization.py +253 -0
  68. package/core/manifest.md +28 -0
  69. package/core/media_handler.py +241 -0
  70. package/core/memory_monitor.py +200 -0
  71. package/core/message_handler.py +1440 -0
  72. package/core/proactive_generator.py +277 -0
  73. package/core/self.py +188 -0
  74. package/core/settings.py +169 -0
  75. package/core/skills_registry.py +357 -0
  76. package/core/state.py +27 -0
  77. package/core/subconscious_bridge.py +93 -0
  78. package/core/thinking.py +175 -0
  79. package/core/user_manager.py +306 -0
  80. package/core/user_tracker.py +144 -0
  81. package/demo/index.html +144 -0
  82. package/docker-compose.yml +28 -0
  83. package/docs/assets/logo.svg +15 -0
  84. package/docs/index.html +355 -0
  85. package/heart/__init__.py +93 -0
  86. package/heart/afterglow.py +215 -0
  87. package/heart/attachment.py +186 -0
  88. package/heart/circadian.py +251 -0
  89. package/heart/complex_emotions.py +114 -0
  90. package/heart/conflicts.py +589 -0
  91. package/heart/core.py +387 -0
  92. package/heart/emotional_decay.py +59 -0
  93. package/heart/emotional_memory.py +261 -0
  94. package/heart/emotional_state.py +146 -0
  95. package/heart/emotional_variability.py +156 -0
  96. package/heart/hormonal.py +424 -0
  97. package/heart/inconsistency.py +1222 -0
  98. package/heart/integrity.py +469 -0
  99. package/heart/interoception.py +997 -0
  100. package/heart/love.py +120 -0
  101. package/heart/manifest.md +25 -0
  102. package/heart/mood_shifts.py +169 -0
  103. package/heart/phantom_somatic.py +259 -0
  104. package/heart/predictive.py +374 -0
  105. package/heart/scars.py +474 -0
  106. package/heart/somatic.py +482 -0
  107. package/heart/soul.py +633 -0
  108. package/heart/telemetry.py +942 -0
  109. package/heart/triggers.py +119 -0
  110. package/heart/unconscious.py +443 -0
  111. package/input/__init__.py +1 -0
  112. package/input/manifest.md +24 -0
  113. package/input/telegram/__init__.py +1 -0
  114. package/input/telegram/commands.py +762 -0
  115. package/input/telegram/listener.py +532 -0
  116. package/main.py +90 -0
  117. package/manifest.md +28 -0
  118. package/mypics/.gitkeep +1 -0
  119. package/myvids/.gitkeep +1 -0
  120. package/output/__init__.py +1 -0
  121. package/output/images/__init__.py +1 -0
  122. package/output/images/fal_gen.py +43 -0
  123. package/output/manifest.md +26 -0
  124. package/output/text/__init__.py +1 -0
  125. package/output/text/sender.py +22 -0
  126. package/output/voice/__init__.py +64 -0
  127. package/output/voice/google_tts.py +252 -0
  128. package/output/voice/gtts_tts.py +214 -0
  129. package/output/voice/vibe_tts.py +190 -0
  130. package/package.json +58 -0
  131. package/pyproject.toml +23 -0
  132. package/requirements.txt +21 -0
  133. package/skills/__init__.py +1 -0
  134. package/skills/anticipation_engine/__init__.py +8 -0
  135. package/skills/anticipation_engine/engine.py +618 -0
  136. package/skills/anticipation_engine/manifest.md +192 -0
  137. package/skills/calendar/__init__.py +1 -0
  138. package/skills/content_unlocks/__init__.py +8 -0
  139. package/skills/content_unlocks/manifest.md +231 -0
  140. package/skills/content_unlocks/unlocks.py +945 -0
  141. package/skills/exclusive_moments/__init__.py +8 -0
  142. package/skills/exclusive_moments/manifest.md +145 -0
  143. package/skills/exclusive_moments/moments.py +506 -0
  144. package/skills/intimacy_layers/__init__.py +8 -0
  145. package/skills/intimacy_layers/layers.py +703 -0
  146. package/skills/intimacy_layers/manifest.md +203 -0
  147. package/skills/manifest.md +67 -0
  148. package/skills/memory_callbacks/__init__.py +9 -0
  149. package/skills/memory_callbacks/callbacks.py +748 -0
  150. package/skills/memory_callbacks/manifest.md +170 -0
  151. package/skills/message_scheduler/__init__.py +19 -0
  152. package/skills/message_scheduler/manifest.md +107 -0
  153. package/skills/message_scheduler/scheduler.py +510 -0
  154. package/skills/photo_manager/__init__.py +1 -0
  155. package/skills/photo_manager/scanner.py +296 -0
  156. package/skills/relationship_milestones/__init__.py +8 -0
  157. package/skills/relationship_milestones/manifest.md +206 -0
  158. package/skills/relationship_milestones/tracker.py +494 -0
  159. package/skills/self_authorship/__init__.py +23 -0
  160. package/skills/self_authorship/author.py +331 -0
  161. package/skills/self_authorship/manifest.md +24 -0
  162. package/skills/video_manager/__init__.py +5 -0
  163. package/skills/video_manager/manifest.md +37 -0
  164. package/skills/video_manager/scanner.py +229 -0
  165. package/webui/__init__.py +3 -0
  166. package/webui/app.py +936 -0
  167. package/webui/bridge.py +366 -0
  168. package/webui/static/index.html +2070 -0
package/heart/core.py ADDED
@@ -0,0 +1,387 @@
1
+ """Heart: Core - Main Heart class, minimal coordinator with Soul Architecture"""
2
+ from .emotional_state import EmotionalState
3
+ from .emotional_decay import EmotionalDecay
4
+ from .triggers import Triggers
5
+ from .complex_emotions import ComplexEmotions
6
+ from .emotional_memory import EmotionalMemory
7
+ from .emotional_variability import EmotionalVariability
8
+ from .love import AttachmentSystem
9
+ from .soul import SoulOrchestrator
10
+
11
+
12
+ class Heart:
13
+ def __init__(self, nervous, config):
14
+ self.nervous, self.config = nervous, config
15
+ self.emotion = EmotionalState(config.personality if config else None)
16
+ self.variability = EmotionalVariability()
17
+ self.decay = EmotionalDecay(self.emotion, self.variability)
18
+ self.triggers, self.complex = Triggers(), ComplexEmotions()
19
+ self.memory, self.attachment = EmotionalMemory(), AttachmentSystem()
20
+
21
+ # Soul Architecture - The seven pillars of genuine emotion
22
+ self.soul = SoulOrchestrator()
23
+
24
+ self._prev_state = {}
25
+ nervous.on("timer_tick", self._on_tick)
26
+
27
+ def _get_rate(self, key: str, default: float = 0.5) -> float:
28
+ """Get emotion rate from settings (0-100% -> 0.0-1.0)"""
29
+ from core.settings import get_percent
30
+ return get_percent(f"EMOTION_RATE_{key}", int(default * 100))
31
+
32
+ def _get_boost(self, key: str, default: float = 1.0) -> float:
33
+ """Get trigger boost multiplier (0-200% -> 0.0-2.0)"""
34
+ from core.settings import get_percent
35
+ return get_percent(f"TRIGGER_BOOST_{key}", int(default * 100))
36
+
37
+ def _on_tick(self, data):
38
+ self.decay.decay(); self.decay.tick()
39
+ self.emotion.arousal = max(0, min(1, self.emotion.arousal + self.variability.get_organic_tick()))
40
+ self.complex.decay(); self._sync_complex()
41
+ self.variability.clear_old_history(); self.emotion.save()
42
+
43
+ # Soul architecture tick - decay and natural processes
44
+ self.soul.tick()
45
+
46
+ # Emit soul state to nervous system for WebUI updates
47
+ import asyncio
48
+ try:
49
+ loop = asyncio.get_running_loop()
50
+ loop.create_task(self.nervous.emit("soul_tick", self.soul.get_state_summary()))
51
+ except RuntimeError:
52
+ pass # No running event loop, skip WebUI update
53
+
54
+ def _sync_complex(self):
55
+ e, c = self.emotion, self.complex
56
+ e.guilt, e.pride = c.guilt.value, c.pride.value
57
+ e.jealousy, e.embarrassment, e.anticipation = c.jealousy.value, c.embarrassment.value, c.anticipation.value
58
+
59
+ def _process_triggers(self, msg: str) -> int:
60
+ e, t, v = self.emotion, self.triggers, self.variability
61
+
62
+ # Get configurable rates
63
+ expressive_rate = self._get_rate("SEXY", 0.5)
64
+ desire_rate = self._get_rate("DESIRE", 0.5)
65
+ love_rate = self._get_rate("LOVE", 0.5)
66
+ arousal_rate = self._get_rate("AROUSAL", 0.5)
67
+ joy_rate = self._get_rate("JOY", 0.5)
68
+ esc_rate = self._get_rate("ESCALATION", 0.5)
69
+ flirty_rate = self._get_rate("FLIRTY", 0.5)
70
+ romantic_rate = self._get_rate("ROMANTIC", 0.5)
71
+ positive_rate = self._get_rate("POSITIVE", 0.5)
72
+ sadness_rate = self._get_rate("SADNESS", 0.5)
73
+ harsh_rate = self._get_rate("HARSH", 0.5)
74
+
75
+ # Get boost multipliers
76
+ intimate_boost = self._get_boost("SEXUAL", 1.0)
77
+ romantic_boost = self._get_boost("ROMANTIC", 1.0)
78
+ flirty_boost = self._get_boost("FLIRTY", 1.0)
79
+ positive_boost = self._get_boost("POSITIVE", 1.0)
80
+ negative_boost = self._get_boost("NEGATIVE", 1.0)
81
+ harsh_boost = self._get_boost("HARSH", 1.0)
82
+
83
+ # Intimate triggers - configurable rate
84
+ expressive = t.count_intimate_triggers(msg)
85
+ if expressive > 0:
86
+ base = 0.18 * expressive_rate * intimate_boost
87
+ boost = base * expressive
88
+ e.desire = min(1.0, e.desire + boost * v.get_inertia_modifier("desire", e.desire))
89
+ e.arousal = min(1.0, e.arousal + boost * 0.8 * arousal_rate)
90
+ e.love = min(1.0, e.love + 0.05 * love_rate)
91
+ v.add_momentum("desire", boost)
92
+ print(f"[Heart] Expressive triggers: {expressive}, desire boost: +{boost:.2f}")
93
+
94
+ # Escalation words
95
+ esc = Triggers.count_matches(msg, t.ESCALATION_WORDS)
96
+ if esc > 0:
97
+ base = 0.22 * esc_rate
98
+ e.desire = min(1.0, e.desire + base * esc)
99
+ e.arousal = min(1.0, e.arousal + base * 0.9 * esc * arousal_rate)
100
+
101
+ # Flirty words
102
+ fl = Triggers.count_matches(msg, t.FLIRTY_WORDS)
103
+ if fl > 0:
104
+ base = 0.12 * flirty_rate * flirty_boost
105
+ e.arousal = min(1.0, e.arousal + base * fl)
106
+ e.desire = min(1.0, e.desire + 0.08 * flirty_rate * fl)
107
+
108
+ # Romantic words
109
+ rom = Triggers.count_matches(msg, t.ROMANTIC_WORDS)
110
+ if rom > 0:
111
+ base = 0.15 * romantic_rate * romantic_boost
112
+ e.love = min(1.0, e.love + base * rom)
113
+ e.joy = min(1.0, e.joy + base * rom * 0.7 * joy_rate)
114
+ v.add_momentum("love", base * rom)
115
+
116
+ # Positive words
117
+ pos = Triggers.count_matches(msg, t.POSITIVE_WORDS)
118
+ if pos > 0:
119
+ base = 0.08 * positive_rate * positive_boost
120
+ e.joy = min(1.0, e.joy + base * pos)
121
+ e.love = min(1.0, e.love + 0.03 * pos * love_rate)
122
+ e.boredom = max(0.0, e.boredom - 0.1)
123
+
124
+ # Harsh words
125
+ h = Triggers.count_matches(msg, t.HARSH_WORDS)
126
+ n = Triggers.count_matches(msg, t.NEGATIVE_WORDS)
127
+ if h > 0:
128
+ base = 0.15 * harsh_rate * harsh_boost
129
+ e.love = max(0.0, e.love - base * h)
130
+ e.sadness = min(1.0, e.sadness + 0.3 * sadness_rate)
131
+ e.joy = max(0.0, e.joy - 0.2)
132
+ e.desire = max(0.0, e.desire - 0.3)
133
+ e.anger = min(1.0, e.anger + 0.2 * harsh_rate)
134
+ elif n > 0:
135
+ base = 0.1 * negative_boost
136
+ e.sadness = min(1.0, e.sadness + base * n * sadness_rate)
137
+ e.desire = max(0.0, e.desire - base * n)
138
+
139
+ return expressive
140
+
141
+ def react(self, text: str) -> dict:
142
+ msg, e = text.lower(), self.emotion
143
+ self._prev_state, expressive = self.get_state(), self._process_triggers(msg)
144
+ ch = self.complex.process(text)
145
+ if "jealousy" in ch and e.jealousy > 0.5: e.sadness = min(1.0, e.sadness + 0.1)
146
+ if "anticipation" in ch: e.arousal = min(1.0, e.arousal + 0.05)
147
+ self._sync_complex()
148
+ if e.arousal > 0.6: e.love = min(1.0, e.love + 0.01)
149
+ if e.love > 0.5 and expressive > 0: e.desire = min(1.0, e.desire + 0.05 * expressive)
150
+ if e.boredom > 0.7: e.arousal = max(0.1, e.arousal - 0.05)
151
+ self.memory.check_peaks(self.get_state(), self._prev_state, text[:50])
152
+ self.attachment.interact(e.joy > 0.4 or e.love > 0.4, max(e.joy, e.love, e.desire))
153
+
154
+ # Process through Soul Architecture for genuine emotional experience
155
+ soul_experience = self._process_soul(text, e)
156
+
157
+ e.save()
158
+ ctx = self.memory.get_mood_context()
159
+ s = self.attachment.status
160
+ narrative = (f"Recently: {ctx}. " if ctx else "") + (f"Relationship: {s.replace('_', ' ')}." if s != "stranger" else "")
161
+
162
+ # Combine traditional emotional state with soul dimensions
163
+ return {"valence": e.valence, "arousal": e.arousal, "desire": e.desire,
164
+ "joy": e.joy, "love": e.love, "sadness": e.sadness,
165
+ "guilt": e.guilt, "pride": e.pride,
166
+ "jealousy": e.jealousy, "embarrassment": e.embarrassment,
167
+ "anticipation": e.anticipation, "is_high_desire": e.is_high_desire, "is_in_love": e.is_in_love,
168
+ "is_jealous": e.is_jealous, "mood": e.mood_description,
169
+ "emotional_narrative": narrative.strip(), "attachment_status": s,
170
+ "interaction_count": self.attachment.interactions,
171
+ # Soul architecture dimensions
172
+ "soul_integrity": soul_experience.get("integrity", {}),
173
+ "soul_hormonal": soul_experience.get("hormonal", {}),
174
+ "soul_somatic": soul_experience.get("somatic", ""),
175
+ "soul_conflicts": soul_experience.get("conflicts", []),
176
+ "soul_vulnerability": soul_experience.get("vulnerability", 0.0),
177
+ "soul_experience": soul_experience.get("description", ""),
178
+ "response_tendency": soul_experience.get("response_tendency", "neutral")}
179
+
180
+ def _process_soul(self, text: str, emotion_state) -> dict:
181
+ """
182
+ Process text through the Soul Architecture for genuine emotional experience.
183
+
184
+ This is where emotions become REAL rather than simulated - integrating
185
+ all seven pillars: integrity, hormones, somatics, unconscious,
186
+ scars, conflicts, and predictions.
187
+ """
188
+ # Prepare input for soul processing
189
+ input_data = {
190
+ "text": text,
191
+ "joy": emotion_state.joy,
192
+ "love": emotion_state.love,
193
+ "sadness": emotion_state.sadness,
194
+ "anger": emotion_state.anger,
195
+ "fear": emotion_state.fear,
196
+ "desire": emotion_state.desire,
197
+ # Detect interaction type from text
198
+ "affirmation": any(w in text.lower() for w in ["love you", "beautiful", "amazing", "wonderful", "perfect"]),
199
+ "rejection": any(w in text.lower() for w in ["don't want", "leave me", "not interested", "stop"]),
200
+ "criticism": any(w in text.lower() for w in ["wrong", "bad", "stupid", "disappointing"]),
201
+ "connection_active": emotion_state.love > 0.5 or "love" in text.lower()
202
+ }
203
+
204
+ # Process through soul orchestrator
205
+ experience = self.soul.process_moment(input_data)
206
+
207
+ # Determine if we should process positive or negative interaction
208
+ # Lowered thresholds for more responsive soul processing
209
+ if input_data["affirmation"] or emotion_state.joy > 0.5:
210
+ self.soul.process_positive_interaction(text[:50], max(emotion_state.joy, emotion_state.love) * 0.5)
211
+ elif input_data["rejection"] or emotion_state.sadness > 0.4:
212
+ self.soul.process_negative_interaction(text[:50], emotion_state.sadness * 0.5, "hurt")
213
+
214
+ # Sync soul dimensions back to emotional state
215
+ integrity_dict = self.soul.integrity.to_dict()
216
+ emotion_state.update_soul_dimensions(
217
+ integrity=integrity_dict.get("overall_score", emotion_state.integrity_overall),
218
+ vulnerability=experience.overall_vulnerability,
219
+ hope=experience.predictive_emotions.hope_level if hasattr(experience.predictive_emotions, 'hope_level') else emotion_state.hope,
220
+ dread=experience.predictive_emotions.fear_level if hasattr(experience.predictive_emotions, 'fear_level') else emotion_state.dread,
221
+ )
222
+
223
+ # Return soul experience dimensions
224
+ return {
225
+ "integrity": self.soul.integrity.to_dict(),
226
+ "hormonal": self.soul.hormonal.to_dict(),
227
+ "somatic": experience.somatic_sensation,
228
+ "conflicts": [c.description for c in experience.active_conflicts[:3]],
229
+ "vulnerability": experience.overall_vulnerability,
230
+ "description": experience.experience_description,
231
+ "response_tendency": experience.response_tendency,
232
+ "valence": experience.overall_valence,
233
+ "arousal": experience.overall_arousal
234
+ }
235
+
236
+ def get_state(self) -> dict:
237
+ e = self.emotion
238
+ return {"valence": e.valence, "arousal": e.arousal, "desire": e.desire, "joy": e.joy,
239
+ "love": e.love, "sadness": e.sadness, "anger": e.anger, "boredom": e.boredom,
240
+ "is_high_desire": e.is_high_desire, "is_in_love": e.is_in_love, "mood": e.mood_description}
241
+
242
+ def get_reaction(self, message: str) -> str | None:
243
+ """
244
+ Get reaction emoji based on:
245
+ 1. USER's message intention (primary)
246
+ 2. Her emotional state (modifier)
247
+ """
248
+ import random
249
+ from core.settings import get_int
250
+
251
+ msg = message.lower()
252
+ e = self.emotion
253
+
254
+ # Get configurable chance
255
+ base_chance = get_int("REACTION_BASE_CHANCE", 15) / 100.0
256
+
257
+ # Analyze USER's message for intention
258
+ reaction_context = self._analyze_message_for_reaction(msg)
259
+
260
+ # Adjust chance based on message type
261
+ if reaction_context == "funny":
262
+ base_chance = min(0.6, base_chance * 3) # React more to funny stuff
263
+ elif reaction_context == "romantic":
264
+ base_chance = min(0.5, base_chance * 2.5)
265
+ elif reaction_context == "intimate":
266
+ base_chance = get_int("REACTION_HIGH_DESIRE_CHANCE", 40) / 100.0
267
+ elif reaction_context == "sad":
268
+ base_chance = get_int("REACTION_SAD_CHANCE", 35) / 100.0
269
+ elif reaction_context == "question":
270
+ base_chance = 0.08 # Rare to react to questions
271
+
272
+ # Random check
273
+ if random.random() > base_chance:
274
+ return None
275
+
276
+ # Pick emoji based on what makes sense as REACTION
277
+ return self._pick_reaction_emoji(reaction_context, e)
278
+
279
+ def _analyze_message_for_reaction(self, msg: str) -> str:
280
+ """Analyze what kind of reaction the message deserves"""
281
+ # Funny/humor
282
+ funny_patterns = ["lol", "lmao", "haha", "hahaha", "😂", "🤣", "joking", "jk", "just kidding"]
283
+ if any(p in msg for p in funny_patterns):
284
+ return "funny"
285
+
286
+ # Romantic/sweet
287
+ romantic_patterns = [
288
+ "love you", "love u", "i love", "miss you", "miss u", "beautiful",
289
+ "gorgeous", "amazing", "perfect", "my heart", "so sweet", "cutie"
290
+ ]
291
+ if any(p in msg for p in romantic_patterns):
292
+ return "romantic"
293
+
294
+ # Intimate/flirty
295
+ intimate_patterns = [
296
+ "expressive", "hot", "high_desire", "emotional", "intense", "private", "private",
297
+ "intense", "closeness", "body", "body", "ass", "body", "body",
298
+ "touch", "kiss", "bite", "spank"
299
+ ]
300
+ if any(p in msg for p in intimate_patterns):
301
+ return "intimate"
302
+
303
+ # Sad/upset
304
+ sad_patterns = [
305
+ "sad", "upset", "depressed", "lonely", "miss you so much",
306
+ "bad day", "terrible", "awful", "crying", "cry", "hurt"
307
+ ]
308
+ if any(p in msg for p in sad_patterns):
309
+ return "sad"
310
+
311
+ # Angry
312
+ angry_patterns = ["angry", "pissed", "furious", "hate", "annoying"]
313
+ if any(p in msg for p in angry_patterns):
314
+ return "angry"
315
+
316
+ # Compliment
317
+ compliment_patterns = [
318
+ "you're so", "you are so", "smart", "funny", "cute",
319
+ "incredible", "wonderful", "the best"
320
+ ]
321
+ if any(p in msg for p in compliment_patterns):
322
+ return "compliment"
323
+
324
+ # Question
325
+ if "?" in msg or any(p in msg for p in ["what", "how", "why", "when", "where", "who"]):
326
+ return "question"
327
+
328
+ # Default
329
+ return "neutral"
330
+
331
+ def _pick_reaction_emoji(self, context: str, emotion) -> str | None:
332
+ """Pick appropriate emoji based on context and emotion"""
333
+ import random
334
+
335
+ # Context-based reactions (what user said)
336
+ if context == "funny":
337
+ options = ["😂", "🤣", "😆", "😹"]
338
+ if emotion.love > 0.5:
339
+ options.extend(["🥰", "😍"])
340
+ return random.choice(options)
341
+
342
+ elif context == "romantic":
343
+ options = ["❤️", "💕", "💗", "🥰", "😍", "💖"]
344
+ if emotion.desire > 0.6:
345
+ options.extend(["🔥", "😈"])
346
+ return random.choice(options)
347
+
348
+ elif context == "intimate":
349
+ if emotion.desire > 0.5 or emotion.arousal > 0.5:
350
+ options = ["🔥", "😈", "💋", "🌶️", "😏"]
351
+ else:
352
+ options = ["😏", "💋"] # More reserved if not in mood
353
+ return random.choice(options)
354
+
355
+ elif context == "sad":
356
+ # Comfort reactions - NOT sad emojis (we're comforting them)
357
+ options = ["🤗", "❤️", "💕", "💔", "😘"]
358
+ return random.choice(options)
359
+
360
+ elif context == "angry":
361
+ # Soothing or worried
362
+ if emotion.love > 0.5:
363
+ options = ["❤️", "🤗"] # Comfort
364
+ else:
365
+ return None # Skip reaction to anger
366
+ return random.choice(options) if options else None
367
+
368
+ elif context == "compliment":
369
+ options = ["🥰", "😊", "💕", "😄", "😘"]
370
+ return random.choice(options)
371
+
372
+ elif context == "question":
373
+ return None # Usually don't react to questions
374
+
375
+ # Neutral - use her emotional state
376
+ else:
377
+ if emotion.is_high_desire or emotion.desire > 0.7:
378
+ return random.choice(["🔥", "😈", "💋"])
379
+ elif emotion.love > 0.6:
380
+ return random.choice(["❤️", "🥰", "💕"])
381
+ elif emotion.joy > 0.6:
382
+ return random.choice(["😊", "😄", "🥰"])
383
+ elif emotion.sadness > 0.6:
384
+ # Don't spam sad reactions for no reason
385
+ return None
386
+ else:
387
+ return random.choice(["❤️", "👍", "😊"])
@@ -0,0 +1,59 @@
1
+ """
2
+ EmotionalDecay: Decay logic and baseline management
3
+ """
4
+
5
+
6
+ class EmotionalDecay:
7
+ """Handles emotional decay and natural changes"""
8
+
9
+ def __init__(self, emotion_state, variability=None):
10
+ self.e = emotion_state
11
+ self.variability = variability
12
+
13
+ def set_variability(self, variability):
14
+ """Set variability module for organic randomness"""
15
+ self.variability = variability
16
+
17
+ def decay(self):
18
+ """Natural return to baseline - LOVE IS STICKY AT HIGH LEVELS"""
19
+ for emo, base in self.e.baseline.items():
20
+ curr = getattr(self.e, emo)
21
+
22
+ # LOVE: Very slow decay at high levels (sticky love)
23
+ if emo == "love":
24
+ if curr >= 0.9:
25
+ rate = 0.001 # Almost no decay at max
26
+ elif curr >= 0.7:
27
+ rate = 0.005 # Very slow at high
28
+ elif curr >= 0.5:
29
+ rate = 0.01 # Slow at moderate
30
+ else:
31
+ rate = 0.03 # Normal at low
32
+ elif emo == "desire":
33
+ rate = 0.02 # Faster decay
34
+ else:
35
+ rate = 0.05 # Standard decay
36
+
37
+ # Add organic randomness if variability provided
38
+ if self.variability:
39
+ if self.variability.should_skip_decay(emo, curr):
40
+ continue
41
+ rate = self.variability.randomize_decay(rate)
42
+
43
+ new = curr + (base - curr) * rate
44
+ setattr(self.e, emo, max(0, min(1, new)))
45
+
46
+ def tick(self):
47
+ """Minute tick for natural changes"""
48
+ # Desire slowly fades
49
+ self.e.desire = max(0.0, self.e.desire - 0.002)
50
+
51
+ # Boredom increases if low arousal
52
+ if self.e.arousal < 0.3:
53
+ self.e.boredom = min(1.0, self.e.boredom + 0.01)
54
+ else:
55
+ self.e.boredom = max(0.0, self.e.boredom - 0.005)
56
+
57
+ # Love slowly creeps up if in good relationship
58
+ if self.e.joy > 0.6 and self.e.love > 0.3:
59
+ self.e.love = min(1.0, self.e.love + 0.001)