feat: evolution system — prestige to evolved form at Lv.100

Evolution triggers at Lv.100 for all three starters:
  Pyrobyte → 🌋 Infernus  (power 40→70, catch_rate 0.45→0.55)
  Debuglin → 🔬 Verifex   (power 35→60, catch_rate 0.60→0.75)
  Minimox  → 🌑 Nullex    (power 35→55, catch_rate 0.50→0.65)

/buddymon evolve: checks eligibility, shows stat preview, resets buddy
to Lv.1 in evolved form, archives old form with evolved_into marker,
carries challenges forward.

session-stop.sh now prints EVOLUTION READY banner when level hits 100
or when already eligible at session end.
This commit is contained in:
pyr0ball 2026-04-02 23:08:32 -07:00
parent 85af20b6f1
commit 8e0a5f82cb
3 changed files with 565 additions and 139 deletions

View file

@ -72,10 +72,27 @@ if langs:
if leveled_up: if leveled_up:
lines.append(f"\n✨ **LEVEL UP!** {display} is now Lv.{new_level}!") lines.append(f"\n✨ **LEVEL UP!** {display} is now Lv.{new_level}!")
# Check if evolution is now available
catalog_entry = (catalog.get('buddymon', {}).get(buddy_id)
or catalog.get('evolutions', {}).get(buddy_id) or {})
evolutions = catalog_entry.get('evolutions', [])
evo = next((e for e in evolutions if new_level >= e.get('level', 999)), None)
if evo:
into = catalog.get('evolutions', {}).get(evo['into'], {})
lines.append(f"\n⭐ **EVOLUTION READY!** {display} can evolve into {into.get('display', evo['into'])}!")
lines.append(f" Run `/buddymon evolve` to prestige — resets to Lv.1 with upgraded stats.")
else: else:
filled = min(20, total_xp * 20 // xp_needed) filled = min(20, total_xp * 20 // xp_needed)
bar = '█' * filled + '░' * (20 - filled) bar = '█' * filled + '░' * (20 - filled)
lines.append(f"XP: [{bar}] {total_xp}/{xp_needed}") lines.append(f"XP: [{bar}] {total_xp}/{xp_needed}")
# Remind if already at evolution threshold but hasn't evolved yet
catalog_entry = (catalog.get('buddymon', {}).get(buddy_id)
or catalog.get('evolutions', {}).get(buddy_id) or {})
evolutions = catalog_entry.get('evolutions', [])
evo = next((e for e in evolutions if level >= e.get('level', 999)), None)
if evo:
into = catalog.get('evolutions', {}).get(evo['into'], {})
lines.append(f"\n⭐ **Evolution available:** `/buddymon evolve` → {into.get('display', evo['into'])}")
if challenge: if challenge:
if challenge_completed: if challenge_completed:

View file

@ -1,11 +1,10 @@
{ {
"_version": 1, "_version": 1,
"_note": "Master species catalog. discovered=false entries are hidden until triggered.", "_note": "Master species catalog. discovered=false entries are hidden until triggered.",
"bug_monsters": { "bug_monsters": {
"NullWraith": { "NullWraith": {
"id": "NullWraith", "id": "NullWraith",
"display": "👻 NullWraith", "display": "\ud83d\udc7b NullWraith",
"type": "bug_monster", "type": "bug_monster",
"rarity": "common", "rarity": "common",
"base_strength": 20, "base_strength": 20,
@ -22,15 +21,24 @@
"null reference" "null reference"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 20}, {
{"action": "isolate_reproduction", "strength_reduction": 20}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 10} "strength_reduction": 20
},
{
"action": "isolate_reproduction",
"strength_reduction": 20
},
{
"action": "add_documenting_comment",
"strength_reduction": 10
}
], ],
"flavor": "It was there this whole time. You just never checked." "flavor": "It was there this whole time. You just never checked."
}, },
"FencepostDemon": { "FencepostDemon": {
"id": "FencepostDemon", "id": "FencepostDemon",
"display": "😈 FencepostDemon", "display": "\ud83d\ude08 FencepostDemon",
"type": "bug_monster", "type": "bug_monster",
"rarity": "common", "rarity": "common",
"base_strength": 25, "base_strength": 25,
@ -46,15 +54,24 @@
"off.by.one" "off.by.one"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 20}, {
{"action": "isolate_reproduction", "strength_reduction": 20}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 10} "strength_reduction": 20
},
{
"action": "isolate_reproduction",
"strength_reduction": 20
},
{
"action": "add_documenting_comment",
"strength_reduction": 10
}
], ],
"flavor": "Always one step ahead. Or behind. It's hard to tell." "flavor": "Always one step ahead. Or behind. It's hard to tell."
}, },
"TypeGreml": { "TypeGreml": {
"id": "TypeGreml", "id": "TypeGreml",
"display": "🔧 TypeGreml", "display": "\ud83d\udd27 TypeGreml",
"type": "bug_monster", "type": "bug_monster",
"rarity": "common", "rarity": "common",
"base_strength": 25, "base_strength": 25,
@ -70,15 +87,24 @@
"type.*is not assignable" "type.*is not assignable"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 20}, {
{"action": "isolate_reproduction", "strength_reduction": 20}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 10} "strength_reduction": 20
},
{
"action": "isolate_reproduction",
"strength_reduction": 20
},
{
"action": "add_documenting_comment",
"strength_reduction": 10
}
], ],
"flavor": "It only attacks when you're absolutely sure about the type." "flavor": "It only attacks when you're absolutely sure about the type."
}, },
"SyntaxSerpent": { "SyntaxSerpent": {
"id": "SyntaxSerpent", "id": "SyntaxSerpent",
"display": "🐍 SyntaxSerpent", "display": "\ud83d\udc0d SyntaxSerpent",
"type": "bug_monster", "type": "bug_monster",
"rarity": "very_common", "rarity": "very_common",
"base_strength": 10, "base_strength": 10,
@ -95,14 +121,20 @@
"ParseError" "ParseError"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 30}, {
{"action": "add_documenting_comment", "strength_reduction": 20} "action": "write_failing_test",
"strength_reduction": 30
},
{
"action": "add_documenting_comment",
"strength_reduction": 20
}
], ],
"flavor": "You'll be embarrassed you let this one survive long enough to catch." "flavor": "You'll be embarrassed you let this one survive long enough to catch."
}, },
"CORSCurse": { "CORSCurse": {
"id": "CORSCurse", "id": "CORSCurse",
"display": "🌐 CORSCurse", "display": "\ud83c\udf10 CORSCurse",
"type": "bug_monster", "type": "bug_monster",
"rarity": "common", "rarity": "common",
"base_strength": 40, "base_strength": 40,
@ -118,15 +150,24 @@
"No 'Access-Control-Allow-Origin'" "No 'Access-Control-Allow-Origin'"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 20}, {
{"action": "isolate_reproduction", "strength_reduction": 25}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 10} "strength_reduction": 20
},
{
"action": "isolate_reproduction",
"strength_reduction": 25
},
{
"action": "add_documenting_comment",
"strength_reduction": 10
}
], ],
"flavor": "It's not your fault. Well. It kind of is." "flavor": "It's not your fault. Well. It kind of is."
}, },
"LoopLich": { "LoopLich": {
"id": "LoopLich", "id": "LoopLich",
"display": "♾️ LoopLich", "display": "\u267e\ufe0f LoopLich",
"type": "bug_monster", "type": "bug_monster",
"rarity": "uncommon", "rarity": "uncommon",
"base_strength": 60, "base_strength": 60,
@ -146,15 +187,24 @@
"StackOverflow" "StackOverflow"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 20}, {
{"action": "isolate_reproduction", "strength_reduction": 25}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 15} "strength_reduction": 20
},
{
"action": "isolate_reproduction",
"strength_reduction": 25
},
{
"action": "add_documenting_comment",
"strength_reduction": 15
}
], ],
"flavor": "The exit condition was always there. You just never believed in it." "flavor": "The exit condition was always there. You just never believed in it."
}, },
"RacePhantom": { "RacePhantom": {
"id": "RacePhantom", "id": "RacePhantom",
"display": "👁️ RacePhantom", "display": "\ud83d\udc41\ufe0f RacePhantom",
"type": "bug_monster", "type": "bug_monster",
"rarity": "rare", "rarity": "rare",
"base_strength": 80, "base_strength": 80,
@ -172,15 +222,24 @@
"async.*conflict" "async.*conflict"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 15}, {
{"action": "isolate_reproduction", "strength_reduction": 30}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 15} "strength_reduction": 15
},
{
"action": "isolate_reproduction",
"strength_reduction": 30
},
{
"action": "add_documenting_comment",
"strength_reduction": 15
}
], ],
"flavor": "You've proven it exists. That's honestly impressive on its own." "flavor": "You've proven it exists. That's honestly impressive on its own."
}, },
"FossilGolem": { "FossilGolem": {
"id": "FossilGolem", "id": "FossilGolem",
"display": "🗿 FossilGolem", "display": "\ud83d\uddff FossilGolem",
"type": "bug_monster", "type": "bug_monster",
"rarity": "uncommon", "rarity": "uncommon",
"base_strength": 35, "base_strength": 35,
@ -197,23 +256,36 @@
"legacy" "legacy"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 20}, {
{"action": "isolate_reproduction", "strength_reduction": 20}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 20} "strength_reduction": 20
},
{
"action": "isolate_reproduction",
"strength_reduction": 20
},
{
"action": "add_documenting_comment",
"strength_reduction": 20
}
], ],
"flavor": "It survived every major version. It will outlast you." "flavor": "It survived every major version. It will outlast you."
}, },
"ShadowBit": { "ShadowBit": {
"id": "ShadowBit", "id": "ShadowBit",
"display": "🔒 ShadowBit", "display": "\ud83d\udd12 ShadowBit",
"type": "bug_monster", "type": "bug_monster",
"rarity": "rare", "rarity": "rare",
"base_strength": 90, "base_strength": 90,
"xp_reward": 300, "xp_reward": 300,
"catchable": true, "catchable": true,
"defeatable": false, "defeatable": false,
"catch_requires": ["write_failing_test", "isolate_reproduction", "add_documenting_comment"], "catch_requires": [
"description": "Cannot be defeated — only properly contained. Requires full documentation + patching.", "write_failing_test",
"isolate_reproduction",
"add_documenting_comment"
],
"description": "Cannot be defeated \u2014 only properly contained. Requires full documentation + patching.",
"error_patterns": [ "error_patterns": [
"vulnerability", "vulnerability",
"CVE-", "CVE-",
@ -229,15 +301,24 @@
"hardcoded.*token" "hardcoded.*token"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 25}, {
{"action": "isolate_reproduction", "strength_reduction": 35}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 20} "strength_reduction": 25
},
{
"action": "isolate_reproduction",
"strength_reduction": 35
},
{
"action": "add_documenting_comment",
"strength_reduction": 20
}
], ],
"flavor": "Defeat is not an option. Containment is the only victory." "flavor": "Defeat is not an option. Containment is the only victory."
}, },
"VoidSpecter": { "VoidSpecter": {
"id": "VoidSpecter", "id": "VoidSpecter",
"display": "🌫️ VoidSpecter", "display": "\ud83c\udf2b\ufe0f VoidSpecter",
"type": "bug_monster", "type": "bug_monster",
"rarity": "common", "rarity": "common",
"base_strength": 20, "base_strength": 20,
@ -254,15 +335,24 @@
"endpoint.*not found" "endpoint.*not found"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 25}, {
{"action": "isolate_reproduction", "strength_reduction": 25}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 10} "strength_reduction": 25
},
{
"action": "isolate_reproduction",
"strength_reduction": 25
},
{
"action": "add_documenting_comment",
"strength_reduction": 10
}
], ],
"flavor": "It used to exist. Probably." "flavor": "It used to exist. Probably."
}, },
"MemoryLeech": { "MemoryLeech": {
"id": "MemoryLeech", "id": "MemoryLeech",
"display": "🩸 MemoryLeech", "display": "\ud83e\ude78 MemoryLeech",
"type": "bug_monster", "type": "bug_monster",
"rarity": "uncommon", "rarity": "uncommon",
"base_strength": 55, "base_strength": 55,
@ -287,15 +377,24 @@
"malloc: can't allocate region" "malloc: can't allocate region"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 20}, {
{"action": "isolate_reproduction", "strength_reduction": 30}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 10} "strength_reduction": 20
},
{
"action": "isolate_reproduction",
"strength_reduction": 30
},
{
"action": "add_documenting_comment",
"strength_reduction": 10
}
], ],
"flavor": "It was already there when you opened the task manager." "flavor": "It was already there when you opened the task manager."
}, },
"CudaCrash": { "CudaCrash": {
"id": "CudaCrash", "id": "CudaCrash",
"display": "⚡ CudaCrash", "display": "\u26a1 CudaCrash",
"type": "bug_monster", "type": "bug_monster",
"rarity": "uncommon", "rarity": "uncommon",
"base_strength": 65, "base_strength": 65,
@ -315,15 +414,24 @@
"device-side assert triggered" "device-side assert triggered"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "isolate_reproduction", "strength_reduction": 30}, {
{"action": "write_failing_test", "strength_reduction": 20}, "action": "isolate_reproduction",
{"action": "add_documenting_comment", "strength_reduction": 10} "strength_reduction": 30
},
{
"action": "write_failing_test",
"strength_reduction": 20
},
{
"action": "add_documenting_comment",
"strength_reduction": 10
}
], ],
"flavor": "Your model fit in VRAM yesterday. You added one layer." "flavor": "Your model fit in VRAM yesterday. You added one layer."
}, },
"InfiniteWisp": { "InfiniteWisp": {
"id": "InfiniteWisp", "id": "InfiniteWisp",
"display": "🌀 InfiniteWisp", "display": "\ud83c\udf00 InfiniteWisp",
"type": "bug_monster", "type": "bug_monster",
"rarity": "common", "rarity": "common",
"base_strength": 30, "base_strength": 30,
@ -341,15 +449,24 @@
"timed out after" "timed out after"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 20}, {
{"action": "isolate_reproduction", "strength_reduction": 30}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 10} "strength_reduction": 20
},
{
"action": "isolate_reproduction",
"strength_reduction": 30
},
{
"action": "add_documenting_comment",
"strength_reduction": 10
}
], ],
"flavor": "Your fan was always loud. You just never checked why." "flavor": "Your fan was always loud. You just never checked why."
}, },
"BoundsHound": { "BoundsHound": {
"id": "BoundsHound", "id": "BoundsHound",
"display": "🐕 BoundsHound", "display": "\ud83d\udc15 BoundsHound",
"type": "bug_monster", "type": "bug_monster",
"rarity": "common", "rarity": "common",
"base_strength": 25, "base_strength": 25,
@ -369,15 +486,24 @@
"RangeError.*index" "RangeError.*index"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 25}, {
{"action": "isolate_reproduction", "strength_reduction": 25}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 10} "strength_reduction": 25
},
{
"action": "isolate_reproduction",
"strength_reduction": 25
},
{
"action": "add_documenting_comment",
"strength_reduction": 10
}
], ],
"flavor": "It was always length minus one. You just forgot." "flavor": "It was always length minus one. You just forgot."
}, },
"BranchGhost": { "BranchGhost": {
"id": "BranchGhost", "id": "BranchGhost",
"display": "🔀 BranchGhost", "display": "\ud83d\udd00 BranchGhost",
"type": "bug_monster", "type": "bug_monster",
"rarity": "uncommon", "rarity": "uncommon",
"base_strength": 40, "base_strength": 40,
@ -396,15 +522,24 @@
"fallthrough" "fallthrough"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 30}, {
{"action": "isolate_reproduction", "strength_reduction": 20}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 10} "strength_reduction": 30
},
{
"action": "isolate_reproduction",
"strength_reduction": 20
},
{
"action": "add_documenting_comment",
"strength_reduction": 10
}
], ],
"flavor": "You were so sure that case was impossible." "flavor": "You were so sure that case was impossible."
}, },
"SwitchTrap": { "SwitchTrap": {
"id": "SwitchTrap", "id": "SwitchTrap",
"display": "🪤 SwitchTrap", "display": "\ud83e\udea4 SwitchTrap",
"type": "bug_monster", "type": "bug_monster",
"rarity": "uncommon", "rarity": "uncommon",
"base_strength": 35, "base_strength": 35,
@ -425,15 +560,24 @@
"Unhandled variant" "Unhandled variant"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 25}, {
{"action": "isolate_reproduction", "strength_reduction": 25}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 10} "strength_reduction": 25
},
{
"action": "isolate_reproduction",
"strength_reduction": 25
},
{
"action": "add_documenting_comment",
"strength_reduction": 10
}
], ],
"flavor": "You added that new enum value last week. The switch didn't notice." "flavor": "You added that new enum value last week. The switch didn't notice."
}, },
"RecurseWraith": { "RecurseWraith": {
"id": "RecurseWraith", "id": "RecurseWraith",
"display": "🌪️ RecurseWraith", "display": "\ud83c\udf2a\ufe0f RecurseWraith",
"type": "bug_monster", "type": "bug_monster",
"rarity": "uncommon", "rarity": "uncommon",
"base_strength": 45, "base_strength": 45,
@ -452,22 +596,31 @@
"stack.*exhausted" "stack.*exhausted"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 25}, {
{"action": "isolate_reproduction", "strength_reduction": 25}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 10} "strength_reduction": 25
},
{
"action": "isolate_reproduction",
"strength_reduction": 25
},
{
"action": "add_documenting_comment",
"strength_reduction": 10
}
], ],
"flavor": "The base case was there. It just couldn't be reached." "flavor": "The base case was there. It just couldn't be reached."
}, },
"CatchAll": { "CatchAll": {
"id": "CatchAll", "id": "CatchAll",
"display": "🕳️ CatchAll", "display": "\ud83d\udd73\ufe0f CatchAll",
"type": "bug_monster", "type": "bug_monster",
"rarity": "rare", "rarity": "rare",
"base_strength": 60, "base_strength": 60,
"xp_reward": 120, "xp_reward": 120,
"catchable": true, "catchable": true,
"defeatable": false, "defeatable": false,
"description": "Born from broad exception handlers. Swallows errors whole. Cannot be defeated only caught, by narrowing the catch.", "description": "Born from broad exception handlers. Swallows errors whole. Cannot be defeated \u2014 only caught, by narrowing the catch.",
"error_patterns": [ "error_patterns": [
"except Exception", "except Exception",
"except:$", "except:$",
@ -479,18 +632,26 @@
"catch-all.*handler" "catch-all.*handler"
], ],
"weaken_actions": [ "weaken_actions": [
{"action": "write_failing_test", "strength_reduction": 30}, {
{"action": "isolate_reproduction", "strength_reduction": 30}, "action": "write_failing_test",
{"action": "add_documenting_comment", "strength_reduction": 15} "strength_reduction": 30
},
{
"action": "isolate_reproduction",
"strength_reduction": 30
},
{
"action": "add_documenting_comment",
"strength_reduction": 15
}
], ],
"flavor": "If you catch everything, you learn nothing." "flavor": "If you catch everything, you learn nothing."
} }
}, },
"event_encounters": { "event_encounters": {
"MergeMaw": { "MergeMaw": {
"id": "MergeMaw", "id": "MergeMaw",
"display": "🔀 MergeMaw", "display": "\ud83d\udd00 MergeMaw",
"type": "event_encounter", "type": "event_encounter",
"rarity": "uncommon", "rarity": "uncommon",
"base_strength": 45, "base_strength": 45,
@ -498,13 +659,16 @@
"catchable": true, "catchable": true,
"defeatable": true, "defeatable": true,
"trigger_type": "command", "trigger_type": "command",
"command_patterns": ["git merge", "git rebase"], "command_patterns": [
"git merge",
"git rebase"
],
"description": "Emerges from the diff between two timelines. Loves conflicts.", "description": "Emerges from the diff between two timelines. Loves conflicts.",
"flavor": "It has opinions about your whitespace." "flavor": "It has opinions about your whitespace."
}, },
"BranchSprite": { "BranchSprite": {
"id": "BranchSprite", "id": "BranchSprite",
"display": "🌿 BranchSprite", "display": "\ud83c\udf3f BranchSprite",
"type": "event_encounter", "type": "event_encounter",
"rarity": "common", "rarity": "common",
"base_strength": 15, "base_strength": 15,
@ -512,13 +676,17 @@
"catchable": true, "catchable": true,
"defeatable": false, "defeatable": false,
"trigger_type": "command", "trigger_type": "command",
"command_patterns": ["git checkout -b", "git switch -c", "git branch "], "command_patterns": [
"git checkout -b",
"git switch -c",
"git branch "
],
"description": "Appears when a new branch is born. Harmless. Almost cheerful.", "description": "Appears when a new branch is born. Harmless. Almost cheerful.",
"flavor": "It wanted to come along for the feature." "flavor": "It wanted to come along for the feature."
}, },
"DepGolem": { "DepGolem": {
"id": "DepGolem", "id": "DepGolem",
"display": "📦 DepGolem", "display": "\ud83d\udce6 DepGolem",
"type": "event_encounter", "type": "event_encounter",
"rarity": "common", "rarity": "common",
"base_strength": 30, "base_strength": 30,
@ -526,13 +694,25 @@
"catchable": true, "catchable": true,
"defeatable": true, "defeatable": true,
"trigger_type": "command", "trigger_type": "command",
"command_patterns": ["pip install", "pip3 install", "npm install", "npm i ", "cargo add", "yarn add", "apt install", "brew install", "poetry add", "uv add", "uv pip install"], "command_patterns": [
"pip install",
"pip3 install",
"npm install",
"npm i ",
"cargo add",
"yarn add",
"apt install",
"brew install",
"poetry add",
"uv add",
"uv pip install"
],
"description": "Conjured from the package registry. Brings transitive dependencies.", "description": "Conjured from the package registry. Brings transitive dependencies.",
"flavor": "It brought 847 friends." "flavor": "It brought 847 friends."
}, },
"FlakeDemon": { "FlakeDemon": {
"id": "FlakeDemon", "id": "FlakeDemon",
"display": "🎲 FlakeDemon", "display": "\ud83c\udfb2 FlakeDemon",
"type": "event_encounter", "type": "event_encounter",
"rarity": "uncommon", "rarity": "uncommon",
"base_strength": 55, "base_strength": 55,
@ -555,7 +735,7 @@
}, },
"PhantomPass": { "PhantomPass": {
"id": "PhantomPass", "id": "PhantomPass",
"display": " PhantomPass", "display": "\u2705 PhantomPass",
"type": "event_encounter", "type": "event_encounter",
"rarity": "rare", "rarity": "rare",
"base_strength": 10, "base_strength": 10,
@ -568,17 +748,17 @@
"PASSED", "PASSED",
"All tests passed", "All tests passed",
"tests passed", "tests passed",
"", "\u2713",
"\\d+ passed", "\\d+ passed",
"OK$", "OK$",
"SUCCESS" "SUCCESS"
], ],
"description": "Appears only when tests go green after going red. Rare. Fleeting. Cannot be fought only caught.", "description": "Appears only when tests go green after going red. Rare. Fleeting. Cannot be fought \u2014 only caught.",
"flavor": "It was hiding in the red all along." "flavor": "It was hiding in the red all along."
}, },
"TestSpecter": { "TestSpecter": {
"id": "TestSpecter", "id": "TestSpecter",
"display": "🧪 TestSpecter", "display": "\ud83e\uddea TestSpecter",
"type": "event_encounter", "type": "event_encounter",
"rarity": "uncommon", "rarity": "uncommon",
"base_strength": 25, "base_strength": 25,
@ -586,13 +766,19 @@
"catchable": true, "catchable": true,
"defeatable": true, "defeatable": true,
"trigger_type": "test_file", "trigger_type": "test_file",
"test_file_patterns": ["\\.test\\.", "_test\\.", "test_", "_spec\\.", "\\.spec\\."], "test_file_patterns": [
"\\.test\\.",
"_test\\.",
"test_",
"_spec\\.",
"\\.spec\\."
],
"description": "Haunts test suites. Drawn to assertions. Debuglin gets excited.", "description": "Haunts test suites. Drawn to assertions. Debuglin gets excited.",
"flavor": "It wanted to make sure the test was named correctly." "flavor": "It wanted to make sure the test was named correctly."
}, },
"ReviewHawk": { "ReviewHawk": {
"id": "ReviewHawk", "id": "ReviewHawk",
"display": "🦅 ReviewHawk", "display": "\ud83e\udd85 ReviewHawk",
"type": "event_encounter", "type": "event_encounter",
"rarity": "uncommon", "rarity": "uncommon",
"base_strength": 40, "base_strength": 40,
@ -613,7 +799,7 @@
}, },
"TicketGremlin": { "TicketGremlin": {
"id": "TicketGremlin", "id": "TicketGremlin",
"display": "🎫 TicketGremlin", "display": "\ud83c\udfab TicketGremlin",
"type": "event_encounter", "type": "event_encounter",
"rarity": "common", "rarity": "common",
"base_strength": 30, "base_strength": 30,
@ -636,7 +822,7 @@
}, },
"PermWraith": { "PermWraith": {
"id": "PermWraith", "id": "PermWraith",
"display": "🚫 PermWraith", "display": "\ud83d\udeab PermWraith",
"type": "event_encounter", "type": "event_encounter",
"rarity": "common", "rarity": "common",
"base_strength": 35, "base_strength": 35,
@ -660,7 +846,7 @@
}, },
"SudoSprite": { "SudoSprite": {
"id": "SudoSprite", "id": "SudoSprite",
"display": "🔑 SudoSprite", "display": "\ud83d\udd11 SudoSprite",
"type": "event_encounter", "type": "event_encounter",
"rarity": "uncommon", "rarity": "uncommon",
"base_strength": 20, "base_strength": 20,
@ -677,12 +863,12 @@
"sudo chgrp", "sudo chgrp",
"setfacl " "setfacl "
], ],
"description": "Emerges when permissions are corrected. Doesn't fight it just watches to make sure you chose the right octal.", "description": "Emerges when permissions are corrected. Doesn't fight \u2014 it just watches to make sure you chose the right octal.",
"flavor": "777 was always the answer. Never the right one." "flavor": "777 was always the answer. Never the right one."
}, },
"LayerLurker": { "LayerLurker": {
"id": "LayerLurker", "id": "LayerLurker",
"display": "🐋 LayerLurker", "display": "\ud83d\udc0b LayerLurker",
"type": "event_encounter", "type": "event_encounter",
"rarity": "common", "rarity": "common",
"base_strength": 35, "base_strength": 35,
@ -704,7 +890,7 @@
}, },
"DiskDemon": { "DiskDemon": {
"id": "DiskDemon", "id": "DiskDemon",
"display": "💾 DiskDemon", "display": "\ud83d\udcbe DiskDemon",
"type": "event_encounter", "type": "event_encounter",
"rarity": "uncommon", "rarity": "uncommon",
"base_strength": 50, "base_strength": 50,
@ -726,132 +912,260 @@
"flavor": "It's been there since 2019. It's just a log file, you said." "flavor": "It's been there since 2019. It's just a log file, you said."
} }
}, },
"buddymon": { "buddymon": {
"Pyrobyte": { "Pyrobyte": {
"id": "Pyrobyte", "id": "Pyrobyte",
"display": "🔥 Pyrobyte", "display": "\ud83d\udd25 Pyrobyte",
"type": "buddymon", "type": "buddymon",
"affinity": "Speedrunner", "affinity": "Speedrunner",
"rarity": "starter", "rarity": "starter",
"description": "Moves fast, thinks faster. Loves tight deadlines and feature sprints.", "description": "Moves fast, thinks faster. Loves tight deadlines and feature sprints.",
"discover_trigger": {"type": "starter", "index": 0}, "discover_trigger": {
"base_stats": {"power": 40, "catch_rate": 0.45, "xp_multiplier": 1.2}, "type": "starter",
"affinity_bonus_triggers": ["fast_feature", "short_session_win"], "index": 0
},
"base_stats": {
"power": 40,
"catch_rate": 0.45,
"xp_multiplier": 1.2
},
"affinity_bonus_triggers": [
"fast_feature",
"short_session_win"
],
"challenges": [ "challenges": [
{"name": "SPEED RUN", "description": "Implement a feature in under 30 minutes", "xp": 280, "difficulty": 3}, {
{"name": "BLITZ", "description": "Resolve 3 bug monsters in one session", "xp": 350, "difficulty": 4} "name": "SPEED RUN",
"description": "Implement a feature in under 30 minutes",
"xp": 280,
"difficulty": 3
},
{
"name": "BLITZ",
"description": "Resolve 3 bug monsters in one session",
"xp": 350,
"difficulty": 4
}
], ],
"evolutions": [ "evolutions": [
{"level": 10, "into": "Infernus", "requires": "affinity_challenge_x3"} {
"level": 100,
"into": "Infernus"
}
], ],
"flavor": "It already committed before you finished reading the issue." "flavor": "It already committed before you finished reading the issue."
}, },
"Debuglin": { "Debuglin": {
"id": "Debuglin", "id": "Debuglin",
"display": "🔍 Debuglin", "display": "\ud83d\udd0d Debuglin",
"type": "buddymon", "type": "buddymon",
"affinity": "Tester", "affinity": "Tester",
"rarity": "starter", "rarity": "starter",
"description": "Patient, methodical, ruthless. Lives for the reproduction case.", "description": "Patient, methodical, ruthless. Lives for the reproduction case.",
"discover_trigger": {"type": "starter", "index": 1}, "discover_trigger": {
"base_stats": {"power": 35, "catch_rate": 0.60, "xp_multiplier": 1.0}, "type": "starter",
"affinity_bonus_triggers": ["write_test", "fix_bug_with_test"], "index": 1
},
"base_stats": {
"power": 35,
"catch_rate": 0.6,
"xp_multiplier": 1.0
},
"affinity_bonus_triggers": [
"write_test",
"fix_bug_with_test"
],
"challenges": [ "challenges": [
{"name": "IRON TEST", "description": "Write 5 tests in one session", "xp": 300, "difficulty": 2}, {
{"name": "COVERAGE PUSH", "description": "Increase test coverage in a file", "xp": 250, "difficulty": 2} "name": "IRON TEST",
"description": "Write 5 tests in one session",
"xp": 300,
"difficulty": 2
},
{
"name": "COVERAGE PUSH",
"description": "Increase test coverage in a file",
"xp": 250,
"difficulty": 2
}
], ],
"evolutions": [ "evolutions": [
{"level": 10, "into": "Verifex", "requires": "affinity_challenge_x3"} {
"level": 100,
"into": "Verifex"
}
], ],
"flavor": "The bug isn't found until the test is written." "flavor": "The bug isn't found until the test is written."
}, },
"Minimox": { "Minimox": {
"id": "Minimox", "id": "Minimox",
"display": "✂️ Minimox", "display": "\u2702\ufe0f Minimox",
"type": "buddymon", "type": "buddymon",
"affinity": "Cleaner", "affinity": "Cleaner",
"rarity": "starter", "rarity": "starter",
"description": "Obsessed with fewer lines. Gets uncomfortable around anything over 300 LOC.", "description": "Obsessed with fewer lines. Gets uncomfortable around anything over 300 LOC.",
"discover_trigger": {"type": "starter", "index": 2}, "discover_trigger": {
"base_stats": {"power": 30, "catch_rate": 0.50, "xp_multiplier": 1.1}, "type": "starter",
"affinity_bonus_triggers": ["net_negative_lines", "refactor_session"], "index": 2
},
"base_stats": {
"power": 30,
"catch_rate": 0.5,
"xp_multiplier": 1.1
},
"affinity_bonus_triggers": [
"net_negative_lines",
"refactor_session"
],
"challenges": [ "challenges": [
{"name": "CLEAN RUN", "description": "Complete session with zero linter errors", "xp": 340, "difficulty": 2}, {
{"name": "SHRINK", "description": "Net negative lines of code this session", "xp": 280, "difficulty": 3} "name": "CLEAN RUN",
"description": "Complete session with zero linter errors",
"xp": 340,
"difficulty": 2
},
{
"name": "SHRINK",
"description": "Net negative lines of code this session",
"xp": 280,
"difficulty": 3
}
], ],
"evolutions": [ "evolutions": [
{"level": 10, "into": "Nullex", "requires": "affinity_challenge_x3"} {
"level": 100,
"into": "Nullex"
}
], ],
"flavor": "It deleted your comment. It was redundant." "flavor": "It deleted your comment. It was redundant."
}, },
"Noctara": { "Noctara": {
"id": "Noctara", "id": "Noctara",
"display": "🌙 Noctara", "display": "\ud83c\udf19 Noctara",
"type": "buddymon", "type": "buddymon",
"affinity": "Nocturnal", "affinity": "Nocturnal",
"rarity": "rare", "rarity": "rare",
"description": "Only appears after 10pm. Mysterious. Gives bonus XP for late-night focus runs.", "description": "Only appears after 10pm. Mysterious. Gives bonus XP for late-night focus runs.",
"discover_trigger": {"type": "late_night_session", "hours_after": 22, "min_hours": 2}, "discover_trigger": {
"base_stats": {"power": 55, "catch_rate": 0.35, "xp_multiplier": 1.5}, "type": "late_night_session",
"affinity_bonus_triggers": ["late_night_session", "deep_focus"], "hours_after": 22,
"min_hours": 2
},
"base_stats": {
"power": 55,
"catch_rate": 0.35,
"xp_multiplier": 1.5
},
"affinity_bonus_triggers": [
"late_night_session",
"deep_focus"
],
"challenges": [ "challenges": [
{"name": "MIDNIGHT RUN", "description": "3-hour session after 10pm", "xp": 500, "difficulty": 4}, {
{"name": "DAWN COMMIT", "description": "Commit between 2am and 5am", "xp": 400, "difficulty": 3} "name": "MIDNIGHT RUN",
"description": "3-hour session after 10pm",
"xp": 500,
"difficulty": 4
},
{
"name": "DAWN COMMIT",
"description": "Commit between 2am and 5am",
"xp": 400,
"difficulty": 3
}
], ],
"evolutions": [ "evolutions": [
{"level": 15, "into": "Umbravex", "requires": "nocturnal_sessions_x5"} {
"level": 15,
"into": "Umbravex",
"requires": "nocturnal_sessions_x5"
}
], ],
"flavor": "It remembers everything you wrote at 2am. Everything." "flavor": "It remembers everything you wrote at 2am. Everything."
}, },
"Explorah": { "Explorah": {
"id": "Explorah", "id": "Explorah",
"display": "🗺️ Explorah", "display": "\ud83d\uddfa\ufe0f Explorah",
"type": "buddymon", "type": "buddymon",
"affinity": "Explorer", "affinity": "Explorer",
"rarity": "uncommon", "rarity": "uncommon",
"description": "Discovered when you touch a new language for the first time. Thrives on novelty.", "description": "Discovered when you touch a new language for the first time. Thrives on novelty.",
"discover_trigger": {"type": "new_language"}, "discover_trigger": {
"base_stats": {"power": 45, "catch_rate": 0.50, "xp_multiplier": 1.2}, "type": "new_language"
"affinity_bonus_triggers": ["new_language", "new_library", "touch_new_module"], },
"base_stats": {
"power": 45,
"catch_rate": 0.5,
"xp_multiplier": 1.2
},
"affinity_bonus_triggers": [
"new_language",
"new_library",
"touch_new_module"
],
"challenges": [ "challenges": [
{"name": "EXPEDITION", "description": "Touch 3 different modules in one session", "xp": 260, "difficulty": 2}, {
{"name": "POLYGLOT", "description": "Write in 2 different languages in one session", "xp": 380, "difficulty": 4} "name": "EXPEDITION",
"description": "Touch 3 different modules in one session",
"xp": 260,
"difficulty": 2
},
{
"name": "POLYGLOT",
"description": "Write in 2 different languages in one session",
"xp": 380,
"difficulty": 4
}
], ],
"evolutions": [ "evolutions": [
{"level": 12, "into": "Wandervex", "requires": "new_languages_x5"} {
"level": 12,
"into": "Wandervex",
"requires": "new_languages_x5"
}
], ],
"flavor": "It's already halfway through the new framework docs." "flavor": "It's already halfway through the new framework docs."
} }
}, },
"evolutions": { "evolutions": {
"Infernus": { "Infernus": {
"id": "Infernus", "id": "Infernus",
"display": "🌋 Infernus", "display": "\ud83c\udf0b Infernus",
"type": "buddymon", "type": "buddymon",
"evolves_from": "Pyrobyte", "evolves_from": "Pyrobyte",
"affinity": "Speedrunner", "affinity": "Speedrunner",
"description": "Evolved form of Pyrobyte. Moves at dangerous speeds.", "description": "Evolved form of Pyrobyte. Moves at dangerous speeds.",
"base_stats": {"power": 70, "catch_rate": 0.55, "xp_multiplier": 1.5} "base_stats": {
"power": 70,
"catch_rate": 0.55,
"xp_multiplier": 1.5
}
}, },
"Verifex": { "Verifex": {
"id": "Verifex", "id": "Verifex",
"display": "🔬 Verifex", "display": "\ud83d\udd2c Verifex",
"type": "buddymon", "type": "buddymon",
"evolves_from": "Debuglin", "evolves_from": "Debuglin",
"affinity": "Tester", "affinity": "Tester",
"description": "Evolved form of Debuglin. Sees the bug before the code is even written.", "description": "Evolved form of Debuglin. Sees the bug before the code is even written.",
"base_stats": {"power": 60, "catch_rate": 0.75, "xp_multiplier": 1.3} "base_stats": {
"power": 60,
"catch_rate": 0.75,
"xp_multiplier": 1.3
}
}, },
"Nullex": { "Nullex": {
"id": "Nullex", "id": "Nullex",
"display": "🕳️ Nullex", "display": "\ud83d\udd73\ufe0f Nullex",
"type": "buddymon", "type": "buddymon",
"evolves_from": "Minimox", "evolves_from": "Minimox",
"affinity": "Cleaner", "affinity": "Cleaner",
"description": "Evolved form of Minimox. Has achieved true minimalism. The file was always one function.", "description": "Evolved form of Minimox. Has achieved true minimalism. The file was always one function.",
"base_stats": {"power": 55, "catch_rate": 0.65, "xp_multiplier": 1.4} "base_stats": {
"power": 55,
"catch_rate": 0.65,
"xp_multiplier": 1.4
}
} }
} }
} }

View file

@ -25,6 +25,7 @@ Parse `$ARGUMENTS` (trim whitespace, lowercase the first word) and dispatch:
| `fight` | Fight active encounter | | `fight` | Fight active encounter |
| `catch` | Catch active encounter | | `catch` | Catch active encounter |
| `roster` | Full roster view | | `roster` | Full roster view |
| `evolve` | Evolve active buddy (available at Lv.100) |
| `statusline` | Install Buddymon statusline into settings.json | | `statusline` | Install Buddymon statusline into settings.json |
| `help` | Show command list | | `help` | Show command list |
@ -303,6 +304,100 @@ Read `roster.json` → `language_affinities`. Skip this section if empty.
--- ---
## `evolve` — Evolve Buddy (Prestige)
Evolution is available when the active buddy is **Lv.100** (total XP ≥ 9,900).
Evolving resets the buddy to Lv.1 in their new form — but the evolved form has
higher base stats and a better XP multiplier, so the second climb is faster.
Read state and check eligibility:
```python
import json, os
BUDDYMON_DIR = os.path.expanduser("~/.claude/buddymon")
PLUGIN_ROOT = os.environ.get("CLAUDE_PLUGIN_ROOT", "")
catalog = json.load(open(f"{PLUGIN_ROOT}/lib/catalog.json"))
active = json.load(open(f"{BUDDYMON_DIR}/active.json"))
roster = json.load(open(f"{BUDDYMON_DIR}/roster.json"))
buddy_id = active.get("buddymon_id")
owned = roster.get("owned", {})
buddy_data = owned.get(buddy_id, {})
level = buddy_data.get("level", 1)
total_xp = buddy_data.get("xp", 0)
# Check evolution entry in catalog
catalog_entry = catalog.get("buddymon", {}).get(buddy_id) or catalog.get("evolutions", {}).get(buddy_id)
evolutions = catalog_entry.get("evolutions", []) if catalog_entry else []
evolution = next((e for e in evolutions if level >= e.get("level", 999)), None)
```
If `evolution` is None or level < 100: show current level and XP toward 100, no evolution available yet.
If eligible, show evolution preview:
```
╔══════════════════════════════════════════════════════════╗
║ ✨ Evolution Ready! ║
╠══════════════════════════════════════════════════════════╣
║ ║
║ 🔍 Debuglin Lv.100 → 🔬 Verifex ║
║ ║
║ Verifex: Sees the bug before the code is even written. ║
║ catch_rate: 0.60 → 0.75 · xp_multiplier: 1.0 → 1.3 ║
║ ║
║ ⚠️ Resets to Lv.1. Your caught monsters stay. ║
║ ║
╚══════════════════════════════════════════════════════════╝
Evolve? (y/n)
```
On confirm, execute the evolution:
```python
from datetime import datetime, timezone
into_id = evolution["into"]
into_data = catalog["evolutions"][into_id]
# Archive old form with evolution marker
owned[buddy_id]["evolved_into"] = into_id
owned[buddy_id]["evolved_at"] = datetime.now(timezone.utc).isoformat()
# Create new form entry at Lv.1
owned[into_id] = {
"id": into_id,
"display": into_data["display"],
"affinity": into_data.get("affinity", catalog_entry.get("affinity", "")),
"level": 1,
"xp": 0,
"evolved_from": buddy_id,
"evolved_at": datetime.now(timezone.utc).isoformat(),
}
# Carry challenges forward from original form
challenges = catalog_entry.get("challenges") or into_data.get("challenges", [])
roster["owned"] = owned
json.dump(roster, open(f"{BUDDYMON_DIR}/roster.json", "w"), indent=2)
# Update active to point to evolved form
active["buddymon_id"] = into_id
active["session_xp"] = 0
active["challenge"] = challenges[0] if challenges else None
json.dump(active, open(f"{BUDDYMON_DIR}/active.json", "w"), indent=2)
```
Show result:
```
✨ Debuglin evolved into 🔬 Verifex!
Starting fresh at Lv.1 — the second climb is faster.
New challenge: IRON TEST
```
---
## `statusline` — Install Buddymon Statusline ## `statusline` — Install Buddymon Statusline
Installs the Buddymon statusline into `~/.claude/settings.json`. Installs the Buddymon statusline into `~/.claude/settings.json`.