clawmate 1.4.0 → 1.4.2
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/index.js +441 -442
- package/main/ai-bridge.js +59 -59
- package/main/ai-connector.js +60 -60
- package/main/autostart.js +6 -6
- package/main/desktop-path.js +4 -4
- package/main/file-command-parser.js +46 -46
- package/main/file-ops.js +27 -27
- package/main/index.js +17 -17
- package/main/ipc-handlers.js +24 -24
- package/main/manifest.js +2 -2
- package/main/platform.js +16 -16
- package/main/smart-file-ops.js +64 -64
- package/main/store.js +1 -1
- package/main/telegram.js +137 -137
- package/main/tray.js +61 -61
- package/main/updater.js +13 -13
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
- package/preload/preload.js +18 -18
- package/renderer/css/effects.css +6 -6
- package/renderer/css/pet.css +8 -8
- package/renderer/css/speech.css +5 -5
- package/renderer/first-run.html +14 -14
- package/renderer/index.html +4 -4
- package/renderer/js/ai-controller.js +91 -91
- package/renderer/js/app.js +24 -24
- package/renderer/js/browser-watcher.js +32 -32
- package/renderer/js/character.js +33 -33
- package/renderer/js/interactions.js +21 -21
- package/renderer/js/memory.js +60 -60
- package/renderer/js/metrics.js +141 -141
- package/renderer/js/mode-manager.js +13 -13
- package/renderer/js/pet-engine.js +236 -236
- package/renderer/js/speech.js +19 -19
- package/renderer/js/state-machine.js +23 -23
- package/renderer/js/time-aware.js +15 -15
- package/renderer/launcher.html +8 -8
- package/shared/constants.js +11 -11
- package/shared/messages.js +130 -130
- package/shared/personalities.js +44 -44
- package/skills/launch-pet/index.js +57 -47
- package/skills/launch-pet/skill.json +12 -23
package/renderer/js/app.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ClawMate
|
|
2
|
+
* ClawMate renderer initialization
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* AI (
|
|
6
|
-
*
|
|
4
|
+
* Architecture:
|
|
5
|
+
* AI (brain) <-> AI Bridge (WebSocket) <-> AI Controller (renderer)
|
|
6
|
+
* |
|
|
7
7
|
* StateMachine / PetEngine / Speech
|
|
8
8
|
*
|
|
9
|
-
* AI
|
|
10
|
-
* AI
|
|
9
|
+
* When AI connected: AI decides all behaviors/speech/emotions
|
|
10
|
+
* When AI disconnected: Autonomous mode (FSM-based) plays alone
|
|
11
11
|
*/
|
|
12
12
|
(async function initClawMate() {
|
|
13
13
|
const petContainer = document.getElementById('pet-container');
|
|
14
14
|
|
|
15
|
-
//
|
|
15
|
+
// Create character canvas
|
|
16
16
|
Character.createCanvas(petContainer);
|
|
17
17
|
|
|
18
|
-
//
|
|
18
|
+
// Set default colors (Pet mode)
|
|
19
19
|
Character.setColorMap({
|
|
20
20
|
primary: '#ff4f40',
|
|
21
21
|
secondary: '#ff775f',
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
claw: '#ff4f40',
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
//
|
|
28
|
+
// State change callback
|
|
29
29
|
StateMachine.setOnStateChange((prevState, newState) => {
|
|
30
30
|
if (newState === 'sleeping') {
|
|
31
31
|
const pet = document.getElementById('pet-container');
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
Interactions.spawnStarEffect();
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
//
|
|
48
|
+
// Record motion history
|
|
49
49
|
Memory.recordMotion(newState);
|
|
50
50
|
|
|
51
|
-
//
|
|
51
|
+
// Report state changes to AI
|
|
52
52
|
if (window.clawmate.reportToAI) {
|
|
53
53
|
window.clawmate.reportToAI('state_change', {
|
|
54
54
|
from: prevState, to: newState,
|
|
@@ -56,48 +56,48 @@
|
|
|
56
56
|
}
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
//
|
|
59
|
+
// Initialize movement engine
|
|
60
60
|
PetEngine.init(petContainer);
|
|
61
61
|
|
|
62
|
-
//
|
|
62
|
+
// Initialize mode manager
|
|
63
63
|
await ModeManager.init();
|
|
64
64
|
|
|
65
|
-
//
|
|
65
|
+
// Initialize memory (including evolution state)
|
|
66
66
|
await Memory.init();
|
|
67
67
|
|
|
68
|
-
// AI
|
|
68
|
+
// Initialize AI controller (AI connection management)
|
|
69
69
|
AIController.init();
|
|
70
70
|
|
|
71
|
-
//
|
|
71
|
+
// Initialize interactions
|
|
72
72
|
Interactions.init();
|
|
73
73
|
|
|
74
|
-
//
|
|
74
|
+
// Initialize time awareness (only proactive in autonomous mode)
|
|
75
75
|
TimeAware.init();
|
|
76
76
|
|
|
77
|
-
//
|
|
77
|
+
// Initialize metrics collector (optional -- app works fine without it)
|
|
78
78
|
if (typeof Metrics !== 'undefined') {
|
|
79
79
|
Metrics.init();
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
//
|
|
82
|
+
// Initialize browser watcher (nosy mode)
|
|
83
83
|
if (typeof BrowserWatcher !== 'undefined') {
|
|
84
84
|
BrowserWatcher.init();
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
//
|
|
87
|
+
// Start engine
|
|
88
88
|
PetEngine.start();
|
|
89
89
|
|
|
90
|
-
//
|
|
90
|
+
// Speech bubble position update loop
|
|
91
91
|
setInterval(() => {
|
|
92
92
|
Speech.updatePosition();
|
|
93
93
|
}, 100);
|
|
94
94
|
|
|
95
|
-
// AI
|
|
95
|
+
// Display AI connection status
|
|
96
96
|
const connected = await window.clawmate.isAIConnected();
|
|
97
97
|
if (connected) {
|
|
98
|
-
Speech.show('
|
|
98
|
+
Speech.show('Connected to AI. Awaiting instructions...');
|
|
99
99
|
} else {
|
|
100
|
-
Speech.show('
|
|
100
|
+
Speech.show('Hi! I can play on my own just fine!');
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
addDynamicStyles();
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Browser activity watcher + AI comment system
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* AI
|
|
6
|
-
* AI
|
|
4
|
+
* Two modes:
|
|
5
|
+
* When AI connected: Send window title + screen capture + cursor position to AI -> AI generates contextual comments
|
|
6
|
+
* When AI disconnected: Fall back to preset messages (autonomous mode)
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* 1.
|
|
10
|
-
* 2.
|
|
11
|
-
* 3. AI
|
|
12
|
-
* 4.
|
|
8
|
+
* Behavior:
|
|
9
|
+
* 1. Check active window title + cursor position every 15 seconds
|
|
10
|
+
* 2. Report context to AI when browser/app detected (title + screen capture)
|
|
11
|
+
* 3. AI analyzes title/capture and generates situation-appropriate comments
|
|
12
|
+
* 4. Fall back to preset messages in autonomous mode
|
|
13
13
|
*/
|
|
14
14
|
const BrowserWatcher = (() => {
|
|
15
|
-
const CHECK_INTERVAL = 15000; //
|
|
16
|
-
const AI_COOLDOWN = 45000; // AI
|
|
17
|
-
const FALLBACK_COOLDOWN = 90000; //
|
|
18
|
-
const COMMENT_CHANCE = 0.4; //
|
|
19
|
-
const SITE_CHANGE_BONUS = 0.3; //
|
|
15
|
+
const CHECK_INTERVAL = 15000; // Active window check interval (15s)
|
|
16
|
+
const AI_COOLDOWN = 45000; // AI comment cooldown (45s)
|
|
17
|
+
const FALLBACK_COOLDOWN = 90000; // Autonomous mode comment cooldown (90s)
|
|
18
|
+
const COMMENT_CHANCE = 0.4; // Comment probability (40%)
|
|
19
|
+
const SITE_CHANGE_BONUS = 0.3; // Additional probability on site change
|
|
20
20
|
|
|
21
21
|
let intervalId = null;
|
|
22
22
|
let lastCategory = null;
|
|
@@ -26,7 +26,7 @@ const BrowserWatcher = (() => {
|
|
|
26
26
|
|
|
27
27
|
function init() {
|
|
28
28
|
intervalId = setInterval(check, CHECK_INTERVAL);
|
|
29
|
-
//
|
|
29
|
+
// First check after 10 seconds (skip right after app start)
|
|
30
30
|
setTimeout(check, 10000);
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -34,7 +34,7 @@ const BrowserWatcher = (() => {
|
|
|
34
34
|
if (!enabled) return;
|
|
35
35
|
if (typeof Speech === 'undefined') return;
|
|
36
36
|
|
|
37
|
-
//
|
|
37
|
+
// Don't comment when in sleeping state
|
|
38
38
|
if (typeof StateMachine !== 'undefined' && StateMachine.getState() === 'sleeping') return;
|
|
39
39
|
|
|
40
40
|
try {
|
|
@@ -47,25 +47,25 @@ const BrowserWatcher = (() => {
|
|
|
47
47
|
|
|
48
48
|
const now = Date.now();
|
|
49
49
|
|
|
50
|
-
//
|
|
50
|
+
// Category matching (used by both AI and autonomous modes)
|
|
51
51
|
const msgs = window._messages;
|
|
52
52
|
const match = msgs?.browsing ? findCategory(titleLower, msgs.browsing) : null;
|
|
53
53
|
const category = match?.category || 'unknown';
|
|
54
54
|
|
|
55
|
-
//
|
|
55
|
+
// Cooldown check
|
|
56
56
|
const isAI = typeof AIController !== 'undefined' && AIController.isConnected();
|
|
57
57
|
const cooldown = isAI ? AI_COOLDOWN : FALLBACK_COOLDOWN;
|
|
58
58
|
if (now - lastCommentTime < cooldown) return;
|
|
59
59
|
|
|
60
|
-
//
|
|
60
|
+
// Skip if same category and title unchanged
|
|
61
61
|
if (category === lastCategory && !titleChanged) return;
|
|
62
62
|
|
|
63
|
-
//
|
|
63
|
+
// Probability check
|
|
64
64
|
let chance = COMMENT_CHANCE;
|
|
65
65
|
if (titleChanged) chance += SITE_CHANGE_BONUS;
|
|
66
66
|
if (Math.random() > chance) return;
|
|
67
67
|
|
|
68
|
-
// === AI vs
|
|
68
|
+
// === AI vs autonomous mode branch ===
|
|
69
69
|
if (isAI) {
|
|
70
70
|
await reportBrowsingToAI(title, category, titleChanged);
|
|
71
71
|
} else {
|
|
@@ -75,19 +75,19 @@ const BrowserWatcher = (() => {
|
|
|
75
75
|
lastCategory = category;
|
|
76
76
|
lastCommentTime = now;
|
|
77
77
|
} catch {
|
|
78
|
-
// IPC
|
|
78
|
+
// Ignore IPC failure
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* AI
|
|
83
|
+
* Send browsing context to AI
|
|
84
|
+
* Transmit title + cursor position + screen capture together
|
|
85
|
+
* AI analyzes and generates comments
|
|
86
86
|
*/
|
|
87
87
|
async function reportBrowsingToAI(title, category, titleChanged) {
|
|
88
88
|
if (!window.clawmate.reportToAI) return;
|
|
89
89
|
|
|
90
|
-
//
|
|
90
|
+
// Get cursor position
|
|
91
91
|
let cursorX = 0, cursorY = 0;
|
|
92
92
|
try {
|
|
93
93
|
if (window.clawmate.getCursorPosition) {
|
|
@@ -97,7 +97,7 @@ const BrowserWatcher = (() => {
|
|
|
97
97
|
}
|
|
98
98
|
} catch {}
|
|
99
99
|
|
|
100
|
-
//
|
|
100
|
+
// Screen capture (for AI to visually analyze page content)
|
|
101
101
|
let screenData = null;
|
|
102
102
|
try {
|
|
103
103
|
const capture = await window.clawmate.screen.capture();
|
|
@@ -110,7 +110,7 @@ const BrowserWatcher = (() => {
|
|
|
110
110
|
}
|
|
111
111
|
} catch {}
|
|
112
112
|
|
|
113
|
-
//
|
|
113
|
+
// Send unified browsing report
|
|
114
114
|
window.clawmate.reportToAI('browsing', {
|
|
115
115
|
title,
|
|
116
116
|
category,
|
|
@@ -123,7 +123,7 @@ const BrowserWatcher = (() => {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/**
|
|
126
|
-
*
|
|
126
|
+
* Autonomous mode fallback: display preset messages
|
|
127
127
|
*/
|
|
128
128
|
function showFallbackComment(match) {
|
|
129
129
|
if (!match?.data?.comments) return;
|
|
@@ -132,7 +132,7 @@ const BrowserWatcher = (() => {
|
|
|
132
132
|
const comment = comments[Math.floor(Math.random() * comments.length)];
|
|
133
133
|
Speech.show(comment);
|
|
134
134
|
|
|
135
|
-
// 50%
|
|
135
|
+
// 50% chance of excitement animation
|
|
136
136
|
if (typeof StateMachine !== 'undefined') {
|
|
137
137
|
const state = StateMachine.getState();
|
|
138
138
|
if ((state === 'idle' || state === 'walking') && Math.random() < 0.5) {
|
|
@@ -145,8 +145,8 @@ const BrowserWatcher = (() => {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
/**
|
|
148
|
-
*
|
|
149
|
-
* general
|
|
148
|
+
* Category matching (keyword-based)
|
|
149
|
+
* 'general' is only used when no other category matches
|
|
150
150
|
*/
|
|
151
151
|
function findCategory(titleLower, browsingMsgs) {
|
|
152
152
|
let generalMatch = null;
|
package/renderer/js/character.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* 16x16
|
|
2
|
+
* Pixel art lobster (Claw) character renderer
|
|
3
|
+
* 16x16 grid -> rendered as CSS div -> 4x scaled (64x64px)
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* 0 =
|
|
7
|
-
* 3 = dark(
|
|
5
|
+
* Color codes:
|
|
6
|
+
* 0 = transparent, 1 = primary (red), 2 = secondary (light red),
|
|
7
|
+
* 3 = dark (brown), 4 = eye, 5 = pupil, 6 = claw specific
|
|
8
8
|
*/
|
|
9
9
|
const Character = (() => {
|
|
10
10
|
const PIXEL = 4;
|
|
11
11
|
const GRID = 16;
|
|
12
12
|
const SIZE = PIXEL * GRID;
|
|
13
13
|
|
|
14
|
-
// ---
|
|
14
|
+
// --- Frame data (16x16 arrays) ---
|
|
15
15
|
const FRAMES = {
|
|
16
16
|
idle: [
|
|
17
|
-
// Frame 0:
|
|
17
|
+
// Frame 0: Default pose -- claws open
|
|
18
18
|
[
|
|
19
19
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
20
20
|
[0,0,6,6,0,0,0,0,0,0,0,0,6,6,0,0],
|
|
@@ -33,7 +33,7 @@ const Character = (() => {
|
|
|
33
33
|
[0,3,3,3,0,0,0,3,3,0,0,0,3,3,3,0],
|
|
34
34
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
35
35
|
],
|
|
36
|
-
// Frame 1:
|
|
36
|
+
// Frame 1: Claws closed (wave)
|
|
37
37
|
[
|
|
38
38
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
39
39
|
[0,0,6,6,0,0,0,0,0,0,0,0,6,6,0,0],
|
|
@@ -55,7 +55,7 @@ const Character = (() => {
|
|
|
55
55
|
],
|
|
56
56
|
|
|
57
57
|
walk: [
|
|
58
|
-
// Frame 0:
|
|
58
|
+
// Frame 0: Left leg set extended forward wide, claws open
|
|
59
59
|
[
|
|
60
60
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
61
61
|
[0,0,6,6,0,0,0,0,0,0,0,0,6,6,0,0],
|
|
@@ -74,7 +74,7 @@ const Character = (() => {
|
|
|
74
74
|
[3,3,0,0,0,0,0,3,3,0,0,0,0,0,3,3],
|
|
75
75
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
76
76
|
],
|
|
77
|
-
// Frame 1:
|
|
77
|
+
// Frame 1: Both leg sets together (contact moment), claws half-closed
|
|
78
78
|
[
|
|
79
79
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
80
80
|
[0,0,6,6,0,0,0,0,0,0,0,0,6,6,0,0],
|
|
@@ -93,7 +93,7 @@ const Character = (() => {
|
|
|
93
93
|
[0,0,0,3,3,0,0,3,3,0,0,3,3,0,0,0],
|
|
94
94
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
95
95
|
],
|
|
96
|
-
// Frame 2:
|
|
96
|
+
// Frame 2: Right leg set extended forward wide, claws closed
|
|
97
97
|
[
|
|
98
98
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
99
99
|
[0,0,6,6,0,0,0,0,0,0,0,0,6,6,0,0],
|
|
@@ -112,7 +112,7 @@ const Character = (() => {
|
|
|
112
112
|
[0,3,3,0,0,0,0,3,3,0,0,0,0,3,3,0],
|
|
113
113
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
114
114
|
],
|
|
115
|
-
// Frame 3:
|
|
115
|
+
// Frame 3: Both leg sets together (contact moment), claws half-open
|
|
116
116
|
[
|
|
117
117
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
118
118
|
[0,0,6,6,0,0,0,0,0,0,0,0,6,6,0,0],
|
|
@@ -134,7 +134,7 @@ const Character = (() => {
|
|
|
134
134
|
],
|
|
135
135
|
|
|
136
136
|
climb: [
|
|
137
|
-
// Frame 0:
|
|
137
|
+
// Frame 0: Climbing -- upper leg set extended, claws open
|
|
138
138
|
[
|
|
139
139
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
140
140
|
[0,0,0,0,6,6,0,0,0,0,0,0,0,0,0,0],
|
|
@@ -153,7 +153,7 @@ const Character = (() => {
|
|
|
153
153
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
154
154
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
155
155
|
],
|
|
156
|
-
// Frame 1:
|
|
156
|
+
// Frame 1: Climbing -- legs together (crossing moment)
|
|
157
157
|
[
|
|
158
158
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
159
159
|
[0,0,0,0,6,6,0,0,0,0,0,0,0,0,0,0],
|
|
@@ -172,7 +172,7 @@ const Character = (() => {
|
|
|
172
172
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
173
173
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
174
174
|
],
|
|
175
|
-
// Frame 2:
|
|
175
|
+
// Frame 2: Climbing -- lower leg set extended, claws closed
|
|
176
176
|
[
|
|
177
177
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
178
178
|
[0,0,0,0,6,6,0,0,0,0,0,0,0,0,0,0],
|
|
@@ -191,7 +191,7 @@ const Character = (() => {
|
|
|
191
191
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
192
192
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
193
193
|
],
|
|
194
|
-
// Frame 3:
|
|
194
|
+
// Frame 3: Climbing -- legs together (crossing moment, opposite side)
|
|
195
195
|
[
|
|
196
196
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
197
197
|
[0,0,0,0,6,6,0,0,0,0,0,0,0,0,0,0],
|
|
@@ -234,7 +234,7 @@ const Character = (() => {
|
|
|
234
234
|
],
|
|
235
235
|
|
|
236
236
|
carry: [
|
|
237
|
-
// Frame 0:
|
|
237
|
+
// Frame 0: Walking with file 1
|
|
238
238
|
[
|
|
239
239
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
240
240
|
[0,0,6,1,1,1,1,0,0,1,1,1,1,6,0,0],
|
|
@@ -253,7 +253,7 @@ const Character = (() => {
|
|
|
253
253
|
[0,3,3,3,0,0,0,3,3,0,0,0,3,3,3,0],
|
|
254
254
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
255
255
|
],
|
|
256
|
-
// Frame 1:
|
|
256
|
+
// Frame 1: Walking with file 2
|
|
257
257
|
[
|
|
258
258
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
259
259
|
[0,0,6,1,1,1,1,0,0,1,1,1,1,6,0,0],
|
|
@@ -275,7 +275,7 @@ const Character = (() => {
|
|
|
275
275
|
],
|
|
276
276
|
|
|
277
277
|
scared: [
|
|
278
|
-
// Frame 0:
|
|
278
|
+
// Frame 0: Startled -- eyes wide
|
|
279
279
|
[
|
|
280
280
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
281
281
|
[0,0,6,6,0,0,0,0,0,0,0,0,6,6,0,0],
|
|
@@ -294,7 +294,7 @@ const Character = (() => {
|
|
|
294
294
|
[0,3,3,3,0,0,0,3,3,0,0,0,3,3,3,0],
|
|
295
295
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
296
296
|
],
|
|
297
|
-
// Frame 1:
|
|
297
|
+
// Frame 1: Flinch backward
|
|
298
298
|
[
|
|
299
299
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
300
300
|
[0,0,0,6,6,0,0,0,0,0,0,6,6,0,0,0],
|
|
@@ -316,7 +316,7 @@ const Character = (() => {
|
|
|
316
316
|
],
|
|
317
317
|
|
|
318
318
|
excited: [
|
|
319
|
-
// Frame 0:
|
|
319
|
+
// Frame 0: Jump up
|
|
320
320
|
[
|
|
321
321
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
322
322
|
[0,6,6,0,0,0,0,0,0,0,0,0,0,6,6,0],
|
|
@@ -335,7 +335,7 @@ const Character = (() => {
|
|
|
335
335
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
336
336
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
337
337
|
],
|
|
338
|
-
// Frame 1:
|
|
338
|
+
// Frame 1: Landing
|
|
339
339
|
[
|
|
340
340
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
341
341
|
[0,0,6,6,0,0,0,0,0,0,0,0,6,6,0,0],
|
|
@@ -357,7 +357,7 @@ const Character = (() => {
|
|
|
357
357
|
],
|
|
358
358
|
};
|
|
359
359
|
|
|
360
|
-
//
|
|
360
|
+
// State -> frameset mapping
|
|
361
361
|
const STATE_FRAMES = {
|
|
362
362
|
idle: 'idle',
|
|
363
363
|
walking: 'walk',
|
|
@@ -370,15 +370,15 @@ const Character = (() => {
|
|
|
370
370
|
interacting: 'excited',
|
|
371
371
|
scared: 'scared',
|
|
372
372
|
excited: 'excited',
|
|
373
|
-
jumping: 'excited', //
|
|
374
|
-
rappelling: 'climb', //
|
|
375
|
-
falling: 'scared', //
|
|
376
|
-
custom: 'walk', //
|
|
373
|
+
jumping: 'excited', // Jump: reuse excited frameset
|
|
374
|
+
rappelling: 'climb', // Rappel: reuse climb frameset
|
|
375
|
+
falling: 'scared', // Falling: reuse scared frameset
|
|
376
|
+
custom: 'walk', // Custom movement: default is walk, can be dynamically changed per pattern
|
|
377
377
|
};
|
|
378
378
|
|
|
379
379
|
let currentCanvas = null;
|
|
380
380
|
let currentColorMap = null;
|
|
381
|
-
let originalFrames = null; //
|
|
381
|
+
let originalFrames = null; // Original frame backup (for reset)
|
|
382
382
|
|
|
383
383
|
function createCanvas(container) {
|
|
384
384
|
const canvas = document.createElement('canvas');
|
|
@@ -436,8 +436,8 @@ const Character = (() => {
|
|
|
436
436
|
}
|
|
437
437
|
|
|
438
438
|
/**
|
|
439
|
-
*
|
|
440
|
-
*
|
|
439
|
+
* Set custom frame data (for AI-generated characters)
|
|
440
|
+
* Backs up existing frames and replaces with new ones
|
|
441
441
|
*/
|
|
442
442
|
function setCustomFrames(newFrames) {
|
|
443
443
|
if (!originalFrames) {
|
|
@@ -454,7 +454,7 @@ const Character = (() => {
|
|
|
454
454
|
}
|
|
455
455
|
|
|
456
456
|
/**
|
|
457
|
-
*
|
|
457
|
+
* Batch set character data (colors + frames)
|
|
458
458
|
* @param {object} data - { colorMap?, frames? }
|
|
459
459
|
*/
|
|
460
460
|
function setCharacterData(data) {
|
|
@@ -467,7 +467,7 @@ const Character = (() => {
|
|
|
467
467
|
}
|
|
468
468
|
|
|
469
469
|
/**
|
|
470
|
-
*
|
|
470
|
+
* Reset to original character
|
|
471
471
|
*/
|
|
472
472
|
function resetCharacter() {
|
|
473
473
|
if (originalFrames) {
|
|
@@ -477,7 +477,7 @@ const Character = (() => {
|
|
|
477
477
|
Object.assign(FRAMES, originalFrames);
|
|
478
478
|
originalFrames = null;
|
|
479
479
|
}
|
|
480
|
-
//
|
|
480
|
+
// Reset to default colors
|
|
481
481
|
setColorMap({
|
|
482
482
|
primary: '#ff4f40',
|
|
483
483
|
secondary: '#ff775f',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Mouse/click/drag interaction system
|
|
3
3
|
*
|
|
4
|
-
* AI
|
|
5
|
-
* AI
|
|
4
|
+
* When AI connected: Events forwarded to AI -> AI decides reactions
|
|
5
|
+
* When AI disconnected: Autonomous reactions (random FSM)
|
|
6
6
|
*/
|
|
7
7
|
const Interactions = (() => {
|
|
8
8
|
let isDragging = false;
|
|
@@ -76,7 +76,7 @@ const Interactions = (() => {
|
|
|
76
76
|
|
|
77
77
|
const endPos = PetEngine.getPosition();
|
|
78
78
|
|
|
79
|
-
//
|
|
79
|
+
// Calculate distance to screen edges
|
|
80
80
|
const screenW = window.innerWidth;
|
|
81
81
|
const screenH = window.innerHeight;
|
|
82
82
|
const charSize = PetEngine.CHAR_SIZE;
|
|
@@ -86,15 +86,15 @@ const Interactions = (() => {
|
|
|
86
86
|
const distRight = screenW - charSize - endPos.x;
|
|
87
87
|
const minEdgeDist = Math.min(distBottom, distTop, distLeft, distRight);
|
|
88
88
|
|
|
89
|
-
//
|
|
90
|
-
//
|
|
89
|
+
// If far enough from edges (near screen center) -> free fall
|
|
90
|
+
// Edge proximity threshold: 15% of the shorter screen dimension
|
|
91
91
|
const edgeThreshold = Math.min(screenW, screenH) * 0.15;
|
|
92
92
|
|
|
93
93
|
if (minEdgeDist > edgeThreshold) {
|
|
94
|
-
//
|
|
94
|
+
// Near screen center: gravity fall to the floor
|
|
95
95
|
PetEngine.startFalling();
|
|
96
96
|
} else {
|
|
97
|
-
//
|
|
97
|
+
// Near edge: snap to the nearest edge as before
|
|
98
98
|
PetEngine.snapToNearestEdge();
|
|
99
99
|
StateMachine.forceState('idle');
|
|
100
100
|
}
|
|
@@ -102,7 +102,7 @@ const Interactions = (() => {
|
|
|
102
102
|
PetEngine.start();
|
|
103
103
|
window.clawmate.setClickThrough(true);
|
|
104
104
|
|
|
105
|
-
//
|
|
105
|
+
// Report drag event to AI + record reaction
|
|
106
106
|
if (dragStartPos) {
|
|
107
107
|
const draggedAction = StateMachine.getState();
|
|
108
108
|
Memory.recordReaction(draggedAction, 'drag');
|
|
@@ -114,14 +114,14 @@ const Interactions = (() => {
|
|
|
114
114
|
const pos = PetEngine.getPosition();
|
|
115
115
|
const currentAction = StateMachine.getState();
|
|
116
116
|
|
|
117
|
-
//
|
|
117
|
+
// Report click event to AI
|
|
118
118
|
AIController.reportClick(pos);
|
|
119
119
|
|
|
120
|
-
//
|
|
120
|
+
// Record user reaction -- click during current action = positive reaction
|
|
121
121
|
Memory.recordReaction(currentAction, 'click');
|
|
122
122
|
|
|
123
|
-
// AI
|
|
124
|
-
// AI
|
|
123
|
+
// When AI connected: AI decides reaction (do nothing, wait for AI response)
|
|
124
|
+
// When AI disconnected: autonomous reaction
|
|
125
125
|
if (AIController.isAutonomous()) {
|
|
126
126
|
StateMachine.forceState('interacting');
|
|
127
127
|
Speech.show(Speech.getReactionMessage());
|
|
@@ -135,26 +135,26 @@ const Interactions = (() => {
|
|
|
135
135
|
const pos = PetEngine.getPosition();
|
|
136
136
|
const currentAction = StateMachine.getState();
|
|
137
137
|
|
|
138
|
-
//
|
|
138
|
+
// Record user reaction
|
|
139
139
|
Memory.recordReaction(currentAction, 'double_click');
|
|
140
140
|
|
|
141
|
-
//
|
|
141
|
+
// Report double-click to AI
|
|
142
142
|
if (window.clawmate.reportToAI) {
|
|
143
143
|
window.clawmate.reportToAI('double_click', { position: pos });
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
//
|
|
146
|
+
// Autonomous mode: double-click = special reaction (jump + excitement)
|
|
147
147
|
if (AIController.isAutonomous()) {
|
|
148
148
|
StateMachine.forceState('excited');
|
|
149
149
|
PetEngine.jumpTo(
|
|
150
150
|
pos.x + (Math.random() - 0.5) * 200,
|
|
151
151
|
Math.max(100, pos.y - 150)
|
|
152
152
|
);
|
|
153
|
-
Speech.show('
|
|
153
|
+
Speech.show('Wow! A double-click!');
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
Memory.recordClick();
|
|
157
|
-
Memory.recordClick(); //
|
|
157
|
+
Memory.recordClick(); // double-click = 2 clicks
|
|
158
158
|
spawnHeartEffect();
|
|
159
159
|
spawnStarEffect();
|
|
160
160
|
}
|
|
@@ -178,14 +178,14 @@ const Interactions = (() => {
|
|
|
178
178
|
const dist = Math.hypot(e.clientX - (pos.x + 32), e.clientY - (pos.y + 32));
|
|
179
179
|
|
|
180
180
|
if (dist < 100) {
|
|
181
|
-
//
|
|
181
|
+
// Record user reaction -- cursor approach = showing interest
|
|
182
182
|
const curAction = StateMachine.getState();
|
|
183
183
|
Memory.recordReaction(curAction, 'cursor_near');
|
|
184
184
|
|
|
185
|
-
//
|
|
185
|
+
// Report cursor proximity to AI
|
|
186
186
|
AIController.reportCursorNear(dist);
|
|
187
187
|
|
|
188
|
-
// AI
|
|
188
|
+
// When AI disconnected: autonomous reaction
|
|
189
189
|
if (AIController.isAutonomous()) {
|
|
190
190
|
const state = StateMachine.getState();
|
|
191
191
|
if (state === 'idle' || state === 'walking') {
|