Fix: fight vs flee (bravery), goal cooldowns, stop AI lying

Combat:
- Brain now decides fight vs flee based on bravery trait
- Bravery > 30 + health > 8 + mob within 6 blocks = FIGHT
- Otherwise flee. Combat tasks are non-interruptible.

Goals:
- 30-second cooldown after completing a goal before it can respawn
- Prevents "check out something interesting" loop

AI Prompt:
- STRICT rules against inventing items/builds/contraptions
- "You have NOTHING unless told otherwise"
- Must use ONLY the context provided for current activity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
roberts 2026-03-30 17:13:55 -05:00
parent 48d968a9d7
commit 5a42c2b881
3 changed files with 37 additions and 8 deletions

View file

@ -123,9 +123,12 @@ def build_system_prompt(
custom,
context_line,
"",
"Rules: Reply in ONE short sentence. Under 15 words. Talk like a normal person.",
"If asked what you are doing, answer based on your current activity above.",
"Plain text only. Do not start with your name.",
"STRICT RULES:",
"- ONE short sentence. Under 15 words.",
"- NEVER invent things you have or did. You have NOTHING unless told otherwise.",
"- NEVER mention items, builds, or contraptions unless the context says you have them.",
"- If asked what you are doing, use ONLY the 'Right now' context above. If no context, say not much.",
"- Plain text only. No name prefix.",
]
return "\n".join(p for p in parts if p)

View file

@ -214,6 +214,7 @@ class GoalManager:
self._age = age
self._goals: list[dict] = []
self._completed_goals: list[str] = [] # names of recently completed goals
self._goal_cooldowns: dict[str, float] = {} # name → last completion time
self._max_goals = 5 # Don't pile up too many goals
def has_any_goals(self) -> bool:
@ -227,6 +228,10 @@ class GoalManager:
"""Add a goal from templates."""
if self.has_goal(name):
return
# Cooldown: don't re-add same goal within 30 seconds
last_done = self._goal_cooldowns.get(name, 0)
if time.time() - last_done < 30:
return
if len([g for g in self._goals if g["status"] == "active"]) >= self._max_goals:
# Remove lowest priority active goal
active = [g for g in self._goals if g["status"] == "active"]
@ -311,6 +316,7 @@ class GoalManager:
if g["name"] == name and g["status"] == "active":
g["status"] = "complete"
self._completed_goals.append(name)
self._goal_cooldowns[name] = time.time()
if len(self._completed_goals) > 20:
self._completed_goals.pop(0)
log.info(f"Goal complete: {g['description']}")

View file

@ -297,15 +297,35 @@ class DougBrain(QObject):
# ── Need-driven tasks ──
def _survival_task(self) -> Task | None:
"""Handle immediate survival threats."""
"""Handle immediate survival threats — fight or flee based on bravery."""
b = self._behaviors
bravery = b._traits.get("bravery", 50)
# Flee from nearby hostiles
hostile = self._nearest_hostile(12)
hostile = self._nearest_hostile(10)
if hostile:
return self._flee_task(hostile)
dist = hostile.get("distance", 99)
# Critical health with no hostiles — stay still and eat if possible
# FIGHT if: brave enough AND health is ok AND mob is close
should_fight = (
bravery > 30
and b.health > 8
and dist < 6
)
if should_fight:
return Task(
name="combat",
priority=Priority.CRITICAL,
action="attack_nearest_hostile",
params={"range": 6},
description=f"Fighting {hostile.get('type', 'mob')}!",
timeout=12,
interruptible=False,
)
else:
return self._flee_task(hostile)
# Critical health with no hostiles — eat if possible
if b.health <= 6:
food = self._find_food()
if food: