fix(P2): 5 priority-2 bug fixes — emoji consolidation, DM safety, pause gap

#10  Redundant coin flip in join_conversation — removed the 50% random
     gate that doubled the V2 engine's own decision to act.

#11  Message-triggered actions skip _autonomous_paused — _check_and_act
     and _check_and_react now bail out immediately when the autonomous
     system is paused (voice session), matching the scheduled-tick path.

#12  Duplicate emoji dictionaries — removed MOOD_EMOJIS and
     EVIL_MOOD_EMOJIS from globals.py (had different emojis from moods.py).
     bipolar_mode.py and evil_mode.py now import the canonical dicts
     from utils/moods.py so all code sees the same emojis.

#13  DM mood can spontaneously become 'asleep' — rotate_dm_mood() now
     filters 'asleep' out of the candidate list since DMs have no
     sleepy-to-asleep transition guard and no wakeup timer.

#15  Engage-user fallback misreports action type — log level raised to
     WARNING with an explicit [engage_user->general] prefix so the
     cooldown-triggered fallback is visible in logs.
This commit is contained in:
2026-02-23 13:43:15 +02:00
parent 0e4aebf353
commit 2b743ed65e
6 changed files with 20 additions and 32 deletions

View File

@@ -184,6 +184,9 @@ async def _check_and_react(guild_id: int, message):
Check if Miku should react to a new message with an emoji.
Called for each new message in real-time.
"""
if _autonomous_paused:
return
# Calculate message age
from datetime import datetime, timezone
message_age = (datetime.now(timezone.utc) - message.created_at).total_seconds()
@@ -211,6 +214,9 @@ async def _check_and_act(guild_id: int):
Uses per-guild lock to prevent race conditions from near-simultaneous messages.
"""
async with _get_action_lock(guild_id):
if _autonomous_paused:
return
# Rate limiting check
now = time.time()
if guild_id in _last_action_execution:

View File

@@ -304,7 +304,7 @@ async def miku_engage_random_user_for_server(guild_id: int, user_id: str = None,
# Skip cooldown check if this is a manual trigger from web UI
if not manual_trigger and now - last_time < 43200: # 12 hours in seconds
logger.info(f"Recently engaged {target.display_name} in server {guild_id}, switching to general message.")
logger.warning(f"[engage_user→general] Recently engaged {target.display_name} in server {guild_id}, falling back to general message (cooldown).")
await miku_say_something_general_for_server(guild_id)
return
@@ -456,10 +456,7 @@ async def miku_detect_and_join_conversation_for_server(guild_id: int, force: boo
# Not enough activity
logger.debug(f"[Join Conv] Not enough activity: {len(recent_msgs)} messages, {len(user_ids)} users (need 5+ messages, 2+ users)")
return
if random.random() > 0.5:
logger.debug(f"[Join Conv] Random chance failed (50% chance)")
return # 50% chance to engage
# Note: V1 had a redundant 50% coin flip here, removed since V2 engine already decided to act
else:
logger.debug(f"[Join Conv] Force mode - bypassing activity checks")
if len(recent_msgs) < 1:

View File

@@ -404,8 +404,9 @@ async def update_webhook_avatars(client):
def get_miku_display_name() -> str:
"""Get Regular Miku's display name with mood and emoji"""
from utils.moods import MOOD_EMOJIS
mood = globals.DM_MOOD
emoji = globals.MOOD_EMOJIS.get(mood, "")
emoji = MOOD_EMOJIS.get(mood, "")
if emoji:
return f"Hatsune Miku {emoji}"
return "Hatsune Miku"
@@ -413,8 +414,9 @@ def get_miku_display_name() -> str:
def get_evil_miku_display_name() -> str:
"""Get Evil Miku's display name with mood and emoji"""
from utils.moods import EVIL_MOOD_EMOJIS
mood = globals.EVIL_DM_MOOD
emoji = globals.EVIL_MOOD_EMOJIS.get(mood, "")
emoji = EVIL_MOOD_EMOJIS.get(mood, "")
if emoji:
return f"Evil Miku {emoji}"
return "Evil Miku"

View File

@@ -191,7 +191,8 @@ def load_evil_mood_description(mood_name: str) -> str:
def get_evil_mood_emoji(mood_name: str) -> str:
"""Get emoji for evil mood"""
return globals.EVIL_MOOD_EMOJIS.get(mood_name, "")
from utils.moods import EVIL_MOOD_EMOJIS
return EVIL_MOOD_EMOJIS.get(mood_name, "")
def is_valid_evil_mood(mood_name: str) -> bool:

View File

@@ -162,9 +162,11 @@ async def rotate_dm_mood():
old_mood = globals.DM_MOOD
new_mood = old_mood
attempts = 0
# Filter out 'asleep' — DMs have no sleepy→asleep transition guard
dm_eligible = [m for m in globals.AVAILABLE_MOODS if m != "asleep"]
while new_mood == old_mood and attempts < 5:
new_mood = random.choice(globals.AVAILABLE_MOODS)
new_mood = random.choice(dm_eligible)
attempts += 1
globals.DM_MOOD = new_mood