feat: rarity + level scaled auto-attacks, wound cooldown for parallel sessions

Auto-attack rates were flat 50%/35% regardless of encounter rarity or buddy
level, and parallel sessions could each roll independently against the same
encounter — compounding to ~87.5% effective wound rate with 3 sessions open.

Wound rates by rarity (base, before level scaling):
  very_common: 55%  common: 40%  uncommon: 22%  rare: 10%  legendary: 2%

Auto-resolve rates by rarity:
  very_common: 45%  common: 28%  uncommon: 14%  rare: 5%   legendary: 1%

Level multiplier: 1.0 + (level / 100) * 0.25 — Lv.44 adds ~11% to each.
So Lv.44 vs common: wound=44%, resolve=31%. Lv.44 vs rare: wound=11%, resolve=6%.

Wound cooldown: encounters stamped with last_wounded_at. Any session that
would wound/resolve within 30s of the last wound is skipped — prevents
parallel sessions from pile-driving the same encounter.
This commit is contained in:
pyr0ball 2026-04-06 00:01:10 -07:00
parent c12a234652
commit b3b1813e9c

View file

@ -200,6 +200,7 @@ def wound_encounter() -> None:
enc["current_strength"] = 5 enc["current_strength"] = 5
enc["wounded"] = True enc["wounded"] = True
enc["announced"] = False # triggers UserPromptSubmit re-announcement enc["announced"] = False # triggers UserPromptSubmit re-announcement
enc["last_wounded_at"] = datetime.now().astimezone().isoformat()
data["active_encounter"] = enc data["active_encounter"] = enc
save_json(enc_file, data) save_json(enc_file, data)
@ -464,18 +465,47 @@ def main():
enc_data = load_json(BUDDYMON_DIR / "encounters.json") enc_data = load_json(BUDDYMON_DIR / "encounters.json")
enc_data["active_encounter"] = existing enc_data["active_encounter"] = existing
save_json(BUDDYMON_DIR / "encounters.json", enc_data) save_json(BUDDYMON_DIR / "encounters.json", enc_data)
elif existing.get("wounded"):
# Wounded: 35% chance to flee per clean run (avg ~3 runs to escape)
if random.random() < 0.35:
xp, display = auto_resolve_encounter(existing, buddy_id)
messages.append(
f"\n💨 **{display} fled!** (escaped while wounded)\n"
f" {buddy_display} gets partial XP: +{xp}\n"
)
else: else:
# Healthy: 50% chance to wound per clean run (avg ~2 runs to wound) # Auto-attack rates scaled by encounter rarity and buddy level.
if random.random() < 0.50: # Multiple parallel sessions share encounters.json — a wound
wound_encounter() # cooldown prevents them pile-driving the same encounter.
rarity = existing.get("rarity", "common")
WOUND_RATES = {
"very_common": 0.55, "common": 0.40,
"uncommon": 0.22, "rare": 0.10, "legendary": 0.02,
}
RESOLVE_RATES = {
"very_common": 0.45, "common": 0.28,
"uncommon": 0.14, "rare": 0.05, "legendary": 0.01,
}
roster = load_json(BUDDYMON_DIR / "roster.json")
buddy_level = roster.get("owned", {}).get(buddy_id, {}).get("level", 1)
level_scale = 1.0 + (buddy_level / 100) * 0.25
# Wound cooldown: skip if another session wounded within 30s
last_wound = existing.get("last_wounded_at", "")
wound_cooldown_ok = True
if last_wound:
try:
from datetime import timezone as _tz
last_dt = datetime.fromisoformat(last_wound)
age = (datetime.now(_tz.utc) - last_dt).total_seconds()
wound_cooldown_ok = age > 30
except Exception:
pass
if existing.get("wounded"):
resolve_rate = min(0.70, RESOLVE_RATES.get(rarity, 0.28) * level_scale)
if wound_cooldown_ok and random.random() < resolve_rate:
xp, display = auto_resolve_encounter(existing, buddy_id)
messages.append(
f"\n💨 **{display} fled!** (escaped while wounded)\n"
f" {buddy_display} gets partial XP: +{xp}\n"
)
else:
wound_rate = min(0.85, WOUND_RATES.get(rarity, 0.40) * level_scale)
if wound_cooldown_ok and random.random() < wound_rate:
wound_encounter()
# else: monster still present, no message — don't spam every tool call # else: monster still present, no message — don't spam every tool call
elif output or command: elif output or command:
# No active encounter — check for bug monster first, then event encounters # No active encounter — check for bug monster first, then event encounters