@umang-boss/claudemon 1.0.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 (203) hide show
  1. package/README.md +164 -0
  2. package/bin/claudemon.js +52 -0
  3. package/bunfig.toml +2 -0
  4. package/cli/doctor.ts +334 -0
  5. package/cli/index.ts +42 -0
  6. package/cli/install.ts +248 -0
  7. package/cli/shared.ts +102 -0
  8. package/cli/uninstall.ts +155 -0
  9. package/cli/update.ts +318 -0
  10. package/hooks/post-tool-use.sh +127 -0
  11. package/hooks/stop.sh +49 -0
  12. package/hooks/user-prompt-submit.sh +73 -0
  13. package/package.json +68 -0
  14. package/scripts/download-colorscripts.ts +311 -0
  15. package/skills/buddy/SKILL.md +47 -0
  16. package/sprites/colorscripts/small/1-bulbasaur.txt +11 -0
  17. package/sprites/colorscripts/small/10-caterpie.txt +9 -0
  18. package/sprites/colorscripts/small/100-voltorb.txt +8 -0
  19. package/sprites/colorscripts/small/101-electrode.txt +9 -0
  20. package/sprites/colorscripts/small/102-exeggcute.txt +10 -0
  21. package/sprites/colorscripts/small/103-exeggutor.txt +23 -0
  22. package/sprites/colorscripts/small/104-cubone.txt +11 -0
  23. package/sprites/colorscripts/small/105-marowak.txt +16 -0
  24. package/sprites/colorscripts/small/106-hitmonlee.txt +16 -0
  25. package/sprites/colorscripts/small/107-hitmonchan.txt +19 -0
  26. package/sprites/colorscripts/small/108-lickitung.txt +10 -0
  27. package/sprites/colorscripts/small/109-koffing.txt +14 -0
  28. package/sprites/colorscripts/small/11-metapod.txt +10 -0
  29. package/sprites/colorscripts/small/110-weezing.txt +23 -0
  30. package/sprites/colorscripts/small/111-rhyhorn.txt +11 -0
  31. package/sprites/colorscripts/small/112-rhydon.txt +20 -0
  32. package/sprites/colorscripts/small/113-chansey.txt +11 -0
  33. package/sprites/colorscripts/small/114-tangela.txt +10 -0
  34. package/sprites/colorscripts/small/115-kangaskhan.txt +18 -0
  35. package/sprites/colorscripts/small/116-horsea.txt +10 -0
  36. package/sprites/colorscripts/small/117-seadra.txt +11 -0
  37. package/sprites/colorscripts/small/118-goldeen.txt +11 -0
  38. package/sprites/colorscripts/small/119-seaking.txt +16 -0
  39. package/sprites/colorscripts/small/12-butterfree.txt +20 -0
  40. package/sprites/colorscripts/small/120-staryu.txt +10 -0
  41. package/sprites/colorscripts/small/121-starmie.txt +17 -0
  42. package/sprites/colorscripts/small/122-mr-mime.txt +18 -0
  43. package/sprites/colorscripts/small/123-scyther.txt +21 -0
  44. package/sprites/colorscripts/small/124-jynx.txt +18 -0
  45. package/sprites/colorscripts/small/125-electabuzz.txt +19 -0
  46. package/sprites/colorscripts/small/126-magmar.txt +19 -0
  47. package/sprites/colorscripts/small/127-pinsir.txt +19 -0
  48. package/sprites/colorscripts/small/128-tauros.txt +20 -0
  49. package/sprites/colorscripts/small/129-magikarp.txt +13 -0
  50. package/sprites/colorscripts/small/13-weedle.txt +10 -0
  51. package/sprites/colorscripts/small/130-gyarados.txt +21 -0
  52. package/sprites/colorscripts/small/131-lapras.txt +19 -0
  53. package/sprites/colorscripts/small/132-ditto.txt +8 -0
  54. package/sprites/colorscripts/small/133-eevee.txt +10 -0
  55. package/sprites/colorscripts/small/134-vaporeon.txt +16 -0
  56. package/sprites/colorscripts/small/135-jolteon.txt +17 -0
  57. package/sprites/colorscripts/small/136-flareon.txt +18 -0
  58. package/sprites/colorscripts/small/137-porygon.txt +10 -0
  59. package/sprites/colorscripts/small/138-omanyte.txt +10 -0
  60. package/sprites/colorscripts/small/139-omastar.txt +18 -0
  61. package/sprites/colorscripts/small/14-kakuna.txt +10 -0
  62. package/sprites/colorscripts/small/140-kabuto.txt +8 -0
  63. package/sprites/colorscripts/small/141-kabutops.txt +17 -0
  64. package/sprites/colorscripts/small/142-aerodactyl.txt +17 -0
  65. package/sprites/colorscripts/small/143-snorlax.txt +21 -0
  66. package/sprites/colorscripts/small/144-articuno.txt +24 -0
  67. package/sprites/colorscripts/small/145-zapdos.txt +20 -0
  68. package/sprites/colorscripts/small/146-moltres.txt +23 -0
  69. package/sprites/colorscripts/small/147-dratini.txt +10 -0
  70. package/sprites/colorscripts/small/148-dragonair.txt +12 -0
  71. package/sprites/colorscripts/small/149-dragonite.txt +21 -0
  72. package/sprites/colorscripts/small/15-beedrill.txt +13 -0
  73. package/sprites/colorscripts/small/150-mewtwo.txt +22 -0
  74. package/sprites/colorscripts/small/151-mew.txt +14 -0
  75. package/sprites/colorscripts/small/16-pidgey.txt +10 -0
  76. package/sprites/colorscripts/small/17-pidgeotto.txt +11 -0
  77. package/sprites/colorscripts/small/18-pidgeot.txt +18 -0
  78. package/sprites/colorscripts/small/19-rattata.txt +12 -0
  79. package/sprites/colorscripts/small/2-ivysaur.txt +11 -0
  80. package/sprites/colorscripts/small/20-raticate.txt +12 -0
  81. package/sprites/colorscripts/small/21-spearow.txt +9 -0
  82. package/sprites/colorscripts/small/22-fearow.txt +12 -0
  83. package/sprites/colorscripts/small/23-ekans.txt +12 -0
  84. package/sprites/colorscripts/small/24-arbok.txt +16 -0
  85. package/sprites/colorscripts/small/25-pikachu.txt +11 -0
  86. package/sprites/colorscripts/small/26-raichu.txt +19 -0
  87. package/sprites/colorscripts/small/27-sandshrew.txt +10 -0
  88. package/sprites/colorscripts/small/28-sandslash.txt +16 -0
  89. package/sprites/colorscripts/small/29-nidoran-f.txt +11 -0
  90. package/sprites/colorscripts/small/3-venusaur.txt +21 -0
  91. package/sprites/colorscripts/small/30-nidorina.txt +12 -0
  92. package/sprites/colorscripts/small/31-nidoqueen.txt +19 -0
  93. package/sprites/colorscripts/small/32-nidoran-m.txt +11 -0
  94. package/sprites/colorscripts/small/33-nidorino.txt +12 -0
  95. package/sprites/colorscripts/small/34-nidoking.txt +18 -0
  96. package/sprites/colorscripts/small/35-clefairy.txt +11 -0
  97. package/sprites/colorscripts/small/36-clefable.txt +17 -0
  98. package/sprites/colorscripts/small/37-vulpix.txt +11 -0
  99. package/sprites/colorscripts/small/38-ninetales.txt +18 -0
  100. package/sprites/colorscripts/small/39-jigglypuff.txt +11 -0
  101. package/sprites/colorscripts/small/4-charmander.txt +11 -0
  102. package/sprites/colorscripts/small/40-wigglytuff.txt +20 -0
  103. package/sprites/colorscripts/small/41-zubat.txt +11 -0
  104. package/sprites/colorscripts/small/42-golbat.txt +18 -0
  105. package/sprites/colorscripts/small/43-oddish.txt +11 -0
  106. package/sprites/colorscripts/small/44-gloom.txt +12 -0
  107. package/sprites/colorscripts/small/45-vileplume.txt +17 -0
  108. package/sprites/colorscripts/small/46-paras.txt +11 -0
  109. package/sprites/colorscripts/small/47-parasect.txt +12 -0
  110. package/sprites/colorscripts/small/48-venonat.txt +14 -0
  111. package/sprites/colorscripts/small/49-venomoth.txt +19 -0
  112. package/sprites/colorscripts/small/5-charmeleon.txt +13 -0
  113. package/sprites/colorscripts/small/50-diglett.txt +8 -0
  114. package/sprites/colorscripts/small/51-dugtrio.txt +18 -0
  115. package/sprites/colorscripts/small/52-meowth.txt +12 -0
  116. package/sprites/colorscripts/small/53-persian.txt +20 -0
  117. package/sprites/colorscripts/small/54-psyduck.txt +12 -0
  118. package/sprites/colorscripts/small/55-golduck.txt +17 -0
  119. package/sprites/colorscripts/small/56-mankey.txt +11 -0
  120. package/sprites/colorscripts/small/57-primeape.txt +13 -0
  121. package/sprites/colorscripts/small/58-growlithe.txt +12 -0
  122. package/sprites/colorscripts/small/59-arcanine.txt +20 -0
  123. package/sprites/colorscripts/small/6-charizard.txt +21 -0
  124. package/sprites/colorscripts/small/60-poliwag.txt +9 -0
  125. package/sprites/colorscripts/small/61-poliwhirl.txt +11 -0
  126. package/sprites/colorscripts/small/62-poliwrath.txt +17 -0
  127. package/sprites/colorscripts/small/63-abra.txt +12 -0
  128. package/sprites/colorscripts/small/64-kadabra.txt +14 -0
  129. package/sprites/colorscripts/small/65-alakazam.txt +19 -0
  130. package/sprites/colorscripts/small/66-machop.txt +11 -0
  131. package/sprites/colorscripts/small/67-machoke.txt +12 -0
  132. package/sprites/colorscripts/small/68-machamp.txt +19 -0
  133. package/sprites/colorscripts/small/69-bellsprout.txt +9 -0
  134. package/sprites/colorscripts/small/7-squirtle.txt +10 -0
  135. package/sprites/colorscripts/small/70-weepinbell.txt +11 -0
  136. package/sprites/colorscripts/small/71-victreebel.txt +17 -0
  137. package/sprites/colorscripts/small/72-tentacool.txt +12 -0
  138. package/sprites/colorscripts/small/73-tentacruel.txt +20 -0
  139. package/sprites/colorscripts/small/74-geodude.txt +9 -0
  140. package/sprites/colorscripts/small/75-graveler.txt +12 -0
  141. package/sprites/colorscripts/small/76-golem.txt +18 -0
  142. package/sprites/colorscripts/small/77-ponyta.txt +13 -0
  143. package/sprites/colorscripts/small/78-rapidash.txt +18 -0
  144. package/sprites/colorscripts/small/79-slowpoke.txt +12 -0
  145. package/sprites/colorscripts/small/8-wartortle.txt +12 -0
  146. package/sprites/colorscripts/small/80-slowbro.txt +18 -0
  147. package/sprites/colorscripts/small/81-magnemite.txt +9 -0
  148. package/sprites/colorscripts/small/82-magneton.txt +18 -0
  149. package/sprites/colorscripts/small/83-farfetchd.txt +12 -0
  150. package/sprites/colorscripts/small/84-doduo.txt +10 -0
  151. package/sprites/colorscripts/small/85-dodrio.txt +17 -0
  152. package/sprites/colorscripts/small/86-seel.txt +13 -0
  153. package/sprites/colorscripts/small/87-dewgong.txt +20 -0
  154. package/sprites/colorscripts/small/88-grimer.txt +10 -0
  155. package/sprites/colorscripts/small/89-muk.txt +14 -0
  156. package/sprites/colorscripts/small/9-blastoise.txt +20 -0
  157. package/sprites/colorscripts/small/90-shellder.txt +10 -0
  158. package/sprites/colorscripts/small/91-cloyster.txt +18 -0
  159. package/sprites/colorscripts/small/92-gastly.txt +12 -0
  160. package/sprites/colorscripts/small/93-haunter.txt +14 -0
  161. package/sprites/colorscripts/small/94-gengar.txt +19 -0
  162. package/sprites/colorscripts/small/95-onix.txt +22 -0
  163. package/sprites/colorscripts/small/96-drowzee.txt +12 -0
  164. package/sprites/colorscripts/small/97-hypno.txt +19 -0
  165. package/sprites/colorscripts/small/98-krabby.txt +12 -0
  166. package/sprites/colorscripts/small/99-kingler.txt +20 -0
  167. package/src/engine/constants.ts +121 -0
  168. package/src/engine/encounter-pool.ts +71 -0
  169. package/src/engine/encounters.ts +308 -0
  170. package/src/engine/evolution-data.ts +535 -0
  171. package/src/engine/evolution.ts +310 -0
  172. package/src/engine/pokemon-data.ts +1838 -0
  173. package/src/engine/reactions.ts +877 -0
  174. package/src/engine/starter-pool.ts +47 -0
  175. package/src/engine/stats.ts +97 -0
  176. package/src/engine/types.ts +312 -0
  177. package/src/engine/xp.ts +135 -0
  178. package/src/gamification/achievements.ts +204 -0
  179. package/src/gamification/legendary-quests.ts +265 -0
  180. package/src/gamification/milestones.ts +86 -0
  181. package/src/hooks/award-xp.ts +131 -0
  182. package/src/hooks/increment-counter.ts +27 -0
  183. package/src/server/index.ts +78 -0
  184. package/src/server/instructions.ts +194 -0
  185. package/src/server/tools/achievements.ts +118 -0
  186. package/src/server/tools/catch.ts +295 -0
  187. package/src/server/tools/display-helpers.ts +35 -0
  188. package/src/server/tools/evolve.ts +236 -0
  189. package/src/server/tools/legendary.ts +78 -0
  190. package/src/server/tools/party.ts +251 -0
  191. package/src/server/tools/pet.ts +124 -0
  192. package/src/server/tools/pokedex.ts +286 -0
  193. package/src/server/tools/rename.ts +63 -0
  194. package/src/server/tools/show.ts +136 -0
  195. package/src/server/tools/starter.ts +175 -0
  196. package/src/server/tools/stats.ts +123 -0
  197. package/src/server/tools/visibility.ts +65 -0
  198. package/src/sprites/index.ts +45 -0
  199. package/src/state/io.ts +91 -0
  200. package/src/state/schemas.ts +131 -0
  201. package/src/state/state-manager.ts +321 -0
  202. package/statusline/buddy-status.sh +233 -0
  203. package/tsconfig.json +37 -0
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Starter pool — base-stage common Pokemon eligible for random
3
+ * starter selection. Only first-in-chain, common-rarity Pokemon.
4
+ */
5
+
6
+ /** Pokemon IDs eligible for random starter selection */
7
+ export const STARTER_POOL: readonly number[] = [
8
+ 10, // Caterpie
9
+ 13, // Weedle
10
+ 16, // Pidgey
11
+ 19, // Rattata
12
+ 21, // Spearow
13
+ 23, // Ekans
14
+ 27, // Sandshrew
15
+ 29, // Nidoran♀
16
+ 32, // Nidoran♂
17
+ 39, // Jigglypuff
18
+ 41, // Zubat
19
+ 43, // Oddish
20
+ 46, // Paras
21
+ 48, // Venonat
22
+ 50, // Diglett
23
+ 52, // Meowth
24
+ 54, // Psyduck
25
+ 56, // Mankey
26
+ 60, // Poliwag
27
+ 66, // Machop
28
+ 69, // Bellsprout
29
+ 72, // Tentacool
30
+ 74, // Geodude
31
+ 77, // Ponyta
32
+ 79, // Slowpoke
33
+ 81, // Magnemite
34
+ 84, // Doduo
35
+ 86, // Seel
36
+ 88, // Grimer
37
+ 90, // Shellder
38
+ 92, // Gastly
39
+ 96, // Drowzee
40
+ 98, // Krabby
41
+ 100, // Voltorb
42
+ 104, // Cubone
43
+ 109, // Koffing
44
+ 116, // Horsea
45
+ 118, // Goldeen
46
+ 129, // Magikarp
47
+ ] as const;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Coding stat system for Claudemon.
3
+ * Maps Gen 1 base stats to coding-themed stats with display utilities.
4
+ */
5
+
6
+ import type { BaseStats, CodingStats, CodingStat, OwnedPokemon } from "./types.js";
7
+ import { BASE_STAT_TO_CODING } from "./types.js";
8
+ import { TRAINER_TITLES } from "./constants.js";
9
+
10
+ /** Full block for filled portion of stat bar */
11
+ const BLOCK_FILLED = "\u2588";
12
+
13
+ /** Light shade block for empty portion of stat bar */
14
+ const BLOCK_EMPTY = "\u2591";
15
+
16
+ /**
17
+ * Create initial coding stats from a pokemon's base stats.
18
+ * Scaled to ~50% of base value since starters begin at level 5.
19
+ * @param baseStats - The species' base stat block
20
+ * @returns Initial coding stats for a newly caught pokemon
21
+ */
22
+ export function initCodingStats(baseStats: BaseStats): CodingStats {
23
+ const stats: CodingStats = {
24
+ stamina: 0,
25
+ debugging: 0,
26
+ stability: 0,
27
+ velocity: 0,
28
+ wisdom: 0,
29
+ };
30
+
31
+ for (const [baseStat, codingStat] of Object.entries(BASE_STAT_TO_CODING) as Array<
32
+ [keyof BaseStats, CodingStat]
33
+ >) {
34
+ stats[codingStat] = Math.floor(baseStats[baseStat] * 0.5);
35
+ }
36
+
37
+ return stats;
38
+ }
39
+
40
+ /**
41
+ * Calculate the effective display value of a coding stat.
42
+ * Formula: floor(baseStat * (0.5 + level/200)) + activityBonus
43
+ * @param baseStat - The species' base stat value
44
+ * @param level - The pokemon's current level
45
+ * @param activityBonus - Accumulated bonus from coding activity
46
+ * @returns The effective stat value for display
47
+ */
48
+ export function calculateDisplayStat(
49
+ baseStat: number,
50
+ level: number,
51
+ activityBonus: number,
52
+ ): number {
53
+ return Math.floor(baseStat * (0.5 + level / 200)) + activityBonus;
54
+ }
55
+
56
+ /**
57
+ * Increment a coding stat's activity bonus on an owned pokemon. Mutates in place.
58
+ * @param pokemon - The owned pokemon instance to boost
59
+ * @param stat - Which coding stat to boost
60
+ * @param amount - How much to add to the stat
61
+ */
62
+ export function applyStatBoost(pokemon: OwnedPokemon, stat: CodingStat, amount: number): void {
63
+ pokemon.codingStats[stat] += amount;
64
+ }
65
+
66
+ /**
67
+ * Render a Unicode progress bar for a stat value.
68
+ * @param value - Stat value (0-100 range)
69
+ * @param maxWidth - Character width of the bar (default 10)
70
+ * @returns Unicode bar string like "████████░░"
71
+ */
72
+ export function renderStatBar(value: number, maxWidth: number = 10): string {
73
+ const clamped = Math.max(0, Math.min(100, value));
74
+ const filled = Math.round((clamped / 100) * maxWidth);
75
+ const empty = maxWidth - filled;
76
+ return BLOCK_FILLED.repeat(filled) + BLOCK_EMPTY.repeat(empty);
77
+ }
78
+
79
+ /**
80
+ * Get the trainer title based on highest pokemon level.
81
+ * @param highestLevel - The highest level among the trainer's pokemon
82
+ * @returns The trainer's title string
83
+ */
84
+ export function getTrainerTitle(highestLevel: number): string {
85
+ // Titles are sorted ascending by minLevel; find the last one that qualifies
86
+ let title = "Bug Catcher"; // Fallback for level 0
87
+
88
+ for (const entry of TRAINER_TITLES) {
89
+ if (highestLevel >= entry.minLevel) {
90
+ title = entry.title;
91
+ } else {
92
+ break;
93
+ }
94
+ }
95
+
96
+ return title;
97
+ }
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Core type definitions for Claudemon.
3
+ * Single source of truth for all shared interfaces and types.
4
+ */
5
+
6
+ // ── Pokemon Types ──────────────────────────────────────────
7
+
8
+ export const POKEMON_TYPES = [
9
+ "Normal",
10
+ "Fire",
11
+ "Water",
12
+ "Electric",
13
+ "Grass",
14
+ "Ice",
15
+ "Fighting",
16
+ "Poison",
17
+ "Ground",
18
+ "Flying",
19
+ "Psychic",
20
+ "Bug",
21
+ "Rock",
22
+ "Ghost",
23
+ "Dragon",
24
+ ] as const;
25
+
26
+ export type PokemonType = (typeof POKEMON_TYPES)[number];
27
+
28
+ export const EXP_GROUPS = ["fast", "medium_fast", "medium_slow", "slow"] as const;
29
+
30
+ export type ExpGroup = (typeof EXP_GROUPS)[number];
31
+
32
+ export const RARITY_TIERS = ["common", "uncommon", "rare", "legendary", "mythical"] as const;
33
+
34
+ export type RarityTier = (typeof RARITY_TIERS)[number];
35
+
36
+ // ── Base Stats (Gen 1 layout) ──────────────────────────────
37
+
38
+ export interface BaseStats {
39
+ readonly hp: number;
40
+ readonly attack: number;
41
+ readonly defense: number;
42
+ readonly speed: number;
43
+ readonly special: number;
44
+ }
45
+
46
+ // ── Pokemon Species (static, immutable Pokedex entry) ──────
47
+
48
+ export interface Pokemon {
49
+ readonly id: number;
50
+ readonly name: string;
51
+ readonly types: readonly [PokemonType, PokemonType?];
52
+ readonly baseStats: BaseStats;
53
+ readonly expGroup: ExpGroup;
54
+ readonly evolutionChainId: number;
55
+ readonly rarity: RarityTier;
56
+ readonly catchRate: number; // 1-255, higher = easier to catch
57
+ readonly description: string; // Short flavor text
58
+ }
59
+
60
+ // ── Evolution ──────────────────────────────────────────────
61
+
62
+ export type EvolutionMethod =
63
+ | { readonly type: "level"; readonly level: number }
64
+ | { readonly type: "badge"; readonly badge: BadgeType }
65
+ | { readonly type: "collaboration" }
66
+ | { readonly type: "stat"; readonly stat: CodingStat; readonly minValue: number };
67
+
68
+ export interface EvolutionLink {
69
+ readonly from: number; // Pokemon ID
70
+ readonly to: number; // Pokemon ID
71
+ readonly method: EvolutionMethod;
72
+ }
73
+
74
+ export interface EvolutionChain {
75
+ readonly id: number;
76
+ readonly links: readonly EvolutionLink[];
77
+ }
78
+
79
+ // ── Coding Stats (mapped from Gen 1 stats) ─────────────────
80
+
81
+ export const CODING_STATS = ["stamina", "debugging", "stability", "velocity", "wisdom"] as const;
82
+
83
+ export type CodingStat = (typeof CODING_STATS)[number];
84
+
85
+ export interface CodingStats {
86
+ stamina: number; // HP → session endurance, streaks
87
+ debugging: number; // Attack → bug-finding, error fixing
88
+ stability: number; // Defense → test coverage, build reliability
89
+ velocity: number; // Speed → throughput, commits, edits
90
+ wisdom: number; // Special → deep problem-solving, refactors
91
+ }
92
+
93
+ /** Maps Gen 1 base stats to coding stats for initial values */
94
+ export const BASE_STAT_TO_CODING: Record<keyof BaseStats, CodingStat> = {
95
+ hp: "stamina",
96
+ attack: "debugging",
97
+ defense: "stability",
98
+ speed: "velocity",
99
+ special: "wisdom",
100
+ } as const;
101
+
102
+ // ── Badges (replace evolution stones) ──────────────────────
103
+
104
+ export const BADGE_TYPES = [
105
+ "blaze", // Fire Stone → 50 bugs fixed
106
+ "flow", // Water Stone → 100 tests passed
107
+ "spark", // Thunder Stone → 200 commits
108
+ "lunar", // Moon Stone → 30-day streak
109
+ "growth", // Leaf Stone → 500 files edited
110
+ ] as const;
111
+
112
+ export type BadgeType = (typeof BADGE_TYPES)[number];
113
+
114
+ export interface Badge {
115
+ readonly type: BadgeType;
116
+ readonly name: string;
117
+ readonly description: string;
118
+ readonly condition: BadgeCondition;
119
+ }
120
+
121
+ export type BadgeCondition =
122
+ | { readonly type: "counter"; readonly counter: EventCounterKey; readonly threshold: number }
123
+ | { readonly type: "streak"; readonly minDays: number };
124
+
125
+ // ── Owned Pokemon (user's individual Pokemon instance) ──────
126
+
127
+ export interface OwnedPokemon {
128
+ readonly id: string; // Unique instance ID (UUID)
129
+ pokemonId: number;
130
+ nickname: string | null;
131
+ level: number;
132
+ currentXp: number;
133
+ totalXp: number;
134
+ codingStats: CodingStats;
135
+ happiness: number; // 0-255
136
+ caughtAt: string; // ISO date
137
+ evolvedAt: string | null;
138
+ isActive: boolean;
139
+ personality: string | null;
140
+ shiny: boolean; // Future: always false in v1
141
+ isStarter: boolean;
142
+ }
143
+
144
+ // ── Player State (single source of truth) ──────────────────
145
+
146
+ export interface PlayerState {
147
+ readonly trainerId: string;
148
+ trainerName: string;
149
+ party: OwnedPokemon[]; // Max 6
150
+ pcBox: OwnedPokemon[]; // Overflow storage
151
+ pokedex: PokedexState;
152
+ badges: BadgeType[];
153
+ achievements: UnlockedAchievement[];
154
+ counters: EventCounters;
155
+ streak: StreakData;
156
+ config: BuddyConfig;
157
+ startedAt: string; // ISO date
158
+ totalXpEarned: number;
159
+ totalSessions: number;
160
+ pendingEncounter: WildEncounter | null;
161
+ xpSinceLastEncounter: number;
162
+ }
163
+
164
+ // ── Pokedex ────────────────────────────────────────────────
165
+
166
+ export interface PokedexEntry {
167
+ seen: boolean;
168
+ caught: boolean;
169
+ firstSeen: string | null; // ISO date
170
+ firstCaught: string | null;
171
+ }
172
+
173
+ export interface PokedexState {
174
+ entries: Record<number, PokedexEntry>; // Keyed by Pokedex number 1-151
175
+ totalSeen: number;
176
+ totalCaught: number;
177
+ }
178
+
179
+ // ── Achievements ───────────────────────────────────────────
180
+
181
+ export interface Achievement {
182
+ readonly id: string;
183
+ readonly name: string;
184
+ readonly description: string;
185
+ readonly category: "trainer" | "coding" | "pokemon" | "secret";
186
+ readonly condition: AchievementCondition;
187
+ }
188
+
189
+ export type AchievementCondition =
190
+ | { readonly type: "counter"; readonly counter: EventCounterKey; readonly threshold: number }
191
+ | { readonly type: "level"; readonly minLevel: number }
192
+ | { readonly type: "pokedex"; readonly minCaught: number }
193
+ | { readonly type: "streak"; readonly minDays: number }
194
+ | { readonly type: "badge"; readonly badge: BadgeType }
195
+ | { readonly type: "evolution" }
196
+ | { readonly type: "party_size"; readonly minSize: number };
197
+
198
+ export interface UnlockedAchievement {
199
+ readonly achievementId: string;
200
+ readonly unlockedAt: string; // ISO date
201
+ }
202
+
203
+ // ── Event Counters ─────────────────────────────────────────
204
+
205
+ export const EVENT_COUNTER_KEYS = [
206
+ "commits",
207
+ "tests_passed",
208
+ "tests_failed",
209
+ "tests_written",
210
+ "builds_succeeded",
211
+ "builds_failed",
212
+ "bugs_fixed",
213
+ "lint_fixes",
214
+ "files_created",
215
+ "files_edited",
216
+ "searches",
217
+ "large_refactors",
218
+ "errors_encountered",
219
+ "sessions",
220
+ "prs_merged",
221
+ ] as const;
222
+
223
+ export type EventCounterKey = (typeof EVENT_COUNTER_KEYS)[number];
224
+
225
+ export type EventCounters = Record<EventCounterKey, number>;
226
+
227
+ // ── Streaks ────────────────────────────────────────────────
228
+
229
+ export interface StreakData {
230
+ currentStreak: number;
231
+ longestStreak: number;
232
+ lastActiveDate: string | null; // ISO date "2026-04-13"
233
+ totalDaysActive: number;
234
+ }
235
+
236
+ // ── Config ─────────────────────────────────────────────────
237
+
238
+ export interface BuddyConfig {
239
+ muted: boolean;
240
+ reactionCooldownMs: number; // Default 30000
241
+ statusLineEnabled: boolean;
242
+ bellEnabled: boolean; // Terminal bell on level-up/encounters
243
+ }
244
+
245
+ // ── XP Events (what triggers XP awards) ────────────────────
246
+
247
+ export const XP_EVENT_TYPES = [
248
+ "commit",
249
+ "test_pass",
250
+ "test_written",
251
+ "build_success",
252
+ "bug_fix",
253
+ "lint_fix",
254
+ "file_create",
255
+ "file_edit",
256
+ "search",
257
+ "large_refactor",
258
+ "session_start",
259
+ "daily_streak",
260
+ "pet",
261
+ ] as const;
262
+
263
+ export type XpEventType = (typeof XP_EVENT_TYPES)[number];
264
+
265
+ export interface XpEvent {
266
+ readonly type: XpEventType;
267
+ readonly xp: number;
268
+ readonly statBoost: CodingStat | null;
269
+ readonly boostAmount: number;
270
+ }
271
+
272
+ // ── Encounters ─────────────────────────────────────────────
273
+
274
+ export interface WildEncounter {
275
+ readonly pokemonId: number;
276
+ readonly level: number;
277
+ readonly catchCondition: CatchCondition;
278
+ }
279
+
280
+ export interface CatchCondition {
281
+ readonly requiredStat: CodingStat | null;
282
+ readonly minStatValue: number;
283
+ readonly requiredLevel: number;
284
+ }
285
+
286
+ // ── Level Up Result ────────────────────────────────────────
287
+
288
+ export interface LevelUpResult {
289
+ readonly previousLevel: number;
290
+ readonly newLevel: number;
291
+ readonly pendingEvolution: EvolutionLink | null;
292
+ }
293
+
294
+ // ── Trainer Titles ─────────────────────────────────────────
295
+
296
+ export interface TrainerTitle {
297
+ readonly minLevel: number;
298
+ readonly title: string;
299
+ }
300
+
301
+ // ── Legendary Quest ────────────────────────────────────────
302
+
303
+ export interface LegendaryQuest {
304
+ readonly pokemonId: number;
305
+ readonly name: string;
306
+ readonly steps: readonly QuestStep[];
307
+ }
308
+
309
+ export interface QuestStep {
310
+ readonly description: string;
311
+ readonly condition: AchievementCondition;
312
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * XP and leveling engine for Claudemon.
3
+ * Implements Gen 1 experience growth rate formulas.
4
+ */
5
+
6
+ import type {
7
+ ExpGroup,
8
+ OwnedPokemon,
9
+ Pokemon,
10
+ LevelUpResult,
11
+ XpEventType,
12
+ XpEvent,
13
+ } from "./types.js";
14
+ import { MAX_LEVEL, XP_AWARDS } from "./constants.js";
15
+
16
+ /**
17
+ * Total cumulative XP required to reach a given level.
18
+ * @param level - Target level (1-100)
19
+ * @param group - Experience growth rate group
20
+ * @returns Cumulative XP needed to reach that level
21
+ */
22
+ export function cumulativeXpForLevel(level: number, group: ExpGroup): number {
23
+ const n = level;
24
+ const n3 = n * n * n;
25
+
26
+ switch (group) {
27
+ case "fast":
28
+ return Math.floor(0.8 * n3);
29
+ case "medium_fast":
30
+ return n3;
31
+ case "medium_slow": {
32
+ const raw = 1.2 * n3 - 15 * n * n + 100 * n - 140;
33
+ return Math.max(0, Math.floor(raw));
34
+ }
35
+ case "slow":
36
+ return Math.floor(1.25 * n3);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * XP delta needed to advance from currentLevel to currentLevel + 1.
42
+ * @param currentLevel - The pokemon's current level (1-99)
43
+ * @param group - Experience growth rate group
44
+ * @returns XP needed for the next level, or 0 if already at MAX_LEVEL
45
+ */
46
+ export function xpToNextLevel(currentLevel: number, group: ExpGroup): number {
47
+ if (currentLevel >= MAX_LEVEL) {
48
+ return 0;
49
+ }
50
+ return cumulativeXpForLevel(currentLevel + 1, group) - cumulativeXpForLevel(currentLevel, group);
51
+ }
52
+
53
+ /**
54
+ * Apply XP to an owned pokemon, handling level-ups (including multi-level).
55
+ * Mutates the pokemon's currentXp, totalXp, and level fields.
56
+ * @param pokemon - The owned pokemon instance to award XP to
57
+ * @param amount - XP amount to add
58
+ * @param pokemonData - The species data for this pokemon
59
+ * @returns Level-up result if the pokemon leveled up, or null
60
+ */
61
+ export function addXp(
62
+ pokemon: OwnedPokemon,
63
+ amount: number,
64
+ pokemonData: Pokemon,
65
+ ): LevelUpResult | null {
66
+ if (amount <= 0 || pokemon.level >= MAX_LEVEL) {
67
+ return null;
68
+ }
69
+
70
+ const previousLevel = pokemon.level;
71
+ const group = pokemonData.expGroup;
72
+
73
+ pokemon.totalXp += amount;
74
+ pokemon.currentXp += amount;
75
+
76
+ // Check for level-ups (possibly multiple from large XP gains)
77
+ while (pokemon.level < MAX_LEVEL) {
78
+ const xpNeeded = xpToNextLevel(pokemon.level, group);
79
+ if (xpNeeded <= 0 || pokemon.currentXp < xpNeeded) {
80
+ break;
81
+ }
82
+ pokemon.currentXp -= xpNeeded;
83
+ pokemon.level += 1;
84
+ }
85
+
86
+ // Clamp at max level: any overflow XP is discarded
87
+ if (pokemon.level >= MAX_LEVEL) {
88
+ pokemon.currentXp = 0;
89
+ }
90
+
91
+ if (pokemon.level === previousLevel) {
92
+ return null;
93
+ }
94
+
95
+ // TODO(phase-2): check evolution triggers and populate pendingEvolution
96
+ return {
97
+ previousLevel,
98
+ newLevel: pokemon.level,
99
+ pendingEvolution: null,
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Calculate progress percentage toward the next level.
105
+ * @param pokemon - The owned pokemon instance
106
+ * @param pokemonData - The species data for this pokemon
107
+ * @returns Percentage (0-100) of progress to next level
108
+ */
109
+ export function xpProgressPercent(pokemon: OwnedPokemon, pokemonData: Pokemon): number {
110
+ if (pokemon.level >= MAX_LEVEL) {
111
+ return 100;
112
+ }
113
+
114
+ const needed = xpToNextLevel(pokemon.level, pokemonData.expGroup);
115
+ if (needed <= 0) {
116
+ return 100;
117
+ }
118
+
119
+ return Math.min(100, Math.floor((pokemon.currentXp / needed) * 100));
120
+ }
121
+
122
+ /**
123
+ * Create an XP event descriptor from an event type, using constants.
124
+ * @param type - The XP event type
125
+ * @returns A fully populated XpEvent
126
+ */
127
+ export function createXpEvent(type: XpEventType): XpEvent {
128
+ const award = XP_AWARDS[type];
129
+ return {
130
+ type,
131
+ xp: award.xp,
132
+ statBoost: award.stat,
133
+ boostAmount: award.boost,
134
+ };
135
+ }