@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.
- package/README.md +164 -0
- package/bin/claudemon.js +52 -0
- package/bunfig.toml +2 -0
- package/cli/doctor.ts +334 -0
- package/cli/index.ts +42 -0
- package/cli/install.ts +248 -0
- package/cli/shared.ts +102 -0
- package/cli/uninstall.ts +155 -0
- package/cli/update.ts +318 -0
- package/hooks/post-tool-use.sh +127 -0
- package/hooks/stop.sh +49 -0
- package/hooks/user-prompt-submit.sh +73 -0
- package/package.json +68 -0
- package/scripts/download-colorscripts.ts +311 -0
- package/skills/buddy/SKILL.md +47 -0
- package/sprites/colorscripts/small/1-bulbasaur.txt +11 -0
- package/sprites/colorscripts/small/10-caterpie.txt +9 -0
- package/sprites/colorscripts/small/100-voltorb.txt +8 -0
- package/sprites/colorscripts/small/101-electrode.txt +9 -0
- package/sprites/colorscripts/small/102-exeggcute.txt +10 -0
- package/sprites/colorscripts/small/103-exeggutor.txt +23 -0
- package/sprites/colorscripts/small/104-cubone.txt +11 -0
- package/sprites/colorscripts/small/105-marowak.txt +16 -0
- package/sprites/colorscripts/small/106-hitmonlee.txt +16 -0
- package/sprites/colorscripts/small/107-hitmonchan.txt +19 -0
- package/sprites/colorscripts/small/108-lickitung.txt +10 -0
- package/sprites/colorscripts/small/109-koffing.txt +14 -0
- package/sprites/colorscripts/small/11-metapod.txt +10 -0
- package/sprites/colorscripts/small/110-weezing.txt +23 -0
- package/sprites/colorscripts/small/111-rhyhorn.txt +11 -0
- package/sprites/colorscripts/small/112-rhydon.txt +20 -0
- package/sprites/colorscripts/small/113-chansey.txt +11 -0
- package/sprites/colorscripts/small/114-tangela.txt +10 -0
- package/sprites/colorscripts/small/115-kangaskhan.txt +18 -0
- package/sprites/colorscripts/small/116-horsea.txt +10 -0
- package/sprites/colorscripts/small/117-seadra.txt +11 -0
- package/sprites/colorscripts/small/118-goldeen.txt +11 -0
- package/sprites/colorscripts/small/119-seaking.txt +16 -0
- package/sprites/colorscripts/small/12-butterfree.txt +20 -0
- package/sprites/colorscripts/small/120-staryu.txt +10 -0
- package/sprites/colorscripts/small/121-starmie.txt +17 -0
- package/sprites/colorscripts/small/122-mr-mime.txt +18 -0
- package/sprites/colorscripts/small/123-scyther.txt +21 -0
- package/sprites/colorscripts/small/124-jynx.txt +18 -0
- package/sprites/colorscripts/small/125-electabuzz.txt +19 -0
- package/sprites/colorscripts/small/126-magmar.txt +19 -0
- package/sprites/colorscripts/small/127-pinsir.txt +19 -0
- package/sprites/colorscripts/small/128-tauros.txt +20 -0
- package/sprites/colorscripts/small/129-magikarp.txt +13 -0
- package/sprites/colorscripts/small/13-weedle.txt +10 -0
- package/sprites/colorscripts/small/130-gyarados.txt +21 -0
- package/sprites/colorscripts/small/131-lapras.txt +19 -0
- package/sprites/colorscripts/small/132-ditto.txt +8 -0
- package/sprites/colorscripts/small/133-eevee.txt +10 -0
- package/sprites/colorscripts/small/134-vaporeon.txt +16 -0
- package/sprites/colorscripts/small/135-jolteon.txt +17 -0
- package/sprites/colorscripts/small/136-flareon.txt +18 -0
- package/sprites/colorscripts/small/137-porygon.txt +10 -0
- package/sprites/colorscripts/small/138-omanyte.txt +10 -0
- package/sprites/colorscripts/small/139-omastar.txt +18 -0
- package/sprites/colorscripts/small/14-kakuna.txt +10 -0
- package/sprites/colorscripts/small/140-kabuto.txt +8 -0
- package/sprites/colorscripts/small/141-kabutops.txt +17 -0
- package/sprites/colorscripts/small/142-aerodactyl.txt +17 -0
- package/sprites/colorscripts/small/143-snorlax.txt +21 -0
- package/sprites/colorscripts/small/144-articuno.txt +24 -0
- package/sprites/colorscripts/small/145-zapdos.txt +20 -0
- package/sprites/colorscripts/small/146-moltres.txt +23 -0
- package/sprites/colorscripts/small/147-dratini.txt +10 -0
- package/sprites/colorscripts/small/148-dragonair.txt +12 -0
- package/sprites/colorscripts/small/149-dragonite.txt +21 -0
- package/sprites/colorscripts/small/15-beedrill.txt +13 -0
- package/sprites/colorscripts/small/150-mewtwo.txt +22 -0
- package/sprites/colorscripts/small/151-mew.txt +14 -0
- package/sprites/colorscripts/small/16-pidgey.txt +10 -0
- package/sprites/colorscripts/small/17-pidgeotto.txt +11 -0
- package/sprites/colorscripts/small/18-pidgeot.txt +18 -0
- package/sprites/colorscripts/small/19-rattata.txt +12 -0
- package/sprites/colorscripts/small/2-ivysaur.txt +11 -0
- package/sprites/colorscripts/small/20-raticate.txt +12 -0
- package/sprites/colorscripts/small/21-spearow.txt +9 -0
- package/sprites/colorscripts/small/22-fearow.txt +12 -0
- package/sprites/colorscripts/small/23-ekans.txt +12 -0
- package/sprites/colorscripts/small/24-arbok.txt +16 -0
- package/sprites/colorscripts/small/25-pikachu.txt +11 -0
- package/sprites/colorscripts/small/26-raichu.txt +19 -0
- package/sprites/colorscripts/small/27-sandshrew.txt +10 -0
- package/sprites/colorscripts/small/28-sandslash.txt +16 -0
- package/sprites/colorscripts/small/29-nidoran-f.txt +11 -0
- package/sprites/colorscripts/small/3-venusaur.txt +21 -0
- package/sprites/colorscripts/small/30-nidorina.txt +12 -0
- package/sprites/colorscripts/small/31-nidoqueen.txt +19 -0
- package/sprites/colorscripts/small/32-nidoran-m.txt +11 -0
- package/sprites/colorscripts/small/33-nidorino.txt +12 -0
- package/sprites/colorscripts/small/34-nidoking.txt +18 -0
- package/sprites/colorscripts/small/35-clefairy.txt +11 -0
- package/sprites/colorscripts/small/36-clefable.txt +17 -0
- package/sprites/colorscripts/small/37-vulpix.txt +11 -0
- package/sprites/colorscripts/small/38-ninetales.txt +18 -0
- package/sprites/colorscripts/small/39-jigglypuff.txt +11 -0
- package/sprites/colorscripts/small/4-charmander.txt +11 -0
- package/sprites/colorscripts/small/40-wigglytuff.txt +20 -0
- package/sprites/colorscripts/small/41-zubat.txt +11 -0
- package/sprites/colorscripts/small/42-golbat.txt +18 -0
- package/sprites/colorscripts/small/43-oddish.txt +11 -0
- package/sprites/colorscripts/small/44-gloom.txt +12 -0
- package/sprites/colorscripts/small/45-vileplume.txt +17 -0
- package/sprites/colorscripts/small/46-paras.txt +11 -0
- package/sprites/colorscripts/small/47-parasect.txt +12 -0
- package/sprites/colorscripts/small/48-venonat.txt +14 -0
- package/sprites/colorscripts/small/49-venomoth.txt +19 -0
- package/sprites/colorscripts/small/5-charmeleon.txt +13 -0
- package/sprites/colorscripts/small/50-diglett.txt +8 -0
- package/sprites/colorscripts/small/51-dugtrio.txt +18 -0
- package/sprites/colorscripts/small/52-meowth.txt +12 -0
- package/sprites/colorscripts/small/53-persian.txt +20 -0
- package/sprites/colorscripts/small/54-psyduck.txt +12 -0
- package/sprites/colorscripts/small/55-golduck.txt +17 -0
- package/sprites/colorscripts/small/56-mankey.txt +11 -0
- package/sprites/colorscripts/small/57-primeape.txt +13 -0
- package/sprites/colorscripts/small/58-growlithe.txt +12 -0
- package/sprites/colorscripts/small/59-arcanine.txt +20 -0
- package/sprites/colorscripts/small/6-charizard.txt +21 -0
- package/sprites/colorscripts/small/60-poliwag.txt +9 -0
- package/sprites/colorscripts/small/61-poliwhirl.txt +11 -0
- package/sprites/colorscripts/small/62-poliwrath.txt +17 -0
- package/sprites/colorscripts/small/63-abra.txt +12 -0
- package/sprites/colorscripts/small/64-kadabra.txt +14 -0
- package/sprites/colorscripts/small/65-alakazam.txt +19 -0
- package/sprites/colorscripts/small/66-machop.txt +11 -0
- package/sprites/colorscripts/small/67-machoke.txt +12 -0
- package/sprites/colorscripts/small/68-machamp.txt +19 -0
- package/sprites/colorscripts/small/69-bellsprout.txt +9 -0
- package/sprites/colorscripts/small/7-squirtle.txt +10 -0
- package/sprites/colorscripts/small/70-weepinbell.txt +11 -0
- package/sprites/colorscripts/small/71-victreebel.txt +17 -0
- package/sprites/colorscripts/small/72-tentacool.txt +12 -0
- package/sprites/colorscripts/small/73-tentacruel.txt +20 -0
- package/sprites/colorscripts/small/74-geodude.txt +9 -0
- package/sprites/colorscripts/small/75-graveler.txt +12 -0
- package/sprites/colorscripts/small/76-golem.txt +18 -0
- package/sprites/colorscripts/small/77-ponyta.txt +13 -0
- package/sprites/colorscripts/small/78-rapidash.txt +18 -0
- package/sprites/colorscripts/small/79-slowpoke.txt +12 -0
- package/sprites/colorscripts/small/8-wartortle.txt +12 -0
- package/sprites/colorscripts/small/80-slowbro.txt +18 -0
- package/sprites/colorscripts/small/81-magnemite.txt +9 -0
- package/sprites/colorscripts/small/82-magneton.txt +18 -0
- package/sprites/colorscripts/small/83-farfetchd.txt +12 -0
- package/sprites/colorscripts/small/84-doduo.txt +10 -0
- package/sprites/colorscripts/small/85-dodrio.txt +17 -0
- package/sprites/colorscripts/small/86-seel.txt +13 -0
- package/sprites/colorscripts/small/87-dewgong.txt +20 -0
- package/sprites/colorscripts/small/88-grimer.txt +10 -0
- package/sprites/colorscripts/small/89-muk.txt +14 -0
- package/sprites/colorscripts/small/9-blastoise.txt +20 -0
- package/sprites/colorscripts/small/90-shellder.txt +10 -0
- package/sprites/colorscripts/small/91-cloyster.txt +18 -0
- package/sprites/colorscripts/small/92-gastly.txt +12 -0
- package/sprites/colorscripts/small/93-haunter.txt +14 -0
- package/sprites/colorscripts/small/94-gengar.txt +19 -0
- package/sprites/colorscripts/small/95-onix.txt +22 -0
- package/sprites/colorscripts/small/96-drowzee.txt +12 -0
- package/sprites/colorscripts/small/97-hypno.txt +19 -0
- package/sprites/colorscripts/small/98-krabby.txt +12 -0
- package/sprites/colorscripts/small/99-kingler.txt +20 -0
- package/src/engine/constants.ts +121 -0
- package/src/engine/encounter-pool.ts +71 -0
- package/src/engine/encounters.ts +308 -0
- package/src/engine/evolution-data.ts +535 -0
- package/src/engine/evolution.ts +310 -0
- package/src/engine/pokemon-data.ts +1838 -0
- package/src/engine/reactions.ts +877 -0
- package/src/engine/starter-pool.ts +47 -0
- package/src/engine/stats.ts +97 -0
- package/src/engine/types.ts +312 -0
- package/src/engine/xp.ts +135 -0
- package/src/gamification/achievements.ts +204 -0
- package/src/gamification/legendary-quests.ts +265 -0
- package/src/gamification/milestones.ts +86 -0
- package/src/hooks/award-xp.ts +131 -0
- package/src/hooks/increment-counter.ts +27 -0
- package/src/server/index.ts +78 -0
- package/src/server/instructions.ts +194 -0
- package/src/server/tools/achievements.ts +118 -0
- package/src/server/tools/catch.ts +295 -0
- package/src/server/tools/display-helpers.ts +35 -0
- package/src/server/tools/evolve.ts +236 -0
- package/src/server/tools/legendary.ts +78 -0
- package/src/server/tools/party.ts +251 -0
- package/src/server/tools/pet.ts +124 -0
- package/src/server/tools/pokedex.ts +286 -0
- package/src/server/tools/rename.ts +63 -0
- package/src/server/tools/show.ts +136 -0
- package/src/server/tools/starter.ts +175 -0
- package/src/server/tools/stats.ts +123 -0
- package/src/server/tools/visibility.ts +65 -0
- package/src/sprites/index.ts +45 -0
- package/src/state/io.ts +91 -0
- package/src/state/schemas.ts +131 -0
- package/src/state/state-manager.ts +321 -0
- package/statusline/buddy-status.sh +233 -0
- 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
|
+
}
|
package/src/engine/xp.ts
ADDED
|
@@ -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
|
+
}
|