claude-minipet 0.1.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 (53) hide show
  1. package/README.md +113 -0
  2. package/dist/cli/index.d.ts +2 -0
  3. package/dist/cli/index.js +351 -0
  4. package/dist/cli/index.js.map +1 -0
  5. package/dist/cli/install.d.ts +4 -0
  6. package/dist/cli/install.js +89 -0
  7. package/dist/cli/install.js.map +1 -0
  8. package/dist/cli/setup.d.ts +11 -0
  9. package/dist/cli/setup.js +203 -0
  10. package/dist/cli/setup.js.map +1 -0
  11. package/dist/core/dna.d.ts +14 -0
  12. package/dist/core/dna.js +95 -0
  13. package/dist/core/dna.js.map +1 -0
  14. package/dist/core/evolution.d.ts +16 -0
  15. package/dist/core/evolution.js +252 -0
  16. package/dist/core/evolution.js.map +1 -0
  17. package/dist/core/pet.d.ts +25 -0
  18. package/dist/core/pet.js +224 -0
  19. package/dist/core/pet.js.map +1 -0
  20. package/dist/core/rarity.d.ts +17 -0
  21. package/dist/core/rarity.js +55 -0
  22. package/dist/core/rarity.js.map +1 -0
  23. package/dist/core/sync.d.ts +37 -0
  24. package/dist/core/sync.js +94 -0
  25. package/dist/core/sync.js.map +1 -0
  26. package/dist/core/types.d.ts +92 -0
  27. package/dist/core/types.js +22 -0
  28. package/dist/core/types.js.map +1 -0
  29. package/dist/daemon/server.d.ts +8 -0
  30. package/dist/daemon/server.js +137 -0
  31. package/dist/daemon/server.js.map +1 -0
  32. package/dist/hooks/handler.d.ts +2 -0
  33. package/dist/hooks/handler.js +99 -0
  34. package/dist/hooks/handler.js.map +1 -0
  35. package/dist/render/anim-state.d.ts +5 -0
  36. package/dist/render/anim-state.js +24 -0
  37. package/dist/render/anim-state.js.map +1 -0
  38. package/dist/render/animation.d.ts +15 -0
  39. package/dist/render/animation.js +94 -0
  40. package/dist/render/animation.js.map +1 -0
  41. package/dist/render/frames.d.ts +51 -0
  42. package/dist/render/frames.js +151 -0
  43. package/dist/render/frames.js.map +1 -0
  44. package/dist/render/pixel.d.ts +44 -0
  45. package/dist/render/pixel.js +169 -0
  46. package/dist/render/pixel.js.map +1 -0
  47. package/dist/render/sprites.d.ts +8 -0
  48. package/dist/render/sprites.js +300 -0
  49. package/dist/render/sprites.js.map +1 -0
  50. package/dist/render/statusline.d.ts +3 -0
  51. package/dist/render/statusline.js +117 -0
  52. package/dist/render/statusline.js.map +1 -0
  53. package/package.json +44 -0
@@ -0,0 +1,137 @@
1
+ import { loadState, saveState } from '../core/pet.js';
2
+ import { checkEvolution, applyEvolution } from '../core/evolution.js';
3
+ import { triggerAnim } from '../render/anim-state.js';
4
+ import { syncPetToServer, loadAuth } from '../core/sync.js';
5
+ import { writeFileSync, readFileSync, existsSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { homedir } from 'node:os';
8
+ const PID_FILE = join(homedir(), '.claude-minipet', 'daemon.pid');
9
+ const DECAY_INTERVAL_MS = 60 * 1000; // Check every minute
10
+ const HUNGER_DECAY_PER_HOUR = 5;
11
+ const MOOD_DECAY_PER_HOUR = 3;
12
+ const MOOD_DECAY_HUNGER_THRESHOLD = 30;
13
+ /** Write PID file */
14
+ function writePid() {
15
+ writeFileSync(PID_FILE, String(process.pid), 'utf-8');
16
+ }
17
+ /** Check if daemon is already running */
18
+ export function isDaemonRunning() {
19
+ if (!existsSync(PID_FILE))
20
+ return false;
21
+ try {
22
+ const pid = parseInt(readFileSync(PID_FILE, 'utf-8').trim());
23
+ // Check if process exists
24
+ process.kill(pid, 0);
25
+ return true;
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ }
31
+ /** Get daemon PID */
32
+ export function getDaemonPid() {
33
+ if (!existsSync(PID_FILE))
34
+ return null;
35
+ try {
36
+ return parseInt(readFileSync(PID_FILE, 'utf-8').trim());
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ /** Apply time-based decay to pet stats */
43
+ function applyDecay() {
44
+ const state = loadState();
45
+ if (!state)
46
+ return;
47
+ const now = Date.now();
48
+ const lastInteraction = new Date(state.lastInteraction).getTime();
49
+ const minutesSinceInteraction = (now - lastInteraction) / 60000;
50
+ // Only decay if some time has passed
51
+ if (minutesSinceInteraction < 1)
52
+ return;
53
+ // Hunger decay: -5 per hour → -5/60 per minute
54
+ const hungerDecay = (HUNGER_DECAY_PER_HOUR / 60) * Math.min(minutesSinceInteraction, 60);
55
+ state.hunger = Math.max(0, state.hunger - hungerDecay);
56
+ // Trigger hungry animation when food drops below threshold
57
+ if (state.hunger < 20) {
58
+ triggerAnim('hungry');
59
+ }
60
+ else if (state.mood < 30) {
61
+ triggerAnim('sad');
62
+ }
63
+ // Mood decay when hungry
64
+ if (state.hunger < MOOD_DECAY_HUNGER_THRESHOLD) {
65
+ // Pixiebot has slower mood decay
66
+ const moodMultiplier = state.species === 'pixiebot' ? 0.5 : 1;
67
+ const moodDecay = (MOOD_DECAY_PER_HOUR / 60) * Math.min(minutesSinceInteraction, 60) * moodMultiplier;
68
+ state.mood = Math.max(0, state.mood - moodDecay);
69
+ }
70
+ // Check evolution conditions periodically
71
+ const evoCandidate = checkEvolution(state);
72
+ if (evoCandidate && evoCandidate !== state.evolution) {
73
+ applyEvolution(state, evoCandidate);
74
+ }
75
+ saveState(state);
76
+ // Sync to server if logged in
77
+ if (loadAuth()) {
78
+ syncPetToServer(state).catch(() => { });
79
+ }
80
+ }
81
+ /** Start the daemon loop */
82
+ export function startDaemon() {
83
+ if (isDaemonRunning()) {
84
+ console.log('Daemon is already running.');
85
+ process.exit(0);
86
+ }
87
+ writePid();
88
+ console.log(`MiniPet daemon started (PID: ${process.pid})`);
89
+ // Run decay check periodically
90
+ const interval = setInterval(() => {
91
+ try {
92
+ applyDecay();
93
+ }
94
+ catch (err) {
95
+ console.error('Decay error:', err);
96
+ }
97
+ }, DECAY_INTERVAL_MS);
98
+ // Initial decay check
99
+ applyDecay();
100
+ // Handle graceful shutdown
101
+ const cleanup = () => {
102
+ clearInterval(interval);
103
+ try {
104
+ const { unlinkSync } = require('node:fs');
105
+ unlinkSync(PID_FILE);
106
+ }
107
+ catch { /* ignore */ }
108
+ process.exit(0);
109
+ };
110
+ process.on('SIGTERM', cleanup);
111
+ process.on('SIGINT', cleanup);
112
+ // Keep process alive
113
+ process.stdin.resume();
114
+ }
115
+ /** Stop the daemon */
116
+ export function stopDaemon() {
117
+ const pid = getDaemonPid();
118
+ if (!pid) {
119
+ console.log('No daemon running.');
120
+ return false;
121
+ }
122
+ try {
123
+ process.kill(pid, 'SIGTERM');
124
+ console.log(`Daemon stopped (PID: ${pid})`);
125
+ return true;
126
+ }
127
+ catch {
128
+ console.log('Daemon process not found, cleaning up PID file.');
129
+ try {
130
+ const { unlinkSync } = require('node:fs');
131
+ unlinkSync(PID_FILE);
132
+ }
133
+ catch { /* ignore */ }
134
+ return false;
135
+ }
136
+ }
137
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/daemon/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ /** Main hook handler - reads stdin JSON, processes event, outputs response */
2
+ export declare function handleHook(): Promise<void>;
@@ -0,0 +1,99 @@
1
+ import { loadState, saveState, processHookEvent } from '../core/pet.js';
2
+ import { checkEvolution, applyEvolution, getEvolutionInfo } from '../core/evolution.js';
3
+ import { RARITY_INFO } from '../core/rarity.js';
4
+ import { evolutionAnimation, levelUpNotification, stageUpNotification, greetingMessage } from '../render/animation.js';
5
+ import { triggerAnim } from '../render/anim-state.js';
6
+ import { syncPetToServer, loadAuth } from '../core/sync.js';
7
+ /** Main hook handler - reads stdin JSON, processes event, outputs response */
8
+ export async function handleHook() {
9
+ const state = loadState();
10
+ if (!state) {
11
+ // No pet initialized yet, silently exit
12
+ process.exit(0);
13
+ }
14
+ // Read stdin
15
+ const chunks = [];
16
+ for await (const chunk of process.stdin) {
17
+ chunks.push(chunk);
18
+ }
19
+ const inputStr = Buffer.concat(chunks).toString('utf-8');
20
+ let input;
21
+ try {
22
+ input = JSON.parse(inputStr);
23
+ }
24
+ catch {
25
+ process.exit(0);
26
+ }
27
+ // Process the event
28
+ const messages = processHookEvent(state, input);
29
+ // Check for evolution opportunity
30
+ const prevEvolution = state.evolution;
31
+ const evoCandidate = checkEvolution(state);
32
+ if (evoCandidate && evoCandidate !== state.evolution) {
33
+ applyEvolution(state, evoCandidate);
34
+ saveState(state);
35
+ }
36
+ // Trigger animations based on event type
37
+ if (input.hook_event_name === 'PostToolUse') {
38
+ triggerAnim('exp');
39
+ }
40
+ else if (input.hook_event_name === 'UserPromptSubmit') {
41
+ triggerAnim('exp');
42
+ }
43
+ else if (input.hook_event_name === 'SessionStart') {
44
+ triggerAnim('pat'); // greeting animation
45
+ }
46
+ // Build output
47
+ const output = [];
48
+ const systemMessages = [];
49
+ for (const msg of messages) {
50
+ if (msg === 'greeting') {
51
+ output.push(greetingMessage(state.name, state.mood, state.hunger));
52
+ }
53
+ else if (msg.startsWith('level_up:')) {
54
+ const level = parseInt(msg.split(':')[1]);
55
+ const color = RARITY_INFO[state.rarity].color;
56
+ systemMessages.push(levelUpNotification(state.name, level, color));
57
+ triggerAnim('levelup');
58
+ }
59
+ else if (msg.startsWith('stage_up:')) {
60
+ const stage = msg.split(':')[1];
61
+ const color = RARITY_INFO[state.rarity].color;
62
+ systemMessages.push(stageUpNotification(state.name, stage, color));
63
+ triggerAnim('levelup');
64
+ }
65
+ else if (msg.startsWith('evolution:')) {
66
+ const evoName = msg.split(':')[1];
67
+ const evo = getEvolutionInfo(state.species, evoName);
68
+ if (evo) {
69
+ const color = RARITY_INFO[state.rarity].color;
70
+ const fromName = state.species;
71
+ systemMessages.push(evolutionAnimation(state.name, fromName, evo.name, evo.nameZh, color));
72
+ // Clear pending evolution after showing
73
+ state.pendingEvolution = null;
74
+ saveState(state);
75
+ }
76
+ }
77
+ }
78
+ // Background sync to server (fire and forget)
79
+ if (loadAuth()) {
80
+ syncPetToServer(state).catch(() => { });
81
+ }
82
+ // Output for Claude Code hooks:
83
+ // - For SessionStart: stdout text becomes context Claude sees
84
+ // - For other hooks: use JSON with systemMessage for user-visible output
85
+ if (input.hook_event_name === 'SessionStart') {
86
+ // Plain text output for SessionStart context
87
+ if (output.length > 0) {
88
+ process.stdout.write(output.join('\n') + '\n');
89
+ }
90
+ }
91
+ // System messages are shown to user in TUI
92
+ if (systemMessages.length > 0) {
93
+ const response = JSON.stringify({
94
+ systemMessage: systemMessages.join('\n'),
95
+ });
96
+ process.stdout.write(response);
97
+ }
98
+ }
99
+ //# sourceMappingURL=handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/hooks/handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { AnimState, AnimTrigger } from './frames.js';
2
+ /** Read current animation state from disk */
3
+ export declare function loadAnimState(): AnimState | null;
4
+ /** Write animation trigger to disk */
5
+ export declare function triggerAnim(trigger: AnimTrigger): void;
@@ -0,0 +1,24 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ const ANIM_FILE = join(homedir(), '.claude-minipet', 'anim.json');
5
+ /** Read current animation state from disk */
6
+ export function loadAnimState() {
7
+ try {
8
+ const raw = readFileSync(ANIM_FILE, 'utf-8');
9
+ return JSON.parse(raw);
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ }
15
+ /** Write animation trigger to disk */
16
+ export function triggerAnim(trigger) {
17
+ const state = {
18
+ trigger,
19
+ triggeredAt: Date.now(),
20
+ frame: 0,
21
+ };
22
+ writeFileSync(ANIM_FILE, JSON.stringify(state), 'utf-8');
23
+ }
24
+ //# sourceMappingURL=anim-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anim-state.js","sourceRoot":"","sources":["../../src/render/anim-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGlC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,iBAAiB,EAAE,WAAW,CAAC,CAAC;AAElE,6CAA6C;AAC7C,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAc,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,sCAAsC;AACtC,MAAM,UAAU,WAAW,CAAC,OAAoB;IAC9C,MAAM,KAAK,GAAc;QACvB,OAAO;QACP,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE;QACvB,KAAK,EAAE,CAAC;KACT,CAAC;IACF,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { Color } from '../core/types.js';
2
+ /** Mood-based eye characters */
3
+ export declare function getEyeChars(mood: number): string;
4
+ /** Mood-based mouth characters */
5
+ export declare function getMouthChars(mood: number): string;
6
+ /** Generate evolution animation text */
7
+ export declare function evolutionAnimation(petName: string, fromName: string, toName: string, toNameZh: string, rarityColor: Color): string;
8
+ /** Generate level up notification */
9
+ export declare function levelUpNotification(petName: string, level: number, color: Color): string;
10
+ /** Generate stage up notification */
11
+ export declare function stageUpNotification(petName: string, stage: string, color: Color): string;
12
+ /** Generate greeting message */
13
+ export declare function greetingMessage(petName: string, mood: number, hunger: number): string;
14
+ /** Shiny border frame */
15
+ export declare function shinyBorder(lines: string[], color: Color): string[];
@@ -0,0 +1,94 @@
1
+ import { fg, RESET, BOLD, BLINK } from './pixel.js';
2
+ /** Mood-based eye characters */
3
+ export function getEyeChars(mood) {
4
+ if (mood >= 80)
5
+ return '★';
6
+ if (mood >= 60)
7
+ return '●';
8
+ if (mood >= 40)
9
+ return '●';
10
+ if (mood >= 20)
11
+ return '◦';
12
+ return 'x';
13
+ }
14
+ /** Mood-based mouth characters */
15
+ export function getMouthChars(mood) {
16
+ if (mood >= 80)
17
+ return '▽';
18
+ if (mood >= 60)
19
+ return '‿';
20
+ if (mood >= 40)
21
+ return '─';
22
+ if (mood >= 20)
23
+ return '╥';
24
+ return '∿';
25
+ }
26
+ /** Generate evolution animation text */
27
+ export function evolutionAnimation(petName, fromName, toName, toNameZh, rarityColor) {
28
+ const c = fg(rarityColor);
29
+ const gold = fg({ r: 255, g: 214, b: 10 });
30
+ const lines = [
31
+ '',
32
+ `${gold} ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨${RESET}`,
33
+ `${gold} ✨ ✨${RESET}`,
34
+ `${gold} ✨ ${BOLD}${petName} 正在进化...!${RESET}${gold} ✨${RESET}`,
35
+ `${gold} ✨ ✨${RESET}`,
36
+ `${gold} ✨ ${c}${fromName}${RESET}${gold} → ${c}${BOLD}${toNameZh} ${toName}${RESET}${gold} ✨${RESET}`,
37
+ `${gold} ✨ ✨${RESET}`,
38
+ `${gold} ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨${RESET}`,
39
+ '',
40
+ ];
41
+ return lines.join('\n');
42
+ }
43
+ /** Generate level up notification */
44
+ export function levelUpNotification(petName, level, color) {
45
+ const c = fg(color);
46
+ return `${c}⬆${RESET} ${BOLD}${petName}${RESET} 升到了 ${c}${BOLD}Lv.${level}${RESET}!`;
47
+ }
48
+ /** Generate stage up notification */
49
+ export function stageUpNotification(petName, stage, color) {
50
+ const c = fg(color);
51
+ const stageNames = {
52
+ baby: '幼年体',
53
+ growth: '成长体',
54
+ final: '完全体',
55
+ };
56
+ const stageName = stageNames[stage] ?? stage;
57
+ return [
58
+ '',
59
+ `${c} ═══════════════════════${RESET}`,
60
+ `${c} ║ ${BOLD}${petName} 成长了!${RESET}${c} ║${RESET}`,
61
+ `${c} ║ 进入 ${BOLD}${stageName}${RESET}${c} 阶段! ║${RESET}`,
62
+ `${c} ═══════════════════════${RESET}`,
63
+ '',
64
+ ].join('\n');
65
+ }
66
+ /** Generate greeting message */
67
+ export function greetingMessage(petName, mood, hunger) {
68
+ let msg;
69
+ if (hunger < 20) {
70
+ msg = `${petName} 饿坏了...快喂喂它吧!`;
71
+ }
72
+ else if (mood >= 80) {
73
+ msg = `${petName} 很开心地迎接你!`;
74
+ }
75
+ else if (mood >= 50) {
76
+ msg = `${petName} 看到你来了,轻轻叫了一声。`;
77
+ }
78
+ else {
79
+ msg = `${petName} 看起来有点低落...`;
80
+ }
81
+ return msg;
82
+ }
83
+ /** Shiny border frame */
84
+ export function shinyBorder(lines, color) {
85
+ const c = fg(color);
86
+ const sparkles = [BLINK, '✦', '✧', '⋆'];
87
+ const s = sparkles[Math.floor(Date.now() / 500) % sparkles.length];
88
+ return [
89
+ `${c}${s} ─────────────── ${s}${RESET}`,
90
+ ...lines.map(l => `${c}│${RESET} ${l} ${c}│${RESET}`),
91
+ `${c}${s} ─────────────── ${s}${RESET}`,
92
+ ];
93
+ }
94
+ //# sourceMappingURL=animation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animation.js","sourceRoot":"","sources":["../../src/render/animation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Animation system: each sprite has multiple frames.
3
+ * Frame 0 = default idle
4
+ * Frame 1 = idle variant (blink/breathe/sway)
5
+ * Frame 2 = happy (EXP gain, pat)
6
+ * Frame 3 = eating (feed)
7
+ * Frame 4 = excited (level up)
8
+ *
9
+ * We only define variant pixels — the rest falls through to the base sprite.
10
+ * A "frame override" is a sparse map of [y][x] → pixel value.
11
+ */
12
+ export type FrameOverride = Map<string, number>;
13
+ /** Animation trigger types */
14
+ export type AnimTrigger = 'idle' | 'exp' | 'feed' | 'pat' | 'levelup' | 'evolve' | 'hungry' | 'sad';
15
+ /** Animation state stored on disk */
16
+ export interface AnimState {
17
+ trigger: AnimTrigger;
18
+ triggeredAt: number;
19
+ frame: number;
20
+ }
21
+ /** How long each triggered animation lasts (ms) */
22
+ export declare const ANIM_DURATION: Record<AnimTrigger, number>;
23
+ /** Number of frames per trigger animation */
24
+ export declare const ANIM_FRAMES: Record<AnimTrigger, number>;
25
+ /**
26
+ * Get the current animation frame index based on time.
27
+ * For idle: cycles through frames based on current time.
28
+ * For triggered: cycles through frames within duration, then returns to idle.
29
+ */
30
+ export declare function getCurrentFrame(anim: AnimState | null): {
31
+ trigger: AnimTrigger;
32
+ frame: number;
33
+ };
34
+ /**
35
+ * Eye overrides per mood and animation state.
36
+ * Returns the character to use for eyes in the sprite.
37
+ */
38
+ export declare function getEyeForAnim(trigger: AnimTrigger, frame: number, mood: number): string;
39
+ /**
40
+ * Mouth overrides per animation state.
41
+ */
42
+ export declare function getMouthForAnim(trigger: AnimTrigger, frame: number, mood: number): string;
43
+ /**
44
+ * Body position offset for bouncy animations.
45
+ * Returns [dx, dy] to shift the sprite.
46
+ */
47
+ export declare function getBodyOffset(trigger: AnimTrigger, frame: number): [number, number];
48
+ /**
49
+ * Status line decoration text per animation.
50
+ */
51
+ export declare function getAnimDecoration(trigger: AnimTrigger, frame: number): string;
@@ -0,0 +1,151 @@
1
+ /** How long each triggered animation lasts (ms) */
2
+ export const ANIM_DURATION = {
3
+ idle: 0, // permanent cycle
4
+ exp: 3000, // 3 seconds
5
+ feed: 4000, // 4 seconds
6
+ pat: 3000,
7
+ levelup: 6000,
8
+ evolve: 8000,
9
+ hungry: 5000,
10
+ sad: 5000,
11
+ };
12
+ /** Number of frames per trigger animation */
13
+ export const ANIM_FRAMES = {
14
+ idle: 3,
15
+ exp: 2,
16
+ feed: 3,
17
+ pat: 2,
18
+ levelup: 3,
19
+ evolve: 2,
20
+ hungry: 2,
21
+ sad: 2,
22
+ };
23
+ /**
24
+ * Get the current animation frame index based on time.
25
+ * For idle: cycles through frames based on current time.
26
+ * For triggered: cycles through frames within duration, then returns to idle.
27
+ */
28
+ export function getCurrentFrame(anim) {
29
+ if (!anim || anim.trigger === 'idle') {
30
+ // Idle: cycle through 3 frames, switching every 5 seconds
31
+ const frame = Math.floor(Date.now() / 2000) % ANIM_FRAMES.idle;
32
+ return { trigger: 'idle', frame };
33
+ }
34
+ const elapsed = Date.now() - anim.triggeredAt;
35
+ const duration = ANIM_DURATION[anim.trigger];
36
+ if (elapsed > duration) {
37
+ // Animation expired, back to idle
38
+ const frame = Math.floor(Date.now() / 2000) % ANIM_FRAMES.idle;
39
+ return { trigger: 'idle', frame };
40
+ }
41
+ // Cycle through animation frames during duration
42
+ const totalFrames = ANIM_FRAMES[anim.trigger];
43
+ const frameTime = duration / (totalFrames * 2); // each frame shows twice
44
+ const frame = Math.floor(elapsed / frameTime) % totalFrames;
45
+ return { trigger: anim.trigger, frame };
46
+ }
47
+ /**
48
+ * Eye overrides per mood and animation state.
49
+ * Returns the character to use for eyes in the sprite.
50
+ */
51
+ export function getEyeForAnim(trigger, frame, mood) {
52
+ switch (trigger) {
53
+ case 'idle':
54
+ if (frame === 1)
55
+ return '-'; // blink
56
+ if (mood >= 80)
57
+ return '*'; // star eyes
58
+ if (mood >= 40)
59
+ return 'o';
60
+ return '.';
61
+ case 'exp':
62
+ return frame === 0 ? '^' : '*'; // excited
63
+ case 'feed':
64
+ return frame === 1 ? '-' : '>'; // happy eating
65
+ case 'pat':
66
+ return frame === 0 ? '~' : '^'; // content
67
+ case 'levelup':
68
+ return ['*', '!', '*'][frame] ?? '*'; // sparkling
69
+ case 'evolve':
70
+ return frame === 0 ? '!' : '*';
71
+ case 'hungry':
72
+ return frame === 0 ? '.' : ';'; // sad/weak
73
+ case 'sad':
74
+ return frame === 0 ? 'T' : ';';
75
+ default:
76
+ return 'o';
77
+ }
78
+ }
79
+ /**
80
+ * Mouth overrides per animation state.
81
+ */
82
+ export function getMouthForAnim(trigger, frame, mood) {
83
+ switch (trigger) {
84
+ case 'idle':
85
+ if (frame === 2)
86
+ return 'o'; // yawn
87
+ if (mood >= 60)
88
+ return 'v'; // smile
89
+ if (mood >= 30)
90
+ return '-';
91
+ return '~';
92
+ case 'exp':
93
+ return frame === 0 ? 'v' : 'V'; // grin
94
+ case 'feed':
95
+ return ['o', 'O', 'v'][frame] ?? 'o'; // nom nom
96
+ case 'pat':
97
+ return frame === 0 ? 'v' : 'w'; // purr
98
+ case 'levelup':
99
+ return ['V', 'O', 'V'][frame] ?? 'V'; // celebrating
100
+ case 'evolve':
101
+ return frame === 0 ? 'O' : 'V';
102
+ case 'hungry':
103
+ return frame === 0 ? '~' : '.'; // weak
104
+ case 'sad':
105
+ return frame === 0 ? 'n' : '~';
106
+ default:
107
+ return '-';
108
+ }
109
+ }
110
+ /**
111
+ * Body position offset for bouncy animations.
112
+ * Returns [dx, dy] to shift the sprite.
113
+ */
114
+ export function getBodyOffset(trigger, frame) {
115
+ switch (trigger) {
116
+ case 'exp':
117
+ return frame === 1 ? [0, -1] : [0, 0]; // bounce up
118
+ case 'pat':
119
+ return frame === 1 ? [0, 0] : [0, 0];
120
+ case 'levelup':
121
+ return [0, frame === 1 ? -1 : 0]; // bounce
122
+ case 'feed':
123
+ return [frame === 1 ? 1 : 0, 0]; // lean forward
124
+ default:
125
+ return [0, 0];
126
+ }
127
+ }
128
+ /**
129
+ * Status line decoration text per animation.
130
+ */
131
+ export function getAnimDecoration(trigger, frame) {
132
+ switch (trigger) {
133
+ case 'exp':
134
+ return frame === 0 ? ' +EXP!' : ' +EXP!';
135
+ case 'feed':
136
+ return [' nom~', ' nom nom~', ' yum!'][frame] ?? '';
137
+ case 'pat':
138
+ return frame === 0 ? ' purr~' : ' purr~~';
139
+ case 'levelup':
140
+ return [' LEVEL UP!', ' ★LEVEL UP!★', ' LEVEL UP!'][frame] ?? '';
141
+ case 'evolve':
142
+ return frame === 0 ? ' EVOLVING...' : ' ★EVOLVED!★';
143
+ case 'hungry':
144
+ return frame === 0 ? ' hungry...' : ' feed me...';
145
+ case 'sad':
146
+ return frame === 0 ? ' ...' : ' zzz';
147
+ default:
148
+ return '';
149
+ }
150
+ }
151
+ //# sourceMappingURL=frames.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frames.js","sourceRoot":"","sources":["../../src/render/frames.ts"],"names":[],"mappings":"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"}