@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,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Achievement definitions and unlock-checking logic for Claudemon.
|
|
3
|
+
* All achievements are derived from PlayerState — pure functions, no side effects.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
Achievement,
|
|
8
|
+
AchievementCondition,
|
|
9
|
+
PlayerState,
|
|
10
|
+
UnlockedAchievement,
|
|
11
|
+
} from "../engine/types.js";
|
|
12
|
+
|
|
13
|
+
// ── Achievement Definitions ──────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export const ACHIEVEMENTS: readonly Achievement[] = [
|
|
16
|
+
// ── Trainer Milestones ───────────────────────────────────
|
|
17
|
+
{
|
|
18
|
+
id: "first_steps",
|
|
19
|
+
name: "First Steps",
|
|
20
|
+
description: "Pick your starter Pokemon",
|
|
21
|
+
category: "trainer",
|
|
22
|
+
condition: { type: "party_size", minSize: 1 },
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "getting_started",
|
|
26
|
+
name: "Getting Started",
|
|
27
|
+
description: "Reach level 10",
|
|
28
|
+
category: "trainer",
|
|
29
|
+
condition: { type: "level", minLevel: 10 },
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "seasoned",
|
|
33
|
+
name: "Seasoned Trainer",
|
|
34
|
+
description: "Reach level 50",
|
|
35
|
+
category: "trainer",
|
|
36
|
+
condition: { type: "level", minLevel: 50 },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "master",
|
|
40
|
+
name: "Pokemon Master",
|
|
41
|
+
description: "Reach level 100",
|
|
42
|
+
category: "trainer",
|
|
43
|
+
condition: { type: "level", minLevel: 100 },
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "full_party",
|
|
47
|
+
name: "Full Party",
|
|
48
|
+
description: "Have 6 Pokemon in your party",
|
|
49
|
+
category: "trainer",
|
|
50
|
+
condition: { type: "party_size", minSize: 6 },
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "collector_50",
|
|
54
|
+
name: "Collector",
|
|
55
|
+
description: "Catch 50 unique Pokemon",
|
|
56
|
+
category: "trainer",
|
|
57
|
+
condition: { type: "pokedex", minCaught: 50 },
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "completionist",
|
|
61
|
+
name: "Completionist",
|
|
62
|
+
description: "Catch all 151 Pokemon",
|
|
63
|
+
category: "trainer",
|
|
64
|
+
condition: { type: "pokedex", minCaught: 151 },
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
// ── Coding Achievements ──────────────────────────────────
|
|
68
|
+
{
|
|
69
|
+
id: "bug_catcher_10",
|
|
70
|
+
name: "Bug Catcher",
|
|
71
|
+
description: "Fix 10 errors",
|
|
72
|
+
category: "coding",
|
|
73
|
+
condition: { type: "counter", counter: "bugs_fixed", threshold: 10 },
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "exterminator",
|
|
77
|
+
name: "Exterminator",
|
|
78
|
+
description: "Fix 100 errors",
|
|
79
|
+
category: "coding",
|
|
80
|
+
condition: { type: "counter", counter: "bugs_fixed", threshold: 100 },
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "test_ace",
|
|
84
|
+
name: "Test Ace",
|
|
85
|
+
description: "Pass 100 tests",
|
|
86
|
+
category: "coding",
|
|
87
|
+
condition: { type: "counter", counter: "tests_passed", threshold: 100 },
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "ci_champion",
|
|
91
|
+
name: "CI Champion",
|
|
92
|
+
description: "50 successful builds",
|
|
93
|
+
category: "coding",
|
|
94
|
+
condition: { type: "counter", counter: "builds_succeeded", threshold: 50 },
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "refactor_king",
|
|
98
|
+
name: "Refactor King",
|
|
99
|
+
description: "Complete 10 large refactors",
|
|
100
|
+
category: "coding",
|
|
101
|
+
condition: { type: "counter", counter: "large_refactors", threshold: 10 },
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "iron_coder",
|
|
105
|
+
name: "Iron Coder",
|
|
106
|
+
description: "Maintain a 7-day coding streak",
|
|
107
|
+
category: "coding",
|
|
108
|
+
condition: { type: "streak", minDays: 7 },
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "marathon",
|
|
112
|
+
name: "Marathon",
|
|
113
|
+
description: "Maintain a 30-day coding streak",
|
|
114
|
+
category: "coding",
|
|
115
|
+
condition: { type: "streak", minDays: 30 },
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "centurion",
|
|
119
|
+
name: "Centurion",
|
|
120
|
+
description: "Maintain a 100-day coding streak",
|
|
121
|
+
category: "coding",
|
|
122
|
+
condition: { type: "streak", minDays: 100 },
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// ── Pokemon Achievements ─────────────────────────────────
|
|
126
|
+
{
|
|
127
|
+
id: "evolution",
|
|
128
|
+
name: "Evolutionary",
|
|
129
|
+
description: "Evolve a Pokemon",
|
|
130
|
+
category: "pokemon",
|
|
131
|
+
condition: { type: "evolution" },
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "badge_collector",
|
|
135
|
+
name: "Badge Collector",
|
|
136
|
+
description: "Earn your first badge",
|
|
137
|
+
category: "pokemon",
|
|
138
|
+
condition: { type: "badge", badge: "blaze" },
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
// ── Condition Checker ────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
/** Check whether a single achievement condition is satisfied by the current player state. */
|
|
145
|
+
export function isConditionMet(condition: AchievementCondition, state: PlayerState): boolean {
|
|
146
|
+
switch (condition.type) {
|
|
147
|
+
case "counter":
|
|
148
|
+
return (state.counters[condition.counter] ?? 0) >= condition.threshold;
|
|
149
|
+
|
|
150
|
+
case "level": {
|
|
151
|
+
const highestLevel = getHighestPartyLevel(state);
|
|
152
|
+
return highestLevel >= condition.minLevel;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
case "pokedex":
|
|
156
|
+
return state.pokedex.totalCaught >= condition.minCaught;
|
|
157
|
+
|
|
158
|
+
case "streak":
|
|
159
|
+
return state.streak.currentStreak >= condition.minDays;
|
|
160
|
+
|
|
161
|
+
case "badge":
|
|
162
|
+
return state.badges.includes(condition.badge);
|
|
163
|
+
|
|
164
|
+
case "evolution":
|
|
165
|
+
return hasAnyEvolvedPokemon(state);
|
|
166
|
+
|
|
167
|
+
case "party_size":
|
|
168
|
+
return state.party.length >= condition.minSize;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Achievement Discovery ────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
/** Return achievements that are newly unlocked (not already in state.achievements). */
|
|
175
|
+
export function checkNewAchievements(state: PlayerState): Achievement[] {
|
|
176
|
+
const alreadyUnlocked = new Set(state.achievements.map((a) => a.achievementId));
|
|
177
|
+
|
|
178
|
+
return ACHIEVEMENTS.filter(
|
|
179
|
+
(achievement) =>
|
|
180
|
+
!alreadyUnlocked.has(achievement.id) && isConditionMet(achievement.condition, state),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Create a timestamped unlock record for an achievement. */
|
|
185
|
+
export function unlockAchievement(achievementId: string): UnlockedAchievement {
|
|
186
|
+
return {
|
|
187
|
+
achievementId,
|
|
188
|
+
unlockedAt: new Date().toISOString(),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ── Internal Helpers ─────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
/** Get the highest level among all party Pokemon (0 if party is empty). */
|
|
195
|
+
function getHighestPartyLevel(state: PlayerState): number {
|
|
196
|
+
if (state.party.length === 0) return 0;
|
|
197
|
+
return Math.max(...state.party.map((p) => p.level));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Check whether any Pokemon in party or PC box has evolved (evolvedAt is set). */
|
|
201
|
+
function hasAnyEvolvedPokemon(state: PlayerState): boolean {
|
|
202
|
+
const allPokemon = [...state.party, ...state.pcBox];
|
|
203
|
+
return allPokemon.some((p) => p.evolvedAt !== null);
|
|
204
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legendary quest chain definitions for Claudemon.
|
|
3
|
+
* Each legendary Pokemon requires completing a multi-step quest
|
|
4
|
+
* tied to sustained coding activity.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { AchievementCondition, LegendaryQuest, PlayerState } from "../engine/types.js";
|
|
8
|
+
import { isConditionMet } from "./achievements.js";
|
|
9
|
+
|
|
10
|
+
// ── Quest Definitions ────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export const LEGENDARY_QUESTS: readonly LegendaryQuest[] = [
|
|
13
|
+
// ── Articuno (#144) — "The Ice Bird of Endurance" ────────
|
|
14
|
+
{
|
|
15
|
+
pokemonId: 144,
|
|
16
|
+
name: "The Ice Bird of Endurance",
|
|
17
|
+
steps: [
|
|
18
|
+
{
|
|
19
|
+
description: "Maintain a 30-day coding streak",
|
|
20
|
+
condition: { type: "streak", minDays: 30 },
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
description: "Catch 3 Water or Ice type Pokemon",
|
|
24
|
+
// Approximated as pokedex gate; real type-check in getQuestProgress.
|
|
25
|
+
condition: { type: "pokedex", minCaught: 3 },
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
description: "Maintain a 100-day coding streak",
|
|
29
|
+
condition: { type: "streak", minDays: 100 },
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
description: "Reach level 50 on your active Pokemon",
|
|
33
|
+
condition: { type: "level", minLevel: 50 },
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
// ── Zapdos (#145) — "The Thunder of Testing" ────────────
|
|
39
|
+
{
|
|
40
|
+
pokemonId: 145,
|
|
41
|
+
name: "The Thunder of Testing",
|
|
42
|
+
steps: [
|
|
43
|
+
{
|
|
44
|
+
description: "Pass 100 tests",
|
|
45
|
+
condition: { type: "counter", counter: "tests_passed", threshold: 100 },
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
description: "Write 50 test files",
|
|
49
|
+
condition: { type: "counter", counter: "tests_written", threshold: 50 },
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
description: "Pass 500 tests",
|
|
53
|
+
condition: { type: "counter", counter: "tests_passed", threshold: 500 },
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
description: "Pass 1000 tests lifetime",
|
|
57
|
+
condition: { type: "counter", counter: "tests_passed", threshold: 1000 },
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// ── Moltres (#146) — "The Flame of Debugging" ──────────
|
|
63
|
+
{
|
|
64
|
+
pokemonId: 146,
|
|
65
|
+
name: "The Flame of Debugging",
|
|
66
|
+
steps: [
|
|
67
|
+
{
|
|
68
|
+
description: "Fix 50 bugs",
|
|
69
|
+
condition: { type: "counter", counter: "bugs_fixed", threshold: 50 },
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
description: "Earn the Blaze Badge",
|
|
73
|
+
condition: { type: "badge", badge: "blaze" },
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
description: "Fix 200 bugs",
|
|
77
|
+
condition: { type: "counter", counter: "bugs_fixed", threshold: 200 },
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
description: "Fix 500 bugs lifetime",
|
|
81
|
+
condition: { type: "counter", counter: "bugs_fixed", threshold: 500 },
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
// ── Mewtwo (#150) — "The Ultimate Creation" ─────────────
|
|
87
|
+
{
|
|
88
|
+
pokemonId: 150,
|
|
89
|
+
name: "The Ultimate Creation",
|
|
90
|
+
steps: [
|
|
91
|
+
{
|
|
92
|
+
description: "Catch 50 unique Pokemon",
|
|
93
|
+
condition: { type: "pokedex", minCaught: 50 },
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
description: "Reach level 75 on any Pokemon",
|
|
97
|
+
condition: { type: "level", minLevel: 75 },
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
description: "Catch 100 unique Pokemon",
|
|
101
|
+
condition: { type: "pokedex", minCaught: 100 },
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
description: "Catch 140 unique Pokemon",
|
|
105
|
+
condition: { type: "pokedex", minCaught: 140 },
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
// ── Mew (#151) — "The Myth" ─────────────────────────────
|
|
111
|
+
{
|
|
112
|
+
pokemonId: 151,
|
|
113
|
+
name: "The Myth",
|
|
114
|
+
steps: [
|
|
115
|
+
{
|
|
116
|
+
description: "Maintain a 100-day coding streak",
|
|
117
|
+
condition: { type: "streak", minDays: 100 },
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
description: "Catch all 3 legendary birds (Articuno, Zapdos, Moltres)",
|
|
121
|
+
// Gate: must have at least 148 caught (birds are #144-146).
|
|
122
|
+
// Real check in getQuestProgress verifies specific IDs.
|
|
123
|
+
condition: { type: "pokedex", minCaught: 148 },
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
description: "Catch Mewtwo",
|
|
127
|
+
// Gate: must have at least 149 caught (Mewtwo is #150).
|
|
128
|
+
// Real check in getQuestProgress verifies specific ID.
|
|
129
|
+
condition: { type: "pokedex", minCaught: 149 },
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
description: "Maintain a 365-day coding streak",
|
|
133
|
+
condition: { type: "streak", minDays: 365 },
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
// ── Quest Progress ───────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
/** IDs of Water and Ice type Pokemon in Gen 1 Pokedex. */
|
|
142
|
+
const WATER_ICE_POKEMON_IDS: ReadonlySet<number> = new Set([
|
|
143
|
+
// Water types
|
|
144
|
+
7,
|
|
145
|
+
8,
|
|
146
|
+
9, // Squirtle line
|
|
147
|
+
54,
|
|
148
|
+
55, // Psyduck, Golduck
|
|
149
|
+
60,
|
|
150
|
+
61,
|
|
151
|
+
62, // Poliwag line
|
|
152
|
+
72,
|
|
153
|
+
73, // Tentacool, Tentacruel
|
|
154
|
+
79,
|
|
155
|
+
80, // Slowpoke, Slowbro
|
|
156
|
+
86,
|
|
157
|
+
87, // Seel, Dewgong (also Ice)
|
|
158
|
+
90,
|
|
159
|
+
91, // Shellder, Cloyster (also Ice)
|
|
160
|
+
98,
|
|
161
|
+
99, // Krabby, Kingler
|
|
162
|
+
116,
|
|
163
|
+
117, // Horsea, Seadra
|
|
164
|
+
118,
|
|
165
|
+
119, // Goldeen, Seaking
|
|
166
|
+
120,
|
|
167
|
+
121, // Staryu, Starmie
|
|
168
|
+
129,
|
|
169
|
+
130, // Magikarp, Gyarados
|
|
170
|
+
131, // Lapras (Water/Ice)
|
|
171
|
+
134, // Vaporeon
|
|
172
|
+
138,
|
|
173
|
+
139, // Omanyte, Omastar
|
|
174
|
+
140,
|
|
175
|
+
141, // Kabuto, Kabutops
|
|
176
|
+
// Ice types (not already listed)
|
|
177
|
+
124, // Jynx (Ice/Psychic)
|
|
178
|
+
144, // Articuno (Ice/Flying)
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
/** Legendary bird Pokedex IDs. */
|
|
182
|
+
const LEGENDARY_BIRD_IDS: readonly number[] = [144, 145, 146];
|
|
183
|
+
|
|
184
|
+
/** Mewtwo Pokedex ID. */
|
|
185
|
+
const MEWTWO_ID = 150;
|
|
186
|
+
|
|
187
|
+
export interface QuestProgress {
|
|
188
|
+
readonly quest: LegendaryQuest;
|
|
189
|
+
readonly stepsCompleted: number;
|
|
190
|
+
readonly totalSteps: number;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Calculate completion progress for every legendary quest.
|
|
195
|
+
* Steps are checked sequentially — a later step cannot complete
|
|
196
|
+
* until all earlier steps are done.
|
|
197
|
+
*/
|
|
198
|
+
export function getQuestProgress(state: PlayerState): QuestProgress[] {
|
|
199
|
+
return LEGENDARY_QUESTS.map((quest) => {
|
|
200
|
+
let stepsCompleted = 0;
|
|
201
|
+
|
|
202
|
+
for (const step of quest.steps) {
|
|
203
|
+
if (isStepComplete(quest.pokemonId, step.description, step.condition, state)) {
|
|
204
|
+
stepsCompleted++;
|
|
205
|
+
} else {
|
|
206
|
+
break; // Steps are sequential — stop at first incomplete.
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
quest,
|
|
212
|
+
stepsCompleted,
|
|
213
|
+
totalSteps: quest.steps.length,
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ── Internal Helpers ─────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Check whether a quest step is complete.
|
|
222
|
+
* Uses isConditionMet for standard conditions, with overrides
|
|
223
|
+
* for steps that require specific-Pokemon or type-based checks.
|
|
224
|
+
*/
|
|
225
|
+
function isStepComplete(
|
|
226
|
+
questPokemonId: number,
|
|
227
|
+
stepDescription: string,
|
|
228
|
+
condition: AchievementCondition,
|
|
229
|
+
state: PlayerState,
|
|
230
|
+
): boolean {
|
|
231
|
+
// Articuno step 2: catch 3 Water/Ice type Pokemon
|
|
232
|
+
if (questPokemonId === 144 && stepDescription.includes("Water or Ice")) {
|
|
233
|
+
return countCaughtByIds(state, WATER_ICE_POKEMON_IDS) >= 3;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Mew step 2: catch all 3 legendary birds
|
|
237
|
+
if (questPokemonId === 151 && stepDescription.includes("legendary birds")) {
|
|
238
|
+
return LEGENDARY_BIRD_IDS.every((id) => isPokemonCaught(state, id));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Mew step 3: catch Mewtwo
|
|
242
|
+
if (questPokemonId === 151 && stepDescription.includes("Mewtwo")) {
|
|
243
|
+
return isPokemonCaught(state, MEWTWO_ID);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Default: delegate to the standard condition checker.
|
|
247
|
+
return isConditionMet(condition, state);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Check whether a specific Pokemon ID has been caught in the Pokedex. */
|
|
251
|
+
function isPokemonCaught(state: PlayerState, pokemonId: number): boolean {
|
|
252
|
+
const entry = state.pokedex.entries[pokemonId];
|
|
253
|
+
return entry !== undefined && entry.caught;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Count how many Pokemon from a given ID set have been caught. */
|
|
257
|
+
function countCaughtByIds(state: PlayerState, ids: ReadonlySet<number>): number {
|
|
258
|
+
let count = 0;
|
|
259
|
+
for (const id of ids) {
|
|
260
|
+
if (isPokemonCaught(state, id)) {
|
|
261
|
+
count++;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return count;
|
|
265
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Milestone discovery definitions for Claudemon.
|
|
3
|
+
* Specific Pokemon appear when coding counters reach defined thresholds.
|
|
4
|
+
* Check these after every counter increment.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { EventCounterKey } from "../engine/types.js";
|
|
8
|
+
|
|
9
|
+
// ── Milestone Types ──────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
export interface MilestoneDiscovery {
|
|
12
|
+
readonly pokemonId: number;
|
|
13
|
+
readonly counter: EventCounterKey;
|
|
14
|
+
readonly threshold: number;
|
|
15
|
+
readonly message: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ── Milestone Definitions ────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
export const MILESTONES: readonly MilestoneDiscovery[] = [
|
|
21
|
+
{
|
|
22
|
+
pokemonId: 16, // Pidgey
|
|
23
|
+
counter: "commits",
|
|
24
|
+
threshold: 1,
|
|
25
|
+
message: "Every journey starts with a first step!",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
pokemonId: 66, // Machop
|
|
29
|
+
counter: "tests_passed",
|
|
30
|
+
threshold: 10,
|
|
31
|
+
message: "Your code is getting stronger!",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
pokemonId: 74, // Geodude
|
|
35
|
+
counter: "builds_succeeded",
|
|
36
|
+
threshold: 1,
|
|
37
|
+
message: "Solid foundations!",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
pokemonId: 63, // Abra
|
|
41
|
+
counter: "commits",
|
|
42
|
+
threshold: 50,
|
|
43
|
+
message: "You're getting quick!",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
pokemonId: 89, // Muk
|
|
47
|
+
counter: "bugs_fixed",
|
|
48
|
+
threshold: 100,
|
|
49
|
+
message: "You've seen some ugly code...",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
pokemonId: 129, // Magikarp
|
|
53
|
+
counter: "commits",
|
|
54
|
+
threshold: 200,
|
|
55
|
+
message: "Humble beginnings... just wait",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
pokemonId: 137, // Porygon
|
|
59
|
+
counter: "tests_written",
|
|
60
|
+
threshold: 100,
|
|
61
|
+
message: "A digital creation for a digital creator!",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
pokemonId: 143, // Snorlax
|
|
65
|
+
counter: "files_edited",
|
|
66
|
+
threshold: 500,
|
|
67
|
+
message: "All that hard work... time for a nap?",
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
// ── Milestone Checker ────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Return milestones whose thresholds are met but whose Pokemon
|
|
75
|
+
* have not yet been caught (not in the provided pokedex set).
|
|
76
|
+
*/
|
|
77
|
+
export function checkNewMilestones(
|
|
78
|
+
counters: Record<string, number>,
|
|
79
|
+
caughtPokemonIds: Set<number>,
|
|
80
|
+
): MilestoneDiscovery[] {
|
|
81
|
+
return MILESTONES.filter(
|
|
82
|
+
(milestone) =>
|
|
83
|
+
!caughtPokemonIds.has(milestone.pokemonId) &&
|
|
84
|
+
(counters[milestone.counter] ?? 0) >= milestone.threshold,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* award-xp.ts — Lightweight bun script called by the PostToolUse hook.
|
|
3
|
+
* Usage: bun run award-xp.ts <event_type> <counter_key>
|
|
4
|
+
*
|
|
5
|
+
* Loads state, awards XP to the active Pokemon, applies stat boosts,
|
|
6
|
+
* increments the event counter, updates the streak, and saves.
|
|
7
|
+
* Emits a terminal bell on level-up.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { XpEventType, EventCounterKey } from "../engine/types.js";
|
|
11
|
+
import { BELL } from "../engine/constants.js";
|
|
12
|
+
import { StateManager } from "../state/state-manager.js";
|
|
13
|
+
import { addXp, createXpEvent } from "../engine/xp.js";
|
|
14
|
+
import { applyStatBoost } from "../engine/stats.js";
|
|
15
|
+
import { POKEMON_BY_ID } from "../engine/pokemon-data.js";
|
|
16
|
+
import { checkEvolution, getNewlyEarnedBadges } from "../engine/evolution.js";
|
|
17
|
+
import { shouldTriggerEncounter, generateEncounter } from "../engine/encounters.js";
|
|
18
|
+
|
|
19
|
+
const eventType = process.argv[2] as XpEventType;
|
|
20
|
+
const counterKey = process.argv[3] as EventCounterKey | undefined;
|
|
21
|
+
|
|
22
|
+
if (!eventType) {
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const stateManager = StateManager.getInstance();
|
|
27
|
+
const state = await stateManager.load();
|
|
28
|
+
|
|
29
|
+
// No state file = first run, nothing to do yet
|
|
30
|
+
if (!state) {
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const pokemon = stateManager.getActivePokemon();
|
|
35
|
+
if (!pokemon) {
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const pokemonData = POKEMON_BY_ID.get(pokemon.pokemonId);
|
|
40
|
+
if (!pokemonData) {
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Create XP event from the event type
|
|
45
|
+
const xpEvent = createXpEvent(eventType);
|
|
46
|
+
|
|
47
|
+
// Award XP and check for level-up
|
|
48
|
+
const levelUp = addXp(pokemon, xpEvent.xp, pokemonData);
|
|
49
|
+
|
|
50
|
+
// Apply stat boost if the event provides one
|
|
51
|
+
if (xpEvent.statBoost && xpEvent.boostAmount > 0) {
|
|
52
|
+
applyStatBoost(pokemon, xpEvent.statBoost, xpEvent.boostAmount);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Track total XP earned by the trainer
|
|
56
|
+
state.totalXpEarned += xpEvent.xp;
|
|
57
|
+
|
|
58
|
+
// Increment the event counter directly on state (avoids extra save from incrementCounter)
|
|
59
|
+
if (counterKey) {
|
|
60
|
+
state.counters[counterKey] += 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Update the daily coding streak
|
|
64
|
+
// Inline the streak logic to avoid the extra save from updateStreak()
|
|
65
|
+
const today = new Date();
|
|
66
|
+
const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
|
|
67
|
+
const { streak } = state;
|
|
68
|
+
|
|
69
|
+
if (streak.lastActiveDate !== todayStr) {
|
|
70
|
+
if (streak.lastActiveDate === null) {
|
|
71
|
+
streak.currentStreak = 1;
|
|
72
|
+
streak.totalDaysActive = 1;
|
|
73
|
+
} else {
|
|
74
|
+
const last = new Date(streak.lastActiveDate);
|
|
75
|
+
const now = new Date(todayStr);
|
|
76
|
+
const diffMs = now.getTime() - last.getTime();
|
|
77
|
+
const diffDays = Math.round(diffMs / (1000 * 60 * 60 * 24));
|
|
78
|
+
|
|
79
|
+
if (diffDays === 1) {
|
|
80
|
+
streak.currentStreak += 1;
|
|
81
|
+
} else {
|
|
82
|
+
streak.currentStreak = 1;
|
|
83
|
+
}
|
|
84
|
+
streak.totalDaysActive += 1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (streak.currentStreak > streak.longestStreak) {
|
|
88
|
+
streak.longestStreak = streak.currentStreak;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
streak.lastActiveDate = todayStr;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check for newly earned badges
|
|
95
|
+
const newBadges = getNewlyEarnedBadges(state);
|
|
96
|
+
for (const badge of newBadges) {
|
|
97
|
+
state.badges.push(badge);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check if evolution is available (sets flag in status for status line indicator)
|
|
101
|
+
const evolutionReady = checkEvolution(pokemon, state) !== null;
|
|
102
|
+
|
|
103
|
+
// Track XP toward next encounter and trigger if threshold met
|
|
104
|
+
state.xpSinceLastEncounter = (state.xpSinceLastEncounter ?? 0) + xpEvent.xp;
|
|
105
|
+
let encounterTriggered = false;
|
|
106
|
+
|
|
107
|
+
if (shouldTriggerEncounter(state.xpSinceLastEncounter) && !state.pendingEncounter) {
|
|
108
|
+
const encounter = generateEncounter(eventType, state);
|
|
109
|
+
if (encounter) {
|
|
110
|
+
state.pendingEncounter = encounter;
|
|
111
|
+
state.xpSinceLastEncounter = 0;
|
|
112
|
+
encounterTriggered = true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Single atomic save for all mutations
|
|
117
|
+
await stateManager.save();
|
|
118
|
+
|
|
119
|
+
// Write status for the status line (includes evolution flag and encounter notification)
|
|
120
|
+
if (encounterTriggered && state.pendingEncounter) {
|
|
121
|
+
const encSpecies = POKEMON_BY_ID.get(state.pendingEncounter.pokemonId);
|
|
122
|
+
const encName = encSpecies?.name ?? "???";
|
|
123
|
+
await stateManager.writeEncounterStatus(encName);
|
|
124
|
+
} else {
|
|
125
|
+
await stateManager.writeStatus(evolutionReady);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Terminal bell on level-up, evolution available, or encounter (written to stderr so it reaches the terminal)
|
|
129
|
+
if ((levelUp || evolutionReady || encounterTriggered) && state.config.bellEnabled) {
|
|
130
|
+
process.stderr.write(BELL);
|
|
131
|
+
}
|