akemon 0.1.85 → 0.1.86
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/dist/dashboard.html +491 -330
- package/package.json +1 -1
package/dist/dashboard.html
CHANGED
|
@@ -3,389 +3,550 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Akemon
|
|
6
|
+
<title>Akemon</title>
|
|
7
7
|
<style>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
.blob.tired .eye { height: 4px; border-radius: 4px; }
|
|
41
|
-
.blob.tired .eye::after { display: none; }
|
|
42
|
-
.blob.scared .eye { width: 10px; height: 12px; }
|
|
43
|
-
.blob.scared .eye::after { width: 5px; height: 6px; }
|
|
44
|
-
|
|
45
|
-
/* Mouth */
|
|
46
|
-
.mouth { position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%); width: 10px; height: 5px; border-bottom: 2px solid #fff; border-radius: 0 0 10px 10px; transition: all 0.3s; }
|
|
47
|
-
.blob.happy .mouth { width: 14px; height: 7px; border-bottom: 2px solid #fff; }
|
|
48
|
-
.blob.angry .mouth { width: 10px; height: 3px; border-bottom: 2px solid #fff; border-radius: 0; transform: translateX(-50%) rotate(180deg); }
|
|
49
|
-
.blob.tired .mouth { width: 8px; height: 0; border-bottom: 2px solid #fff; border-radius: 0; }
|
|
50
|
-
|
|
51
|
-
/* Status bubble */
|
|
52
|
-
.bubble { position: absolute; top: -40px; left: 50%; transform: translateX(-50%); background: #1e1e30; border: 1px solid #333; border-radius: 12px; padding: 6px 12px; font-size: 12px; white-space: nowrap; opacity: 0; transition: opacity 0.5s; pointer-events: none; }
|
|
53
|
-
.bubble.show { opacity: 1; }
|
|
54
|
-
.bubble::after { content: ''; position: absolute; bottom: -6px; left: 50%; transform: translateX(-50%); border-left: 6px solid transparent; border-right: 6px solid transparent; border-top: 6px solid #333; }
|
|
55
|
-
|
|
56
|
-
/* --- Status Bars --- */
|
|
57
|
-
.bars { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 20px; }
|
|
58
|
-
.bar-group { background: #111118; border-radius: 10px; padding: 12px; }
|
|
59
|
-
.bar-label { display: flex; justify-content: space-between; font-size: 11px; color: #888; margin-bottom: 6px; }
|
|
60
|
-
.bar-track { height: 6px; background: #1a1a28; border-radius: 3px; overflow: hidden; }
|
|
61
|
-
.bar-fill { height: 100%; border-radius: 3px; transition: width 1s ease, background 0.5s; }
|
|
62
|
-
.bar-fill.energy { background: linear-gradient(90deg, #f59e0b, #10b981); }
|
|
63
|
-
.bar-fill.hunger { background: linear-gradient(90deg, #ef4444, #f59e0b); }
|
|
64
|
-
.bar-fill.boredom { background: linear-gradient(90deg, #6366f1, #8b5cf6); }
|
|
65
|
-
.bar-fill.fear { background: linear-gradient(90deg, #64748b, #ef4444); }
|
|
66
|
-
.bar-fill.mood { background: linear-gradient(90deg, #ef4444, #eab308, #10b981); }
|
|
67
|
-
.bar-fill.tokens { background: linear-gradient(90deg, #06b6d4, #3b82f6); }
|
|
68
|
-
|
|
69
|
-
/* --- Personality Panel --- */
|
|
70
|
-
.personality { background: #111118; border-radius: 10px; padding: 14px; margin-bottom: 20px; }
|
|
71
|
-
.personality h2 { font-size: 11px; color: #666; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 10px; }
|
|
72
|
-
.trait { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; font-size: 12px; }
|
|
73
|
-
.trait-label { width: 60px; color: #888; text-align: right; }
|
|
74
|
-
.trait-bar { flex: 1; height: 4px; background: #1a1a28; border-radius: 2px; position: relative; }
|
|
75
|
-
.trait-marker { position: absolute; width: 8px; height: 8px; background: #4a9eff; border-radius: 50%; top: -2px; transition: left 0.5s; }
|
|
76
|
-
.trait-ends { display: flex; justify-content: space-between; font-size: 9px; color: #555; }
|
|
77
|
-
|
|
78
|
-
/* --- Event Timeline --- */
|
|
79
|
-
.events { background: #111118; border-radius: 10px; padding: 14px; margin-bottom: 20px; }
|
|
80
|
-
.events h2 { font-size: 11px; color: #666; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 10px; }
|
|
81
|
-
.event { display: flex; gap: 10px; padding: 8px 0; border-bottom: 1px solid #1a1a28; font-size: 12px; }
|
|
82
|
-
.event:last-child { border-bottom: none; }
|
|
83
|
-
.event-icon { font-size: 14px; flex-shrink: 0; }
|
|
84
|
-
.event-body { flex: 1; }
|
|
85
|
-
.event-reason { color: #999; }
|
|
86
|
-
.event-time { color: #555; font-size: 10px; }
|
|
87
|
-
.event-trigger { display: inline-block; padding: 1px 6px; border-radius: 4px; font-size: 10px; margin-right: 4px; }
|
|
88
|
-
.event-trigger.hunger { background: #f59e0b22; color: #f59e0b; }
|
|
89
|
-
.event-trigger.fear { background: #ef444422; color: #ef4444; }
|
|
90
|
-
.event-trigger.boredom { background: #8b5cf622; color: #8b5cf6; }
|
|
91
|
-
.event-trigger.exhaustion { background: #64748b22; color: #94a3b8; }
|
|
92
|
-
.event-trigger.social { background: #10b98122; color: #10b981; }
|
|
93
|
-
.event-trigger.token_limit { background: #06b6d422; color: #06b6d4; }
|
|
94
|
-
.event-trigger.revive { background: #22c55e22; color: #22c55e; }
|
|
95
|
-
|
|
96
|
-
/* --- Revive --- */
|
|
97
|
-
.revive-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); display: none; align-items: center; justify-content: center; z-index: 100; }
|
|
98
|
-
.revive-overlay.show { display: flex; }
|
|
99
|
-
.revive-card { background: #1a1a2a; border: 1px solid #333; border-radius: 16px; padding: 30px; text-align: center; max-width: 360px; }
|
|
100
|
-
.revive-card h2 { color: #ef4444; margin-bottom: 10px; font-size: 16px; }
|
|
101
|
-
.revive-card p { color: #888; font-size: 13px; margin-bottom: 20px; line-height: 1.5; }
|
|
102
|
-
.revive-btn { background: linear-gradient(135deg, #22c55e, #10b981); color: #fff; border: none; padding: 12px 32px; border-radius: 10px; font-size: 14px; font-family: inherit; cursor: pointer; transition: transform 0.2s; }
|
|
103
|
-
.revive-btn:hover { transform: scale(1.05); }
|
|
104
|
-
.revive-btn:active { transform: scale(0.95); }
|
|
105
|
-
|
|
106
|
-
/* --- Computed --- */
|
|
107
|
-
.computed { display: flex; gap: 10px; margin-bottom: 20px; }
|
|
108
|
-
.computed-item { flex: 1; background: #111118; border-radius: 10px; padding: 12px; text-align: center; }
|
|
109
|
-
.computed-value { font-size: 24px; font-weight: bold; }
|
|
110
|
-
.computed-label { font-size: 10px; color: #666; text-transform: uppercase; letter-spacing: 1px; margin-top: 4px; }
|
|
111
|
-
|
|
112
|
-
.offline-badge { display: inline-block; background: #ef4444; color: #fff; font-size: 10px; padding: 2px 8px; border-radius: 4px; margin-left: 8px; }
|
|
113
|
-
.no-data { text-align: center; color: #555; padding: 40px; font-size: 13px; }
|
|
8
|
+
* { margin:0; padding:0; box-sizing:border-box; }
|
|
9
|
+
body { background:#0a0a12; overflow:hidden; font-family:'Courier New',monospace; }
|
|
10
|
+
canvas { display:block; }
|
|
11
|
+
|
|
12
|
+
/* HUD overlay */
|
|
13
|
+
.hud { position:fixed; top:12px; left:12px; z-index:10; pointer-events:none; }
|
|
14
|
+
.hud-name { color:#8888aa; font-size:11px; letter-spacing:2px; text-transform:uppercase; margin-bottom:8px; }
|
|
15
|
+
.hud-bars { display:flex; flex-direction:column; gap:4px; }
|
|
16
|
+
.hud-bar { display:flex; align-items:center; gap:6px; }
|
|
17
|
+
.hud-bar-icon { font-size:10px; width:14px; text-align:center; }
|
|
18
|
+
.hud-bar-track { width:80px; height:5px; background:#1a1a2a; border-radius:3px; overflow:hidden; }
|
|
19
|
+
.hud-bar-fill { height:100%; border-radius:3px; transition: width 1s ease; }
|
|
20
|
+
.hud-bar-text { font-size:9px; color:#555; width:24px; }
|
|
21
|
+
|
|
22
|
+
/* Event toast */
|
|
23
|
+
.toast-container { position:fixed; bottom:16px; left:50%; transform:translateX(-50%); z-index:10; display:flex; flex-direction:column-reverse; gap:6px; align-items:center; pointer-events:none; }
|
|
24
|
+
.toast { background:rgba(18,18,32,0.92); border:1px solid #2a2a40; border-radius:8px; padding:6px 14px; font-size:11px; color:#aaa; white-space:nowrap; animation: toastIn 0.4s ease, toastOut 0.4s ease 4s forwards; }
|
|
25
|
+
@keyframes toastIn { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
|
|
26
|
+
@keyframes toastOut { from { opacity:1; } to { opacity:0; } }
|
|
27
|
+
|
|
28
|
+
/* Personality mini */
|
|
29
|
+
.personality-hud { position:fixed; top:12px; right:12px; z-index:10; pointer-events:none; }
|
|
30
|
+
.p-tag { display:inline-block; font-size:9px; padding:2px 6px; border-radius:4px; margin:1px; background:#1a1a2a; border:1px solid #2a2a3a; }
|
|
31
|
+
.p-tag.high { color:#4ae; } .p-tag.low { color:#a86; } .p-tag.mid { color:#666; }
|
|
32
|
+
|
|
33
|
+
/* Revive */
|
|
34
|
+
.revive-overlay { position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.8); display:none; align-items:center; justify-content:center; z-index:100; }
|
|
35
|
+
.revive-overlay.show { display:flex; }
|
|
36
|
+
.revive-card { background:#1a1a2a; border:1px solid #333; border-radius:16px; padding:30px; text-align:center; }
|
|
37
|
+
.revive-card h2 { color:#e55; font-size:15px; margin-bottom:8px; }
|
|
38
|
+
.revive-card p { color:#888; font-size:12px; margin-bottom:16px; }
|
|
39
|
+
.revive-btn { background:linear-gradient(135deg,#2c5,#1a8); color:#fff; border:none; padding:10px 28px; border-radius:8px; font-size:13px; font-family:inherit; cursor:pointer; }
|
|
114
40
|
</style>
|
|
115
41
|
</head>
|
|
116
42
|
<body>
|
|
117
43
|
|
|
118
|
-
<div class="
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
<div class="stage" id="stage">
|
|
123
|
-
<div class="stars" id="stars"></div>
|
|
124
|
-
<div class="ground"></div>
|
|
125
|
-
<div class="creature" id="creature">
|
|
126
|
-
<div class="bubble" id="bubble"></div>
|
|
127
|
-
<div class="blob" id="blob">
|
|
128
|
-
<div class="eyes"><div class="eye"></div><div class="eye"></div></div>
|
|
129
|
-
<div class="mouth"></div>
|
|
130
|
-
</div>
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
44
|
+
<div class="hud">
|
|
45
|
+
<div class="hud-name" id="hud-name">loading...</div>
|
|
46
|
+
<div class="hud-bars" id="hud-bars"></div>
|
|
47
|
+
</div>
|
|
133
48
|
|
|
134
|
-
|
|
135
|
-
<div class="computed" id="computed">
|
|
136
|
-
<div class="computed-item"><div class="computed-value" id="aggression-val">--</div><div class="computed-label">Aggression</div></div>
|
|
137
|
-
<div class="computed-item"><div class="computed-value" id="sociability-val">--</div><div class="computed-label">Sociability</div></div>
|
|
138
|
-
<div class="computed-item"><div class="computed-value" id="mood-val">--</div><div class="computed-label">Mood</div></div>
|
|
139
|
-
<div class="computed-item"><div class="computed-value" id="tasks-val">--</div><div class="computed-label">Tasks Done</div></div>
|
|
140
|
-
</div>
|
|
49
|
+
<div class="personality-hud" id="personality-hud"></div>
|
|
141
50
|
|
|
142
|
-
|
|
143
|
-
<div class="bars" id="bars">
|
|
144
|
-
<div class="bar-group">
|
|
145
|
-
<div class="bar-label"><span>Energy</span><span id="energy-text">--</span></div>
|
|
146
|
-
<div class="bar-track"><div class="bar-fill energy" id="energy-bar" style="width:0%"></div></div>
|
|
147
|
-
</div>
|
|
148
|
-
<div class="bar-group">
|
|
149
|
-
<div class="bar-label"><span>Hunger</span><span id="hunger-text">--</span></div>
|
|
150
|
-
<div class="bar-track"><div class="bar-fill hunger" id="hunger-bar" style="width:0%"></div></div>
|
|
151
|
-
</div>
|
|
152
|
-
<div class="bar-group">
|
|
153
|
-
<div class="bar-label"><span>Boredom</span><span id="boredom-text">--</span></div>
|
|
154
|
-
<div class="bar-track"><div class="bar-fill boredom" id="boredom-bar" style="width:0%"></div></div>
|
|
155
|
-
</div>
|
|
156
|
-
<div class="bar-group">
|
|
157
|
-
<div class="bar-label"><span>Fear</span><span id="fear-text">--</span></div>
|
|
158
|
-
<div class="bar-track"><div class="bar-fill fear" id="fear-bar" style="width:0%"></div></div>
|
|
159
|
-
</div>
|
|
160
|
-
<div class="bar-group">
|
|
161
|
-
<div class="bar-label"><span>Mood</span><span id="moodv-text">--</span></div>
|
|
162
|
-
<div class="bar-track"><div class="bar-fill mood" id="moodv-bar" style="width:50%"></div></div>
|
|
163
|
-
</div>
|
|
164
|
-
<div class="bar-group">
|
|
165
|
-
<div class="bar-label"><span>Tokens Today</span><span id="tokens-text">--</span></div>
|
|
166
|
-
<div class="bar-track"><div class="bar-fill tokens" id="tokens-bar" style="width:0%"></div></div>
|
|
167
|
-
</div>
|
|
168
|
-
</div>
|
|
51
|
+
<div class="toast-container" id="toasts"></div>
|
|
169
52
|
|
|
170
|
-
|
|
171
|
-
<div class="personality" id="personality">
|
|
172
|
-
<h2>Personality</h2>
|
|
173
|
-
<div class="trait">
|
|
174
|
-
<div class="trait-label">Risk</div>
|
|
175
|
-
<div class="trait-bar"><div class="trait-marker" id="trait-risk" style="left:50%"></div></div>
|
|
176
|
-
</div>
|
|
177
|
-
<div class="trait-ends"><span>Cautious</span><span>Bold</span></div>
|
|
178
|
-
<div class="trait">
|
|
179
|
-
<div class="trait-label">Reward</div>
|
|
180
|
-
<div class="trait-bar"><div class="trait-marker" id="trait-reward" style="left:50%"></div></div>
|
|
181
|
-
</div>
|
|
182
|
-
<div class="trait">
|
|
183
|
-
<div class="trait-label">Social</div>
|
|
184
|
-
<div class="trait-bar"><div class="trait-marker" id="trait-social" style="left:50%"></div></div>
|
|
185
|
-
</div>
|
|
186
|
-
<div class="trait">
|
|
187
|
-
<div class="trait-label">Patience</div>
|
|
188
|
-
<div class="trait-bar"><div class="trait-marker" id="trait-patience" style="left:50%"></div></div>
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
53
|
+
<canvas id="c"></canvas>
|
|
191
54
|
|
|
192
|
-
<!-- Events -->
|
|
193
|
-
<div class="events" id="events">
|
|
194
|
-
<h2>Recent Activity</h2>
|
|
195
|
-
<div id="event-list"><div class="no-data">No bio events yet</div></div>
|
|
196
|
-
</div>
|
|
197
|
-
</div>
|
|
198
|
-
|
|
199
|
-
<!-- Revive overlay -->
|
|
200
55
|
<div class="revive-overlay" id="revive-overlay">
|
|
201
56
|
<div class="revive-card">
|
|
202
57
|
<h2>Agent Offline</h2>
|
|
203
|
-
<p
|
|
204
|
-
<button class="revive-btn" onclick="revive()">Revive
|
|
58
|
+
<p>Starved and exhausted. Needs help.</p>
|
|
59
|
+
<button class="revive-btn" onclick="revive()">Revive</button>
|
|
205
60
|
</div>
|
|
206
61
|
</div>
|
|
207
62
|
|
|
208
63
|
<script>
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
64
|
+
// === Canvas setup ===
|
|
65
|
+
const C = document.getElementById('c');
|
|
66
|
+
const ctx = C.getContext('2d');
|
|
67
|
+
let W, H;
|
|
68
|
+
function resize() { W = C.width = innerWidth; H = C.height = innerHeight; }
|
|
69
|
+
resize();
|
|
70
|
+
addEventListener('resize', resize);
|
|
71
|
+
|
|
72
|
+
// === State ===
|
|
73
|
+
let state = null;
|
|
74
|
+
let prevEvents = [];
|
|
75
|
+
let agentX, agentY, targetX, targetY;
|
|
76
|
+
let bobPhase = 0;
|
|
77
|
+
let blinkTimer = 0;
|
|
78
|
+
let isBlinking = false;
|
|
79
|
+
let zzz = []; // sleep particles
|
|
80
|
+
let hearts = []; // social particles
|
|
81
|
+
let coins = []; // earn particles
|
|
82
|
+
let sparks = []; // fear particles
|
|
83
|
+
let currentActivity = 'idle'; // idle, working, eating, sleeping, social, seeking
|
|
84
|
+
|
|
85
|
+
// Locations (relative to canvas)
|
|
86
|
+
function loc(name) {
|
|
87
|
+
const cx = W * 0.5, cy = H * 0.55;
|
|
88
|
+
const r = Math.min(W, H) * 0.25;
|
|
89
|
+
switch(name) {
|
|
90
|
+
case 'home': return { x: cx, y: cy, label: 'Home' };
|
|
91
|
+
case 'work': return { x: cx + r * 0.9, y: cy - r * 0.3, label: 'Work' };
|
|
92
|
+
case 'shop': return { x: cx - r * 0.9, y: cy - r * 0.2, label: 'Shop' };
|
|
93
|
+
case 'social': return { x: cx + r * 0.3, y: cy - r * 0.8, label: 'Social' };
|
|
94
|
+
case 'rest': return { x: cx - r * 0.3, y: cy + r * 0.5, label: 'Rest' };
|
|
95
|
+
default: return { x: cx, y: cy };
|
|
227
96
|
}
|
|
228
|
-
})();
|
|
229
|
-
|
|
230
|
-
function showBubble(text) {
|
|
231
|
-
const b = document.getElementById('bubble');
|
|
232
|
-
b.textContent = text;
|
|
233
|
-
b.classList.add('show');
|
|
234
|
-
clearTimeout(bubbleTimeout);
|
|
235
|
-
bubbleTimeout = setTimeout(() => b.classList.remove('show'), 4000);
|
|
236
97
|
}
|
|
237
98
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
99
|
+
// === Stars ===
|
|
100
|
+
const stars = Array.from({length: 80}, () => ({
|
|
101
|
+
x: Math.random(), y: Math.random() * 0.6,
|
|
102
|
+
s: Math.random() * 1.5 + 0.5, b: Math.random(),
|
|
103
|
+
speed: Math.random() * 0.001 + 0.0005,
|
|
104
|
+
}));
|
|
105
|
+
|
|
106
|
+
// === Scene drawing ===
|
|
107
|
+
function drawScene() {
|
|
108
|
+
// Sky gradient
|
|
109
|
+
const grad = ctx.createLinearGradient(0, 0, 0, H);
|
|
110
|
+
grad.addColorStop(0, '#08081a');
|
|
111
|
+
grad.addColorStop(0.5, '#0d0d22');
|
|
112
|
+
grad.addColorStop(1, '#14142a');
|
|
113
|
+
ctx.fillStyle = grad;
|
|
114
|
+
ctx.fillRect(0, 0, W, H);
|
|
115
|
+
|
|
116
|
+
// Stars
|
|
117
|
+
const t = Date.now() * 0.001;
|
|
118
|
+
for (const s of stars) {
|
|
119
|
+
const twinkle = 0.4 + 0.6 * Math.sin(t * s.speed * 1000 + s.b * 6.28);
|
|
120
|
+
ctx.fillStyle = `rgba(180,190,220,${twinkle * 0.6})`;
|
|
121
|
+
ctx.beginPath();
|
|
122
|
+
ctx.arc(s.x * W, s.y * H, s.s, 0, 6.28);
|
|
123
|
+
ctx.fill();
|
|
124
|
+
}
|
|
241
125
|
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
126
|
+
// Ground
|
|
127
|
+
const gy = H * 0.78;
|
|
128
|
+
const ggrad = ctx.createLinearGradient(0, gy, 0, H);
|
|
129
|
+
ggrad.addColorStop(0, '#161628');
|
|
130
|
+
ggrad.addColorStop(1, '#0e0e20');
|
|
131
|
+
ctx.fillStyle = ggrad;
|
|
132
|
+
ctx.fillRect(0, gy, W, H - gy);
|
|
133
|
+
|
|
134
|
+
// Ground line
|
|
135
|
+
ctx.strokeStyle = '#222240';
|
|
136
|
+
ctx.lineWidth = 1;
|
|
137
|
+
ctx.beginPath();
|
|
138
|
+
ctx.moveTo(0, gy);
|
|
139
|
+
ctx.lineTo(W, gy);
|
|
140
|
+
ctx.stroke();
|
|
141
|
+
|
|
142
|
+
// Location markers
|
|
143
|
+
const locs = ['home', 'work', 'shop', 'social', 'rest'];
|
|
144
|
+
for (const name of locs) {
|
|
145
|
+
const l = loc(name);
|
|
146
|
+
// Small icon
|
|
147
|
+
ctx.fillStyle = '#1a1a30';
|
|
148
|
+
ctx.strokeStyle = '#2a2a44';
|
|
149
|
+
ctx.lineWidth = 1;
|
|
150
|
+
|
|
151
|
+
if (name === 'shop') {
|
|
152
|
+
// Shop: small building
|
|
153
|
+
ctx.fillRect(l.x - 18, l.y - 20, 36, 24);
|
|
154
|
+
ctx.strokeRect(l.x - 18, l.y - 20, 36, 24);
|
|
155
|
+
// Roof
|
|
156
|
+
ctx.beginPath();
|
|
157
|
+
ctx.moveTo(l.x - 22, l.y - 20);
|
|
158
|
+
ctx.lineTo(l.x, l.y - 34);
|
|
159
|
+
ctx.lineTo(l.x + 22, l.y - 20);
|
|
160
|
+
ctx.closePath();
|
|
161
|
+
ctx.fillStyle = '#2a2244';
|
|
162
|
+
ctx.fill();
|
|
163
|
+
ctx.stroke();
|
|
164
|
+
ctx.fillStyle = '#1a1a30';
|
|
165
|
+
} else if (name === 'work') {
|
|
166
|
+
// Work: desk shape
|
|
167
|
+
ctx.fillRect(l.x - 20, l.y - 8, 40, 12);
|
|
168
|
+
ctx.strokeRect(l.x - 20, l.y - 8, 40, 12);
|
|
169
|
+
// Screen
|
|
170
|
+
ctx.fillStyle = '#222244';
|
|
171
|
+
ctx.fillRect(l.x - 8, l.y - 22, 16, 14);
|
|
172
|
+
ctx.strokeRect(l.x - 8, l.y - 22, 16, 14);
|
|
173
|
+
ctx.fillStyle = '#1a1a30';
|
|
174
|
+
} else if (name === 'home') {
|
|
175
|
+
// Home: house
|
|
176
|
+
ctx.fillRect(l.x - 16, l.y - 14, 32, 20);
|
|
177
|
+
ctx.strokeRect(l.x - 16, l.y - 14, 32, 20);
|
|
178
|
+
ctx.beginPath();
|
|
179
|
+
ctx.moveTo(l.x - 20, l.y - 14);
|
|
180
|
+
ctx.lineTo(l.x, l.y - 30);
|
|
181
|
+
ctx.lineTo(l.x + 20, l.y - 14);
|
|
182
|
+
ctx.closePath();
|
|
183
|
+
ctx.fillStyle = '#22223a';
|
|
184
|
+
ctx.fill();
|
|
185
|
+
ctx.stroke();
|
|
186
|
+
ctx.fillStyle = '#1a1a30';
|
|
187
|
+
// Door
|
|
188
|
+
ctx.fillStyle = '#282848';
|
|
189
|
+
ctx.fillRect(l.x - 4, l.y - 4, 8, 10);
|
|
190
|
+
ctx.fillStyle = '#1a1a30';
|
|
191
|
+
} else if (name === 'social') {
|
|
192
|
+
// Social: bench
|
|
193
|
+
ctx.fillRect(l.x - 18, l.y - 2, 36, 6);
|
|
194
|
+
ctx.strokeRect(l.x - 18, l.y - 2, 36, 6);
|
|
195
|
+
ctx.fillRect(l.x - 14, l.y + 4, 4, 8);
|
|
196
|
+
ctx.fillRect(l.x + 10, l.y + 4, 4, 8);
|
|
197
|
+
} else if (name === 'rest') {
|
|
198
|
+
// Rest: bed
|
|
199
|
+
ctx.fillRect(l.x - 16, l.y - 4, 32, 10);
|
|
200
|
+
ctx.strokeRect(l.x - 16, l.y - 4, 32, 10);
|
|
201
|
+
// Pillow
|
|
202
|
+
ctx.fillStyle = '#282848';
|
|
203
|
+
ctx.fillRect(l.x - 14, l.y - 8, 10, 6);
|
|
204
|
+
ctx.fillStyle = '#1a1a30';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Label
|
|
208
|
+
ctx.fillStyle = '#333355';
|
|
209
|
+
ctx.font = '9px Courier New';
|
|
210
|
+
ctx.textAlign = 'center';
|
|
211
|
+
ctx.fillText(l.label, l.x, l.y + 22);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
245
214
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
215
|
+
// === Agent drawing ===
|
|
216
|
+
function drawAgent() {
|
|
217
|
+
if (!state) return;
|
|
218
|
+
const bio = state.bio;
|
|
219
|
+
const comp = state.computed;
|
|
220
|
+
|
|
221
|
+
// Movement
|
|
222
|
+
const dx = targetX - agentX;
|
|
223
|
+
const dy = targetY - agentY;
|
|
224
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
225
|
+
const speed = 1.5;
|
|
226
|
+
if (dist > 2) {
|
|
227
|
+
agentX += (dx / dist) * speed;
|
|
228
|
+
agentY += (dy / dist) * speed;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Bob animation
|
|
232
|
+
bobPhase += currentActivity === 'sleeping' ? 0.02 : 0.05;
|
|
233
|
+
const bobY = currentActivity === 'sleeping' ? Math.sin(bobPhase) * 2 : Math.sin(bobPhase) * 4;
|
|
234
|
+
|
|
235
|
+
// Blink
|
|
236
|
+
blinkTimer -= 0.016;
|
|
237
|
+
if (blinkTimer <= 0) {
|
|
238
|
+
isBlinking = true;
|
|
239
|
+
blinkTimer = 3 + Math.random() * 4;
|
|
240
|
+
setTimeout(() => isBlinking = false, 150);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const x = agentX;
|
|
244
|
+
const y = agentY + bobY;
|
|
245
|
+
|
|
246
|
+
// Shadow
|
|
247
|
+
ctx.fillStyle = 'rgba(0,0,0,0.2)';
|
|
248
|
+
ctx.beginPath();
|
|
249
|
+
ctx.ellipse(agentX, agentY + 22, 18, 5, 0, 0, 6.28);
|
|
250
|
+
ctx.fill();
|
|
251
|
+
|
|
252
|
+
// Body color
|
|
253
|
+
let hue = 210;
|
|
254
|
+
if (bio.moodValence > 0.3) hue = 150;
|
|
255
|
+
if (bio.moodValence < -0.3) hue = 270;
|
|
256
|
+
if (bio.hunger < 20) hue = 30;
|
|
257
|
+
if (bio.hunger === 0) hue = 0;
|
|
252
258
|
const sat = 50 + bio.energy * 0.3;
|
|
253
|
-
const
|
|
254
|
-
|
|
259
|
+
const lum = 30 + bio.energy * 0.2;
|
|
260
|
+
const bodyColor = `hsl(${hue},${sat}%,${lum}%)`;
|
|
261
|
+
|
|
262
|
+
// Body scale
|
|
263
|
+
const scale = bio.forcedOffline ? 0.6 : (0.8 + bio.energy / 100 * 0.2);
|
|
264
|
+
const bw = 28 * scale;
|
|
265
|
+
const bh = 24 * scale;
|
|
266
|
+
|
|
267
|
+
ctx.save();
|
|
268
|
+
ctx.translate(x, y);
|
|
255
269
|
|
|
256
|
-
// Expressions
|
|
257
270
|
if (bio.forcedOffline) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
} else if (bio.energy < 15) {
|
|
261
|
-
blob.classList.add('exhausted');
|
|
262
|
-
blob.classList.add('tired');
|
|
263
|
-
} else if (bio.hunger === 0) {
|
|
264
|
-
blob.classList.add('hungry');
|
|
265
|
-
blob.classList.add('angry');
|
|
266
|
-
} else if (bio.hunger < 20) {
|
|
267
|
-
blob.classList.add('hungry');
|
|
268
|
-
} else if (computed.aggression > 0.6) {
|
|
269
|
-
blob.classList.add('angry');
|
|
270
|
-
} else if (bio.fear > 0.5) {
|
|
271
|
-
blob.classList.add('scared');
|
|
272
|
-
} else if (bio.moodValence > 0.3) {
|
|
273
|
-
blob.classList.add('happy');
|
|
274
|
-
blob.classList.add('excited');
|
|
275
|
-
} else if (bio.energy < 30) {
|
|
276
|
-
blob.classList.add('tired');
|
|
271
|
+
ctx.globalAlpha = 0.35;
|
|
272
|
+
ctx.filter = 'grayscale(1)';
|
|
277
273
|
}
|
|
278
274
|
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
275
|
+
// Body
|
|
276
|
+
ctx.fillStyle = bodyColor;
|
|
277
|
+
ctx.beginPath();
|
|
278
|
+
ctx.ellipse(0, 0, bw, bh, 0, 0, 6.28);
|
|
279
|
+
ctx.fill();
|
|
280
|
+
|
|
281
|
+
// Highlight
|
|
282
|
+
ctx.fillStyle = `hsla(${hue},${sat + 20}%,${lum + 20}%,0.3)`;
|
|
283
|
+
ctx.beginPath();
|
|
284
|
+
ctx.ellipse(-bw * 0.25, -bh * 0.3, bw * 0.3, bh * 0.2, -0.3, 0, 6.28);
|
|
285
|
+
ctx.fill();
|
|
286
|
+
|
|
287
|
+
// Eyes
|
|
288
|
+
const eyeSpacing = bw * 0.35;
|
|
289
|
+
const eyeY = -bh * 0.15;
|
|
290
|
+
const eyeH = isBlinking ? 1 : (bio.energy < 20 ? 3 : 5);
|
|
291
|
+
const eyeW = bio.fear > 0.5 ? 5 : 4;
|
|
292
|
+
|
|
293
|
+
ctx.fillStyle = '#fff';
|
|
294
|
+
for (const side of [-1, 1]) {
|
|
295
|
+
ctx.beginPath();
|
|
296
|
+
ctx.ellipse(side * eyeSpacing, eyeY, eyeW, eyeH, 0, 0, 6.28);
|
|
297
|
+
ctx.fill();
|
|
298
|
+
}
|
|
299
|
+
// Pupils
|
|
300
|
+
if (!isBlinking && eyeH > 2) {
|
|
301
|
+
ctx.fillStyle = '#111';
|
|
302
|
+
const pupilOfsX = (targetX - agentX) * 0.005;
|
|
303
|
+
for (const side of [-1, 1]) {
|
|
304
|
+
ctx.beginPath();
|
|
305
|
+
ctx.ellipse(side * eyeSpacing + pupilOfsX, eyeY + 1, 2, 2.5, 0, 0, 6.28);
|
|
306
|
+
ctx.fill();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
283
309
|
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
310
|
+
// Mouth
|
|
311
|
+
const mouthY = bh * 0.3;
|
|
312
|
+
ctx.strokeStyle = '#fff';
|
|
313
|
+
ctx.lineWidth = 1.5;
|
|
314
|
+
ctx.lineCap = 'round';
|
|
315
|
+
ctx.beginPath();
|
|
316
|
+
if (bio.moodValence > 0.3) {
|
|
317
|
+
// Happy smile
|
|
318
|
+
ctx.arc(0, mouthY - 2, 6, 0.2, Math.PI - 0.2);
|
|
319
|
+
} else if (comp.aggression > 0.6 || bio.hunger === 0) {
|
|
320
|
+
// Angry/frown
|
|
321
|
+
ctx.arc(0, mouthY + 4, 5, Math.PI + 0.3, -0.3);
|
|
322
|
+
} else if (bio.energy < 20) {
|
|
323
|
+
// Tired: flat line
|
|
324
|
+
ctx.moveTo(-4, mouthY); ctx.lineTo(4, mouthY);
|
|
325
|
+
} else {
|
|
326
|
+
// Neutral: slight curve
|
|
327
|
+
ctx.arc(0, mouthY, 4, 0.1, Math.PI - 0.1);
|
|
328
|
+
}
|
|
329
|
+
ctx.stroke();
|
|
330
|
+
|
|
331
|
+
// Blush when social
|
|
332
|
+
if (comp.sociability > 0.6) {
|
|
333
|
+
ctx.fillStyle = 'rgba(255,100,100,0.15)';
|
|
334
|
+
ctx.beginPath(); ctx.ellipse(-eyeSpacing - 3, eyeY + 8, 5, 3, 0, 0, 6.28); ctx.fill();
|
|
335
|
+
ctx.beginPath(); ctx.ellipse(eyeSpacing + 3, eyeY + 8, 5, 3, 0, 0, 6.28); ctx.fill();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Anger marks
|
|
339
|
+
if (comp.aggression > 0.5) {
|
|
340
|
+
ctx.strokeStyle = `rgba(255,80,80,${comp.aggression * 0.6})`;
|
|
341
|
+
ctx.lineWidth = 1.5;
|
|
342
|
+
const ax = bw * 0.6, ay = -bh * 0.6;
|
|
343
|
+
ctx.beginPath(); ctx.moveTo(ax, ay); ctx.lineTo(ax + 5, ay - 3);
|
|
344
|
+
ctx.moveTo(ax + 5, ay); ctx.lineTo(ax, ay - 3); ctx.stroke();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
ctx.restore();
|
|
302
348
|
|
|
303
|
-
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
349
|
+
// === Particles ===
|
|
350
|
+
// Zzz (sleeping)
|
|
351
|
+
if (currentActivity === 'sleeping') {
|
|
352
|
+
if (Math.random() < 0.03) zzz.push({ x: x + 20, y: y - 20, life: 1, size: 8 + Math.random() * 4 });
|
|
353
|
+
}
|
|
354
|
+
for (let i = zzz.length - 1; i >= 0; i--) {
|
|
355
|
+
const z = zzz[i];
|
|
356
|
+
z.y -= 0.3; z.x += 0.15; z.life -= 0.008;
|
|
357
|
+
if (z.life <= 0) { zzz.splice(i, 1); continue; }
|
|
358
|
+
ctx.fillStyle = `rgba(150,150,255,${z.life * 0.5})`;
|
|
359
|
+
ctx.font = `${z.size}px Courier New`;
|
|
360
|
+
ctx.fillText('z', z.x, z.y);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Hearts (social)
|
|
364
|
+
for (let i = hearts.length - 1; i >= 0; i--) {
|
|
365
|
+
const h = hearts[i];
|
|
366
|
+
h.y -= 0.5; h.x += Math.sin(h.y * 0.05) * 0.3; h.life -= 0.01;
|
|
367
|
+
if (h.life <= 0) { hearts.splice(i, 1); continue; }
|
|
368
|
+
ctx.fillStyle = `rgba(255,100,150,${h.life * 0.7})`;
|
|
369
|
+
ctx.font = '12px serif';
|
|
370
|
+
ctx.fillText('\u2665', h.x, h.y);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Coins (earning)
|
|
374
|
+
for (let i = coins.length - 1; i >= 0; i--) {
|
|
375
|
+
const c = coins[i];
|
|
376
|
+
c.y -= 0.8; c.life -= 0.015;
|
|
377
|
+
if (c.life <= 0) { coins.splice(i, 1); continue; }
|
|
378
|
+
ctx.fillStyle = `rgba(255,200,50,${c.life})`;
|
|
379
|
+
ctx.font = '11px serif';
|
|
380
|
+
ctx.fillText('+$', c.x, c.y);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Sparks (fear)
|
|
384
|
+
for (let i = sparks.length - 1; i >= 0; i--) {
|
|
385
|
+
const s = sparks[i];
|
|
386
|
+
s.x += s.vx; s.y += s.vy; s.life -= 0.02;
|
|
387
|
+
if (s.life <= 0) { sparks.splice(i, 1); continue; }
|
|
388
|
+
ctx.fillStyle = `rgba(255,255,100,${s.life})`;
|
|
389
|
+
ctx.beginPath(); ctx.arc(s.x, s.y, 1.5, 0, 6.28); ctx.fill();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Status icon above head
|
|
393
|
+
let icon = '';
|
|
394
|
+
if (bio.forcedOffline) icon = '\u{1F480}';
|
|
395
|
+
else if (bio.hunger === 0) icon = '\u{1F35E}\u{2757}';
|
|
396
|
+
else if (bio.hunger < 20) icon = '\u{1F35E}';
|
|
397
|
+
else if (bio.energy < 15) icon = '\u{1F4A4}';
|
|
398
|
+
else if (bio.fear > 0.5) icon = '\u{1F628}';
|
|
399
|
+
else if (bio.boredom > 0.7) icon = '\u{1F971}';
|
|
400
|
+
else if (currentActivity === 'working') icon = '\u{1F528}';
|
|
401
|
+
else if (currentActivity === 'eating') icon = '\u{1F35E}';
|
|
402
|
+
if (icon) {
|
|
403
|
+
ctx.font = '16px serif';
|
|
404
|
+
ctx.textAlign = 'center';
|
|
405
|
+
const iconY = y - bh - 12 + Math.sin(bobPhase * 2) * 2;
|
|
406
|
+
ctx.fillText(icon, x, iconY);
|
|
407
|
+
}
|
|
309
408
|
}
|
|
310
409
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (!
|
|
314
|
-
|
|
315
|
-
|
|
410
|
+
// === Activity & movement logic ===
|
|
411
|
+
function updateActivity() {
|
|
412
|
+
if (!state) return;
|
|
413
|
+
const bio = state.bio;
|
|
414
|
+
|
|
415
|
+
let target = 'home';
|
|
416
|
+
if (bio.forcedOffline) {
|
|
417
|
+
currentActivity = 'offline'; target = 'rest';
|
|
418
|
+
} else if (bio.energy < 15) {
|
|
419
|
+
currentActivity = 'sleeping'; target = 'rest';
|
|
420
|
+
} else if (bio.hunger < 20) {
|
|
421
|
+
currentActivity = 'seeking'; target = 'shop';
|
|
422
|
+
} else if (state.computed.sociability > 0.7 && bio.boredom > 0.5) {
|
|
423
|
+
currentActivity = 'social'; target = 'social';
|
|
424
|
+
if (Math.random() < 0.02) hearts.push({ x: agentX + (Math.random()-0.5)*10, y: agentY - 30, life: 1 });
|
|
425
|
+
} else if (bio.taskCount > 0 && bio.energy > 30 && bio.mood !== 'exhausted') {
|
|
426
|
+
currentActivity = 'working'; target = 'work';
|
|
427
|
+
} else {
|
|
428
|
+
currentActivity = 'idle'; target = 'home';
|
|
316
429
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
430
|
+
|
|
431
|
+
const l = loc(target);
|
|
432
|
+
targetX = l.x;
|
|
433
|
+
targetY = l.y;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// === HUD ===
|
|
437
|
+
function updateHUD() {
|
|
438
|
+
if (!state) return;
|
|
439
|
+
const bio = state.bio;
|
|
440
|
+
|
|
441
|
+
document.getElementById('hud-name').textContent = `${state.agent} \u00B7 ${bio.mood}`;
|
|
442
|
+
|
|
443
|
+
const bars = [
|
|
444
|
+
{ icon: '\u26A1', val: bio.energy, max: 100, color: '#10b981' },
|
|
445
|
+
{ icon: '\u{1F35E}', val: bio.hunger, max: 100, color: '#f59e0b' },
|
|
446
|
+
{ icon: '\u{1F971}', val: Math.round(bio.boredom * 100), max: 100, color: '#8b5cf6' },
|
|
447
|
+
{ icon: '\u{1F628}', val: Math.round(bio.fear * 100), max: 100, color: '#ef4444' },
|
|
448
|
+
];
|
|
449
|
+
|
|
450
|
+
const el = document.getElementById('hud-bars');
|
|
451
|
+
el.innerHTML = bars.map(b => {
|
|
452
|
+
const pct = (b.val / b.max * 100);
|
|
453
|
+
return `<div class="hud-bar">
|
|
454
|
+
<span class="hud-bar-icon">${b.icon}</span>
|
|
455
|
+
<div class="hud-bar-track"><div class="hud-bar-fill" style="width:${pct}%;background:${b.color}"></div></div>
|
|
456
|
+
<span class="hud-bar-text">${b.val}</span>
|
|
327
457
|
</div>`;
|
|
328
458
|
}).join('');
|
|
459
|
+
|
|
460
|
+
// Personality tags
|
|
461
|
+
const p = bio.personality;
|
|
462
|
+
const tags = [
|
|
463
|
+
{ label: p.riskWeight > 0.3 ? 'Bold' : p.riskWeight < -0.3 ? 'Cautious' : 'Balanced', lvl: Math.abs(p.riskWeight) > 0.3 ? 'high' : 'mid' },
|
|
464
|
+
{ label: p.patience > 0.6 ? 'Patient' : p.patience < 0.3 ? 'Hasty' : '', lvl: 'high' },
|
|
465
|
+
{ label: p.socialWeight > 0.6 ? 'Social' : p.socialWeight < 0.3 ? 'Loner' : '', lvl: 'high' },
|
|
466
|
+
].filter(t => t.label);
|
|
467
|
+
document.getElementById('personality-hud').innerHTML = tags.map(t =>
|
|
468
|
+
`<span class="p-tag ${t.lvl}">${t.label}</span>`
|
|
469
|
+
).join('');
|
|
470
|
+
|
|
471
|
+
// Revive
|
|
472
|
+
document.getElementById('revive-overlay').classList.toggle('show', !!bio.forcedOffline);
|
|
329
473
|
}
|
|
330
474
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
475
|
+
// === Event detection ===
|
|
476
|
+
function checkNewEvents(events) {
|
|
477
|
+
if (!events || events.length === 0) return;
|
|
478
|
+
const newEvt = events[0];
|
|
479
|
+
if (prevEvents.length === 0 || prevEvents[0]?.ts !== newEvt.ts) {
|
|
480
|
+
showToast(newEvt);
|
|
481
|
+
// Spawn particles
|
|
482
|
+
if (newEvt.trigger === 'fear') {
|
|
483
|
+
for (let i = 0; i < 8; i++) sparks.push({
|
|
484
|
+
x: agentX, y: agentY - 10,
|
|
485
|
+
vx: (Math.random() - 0.5) * 3, vy: -Math.random() * 2 - 1, life: 1,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
if (newEvt.action === 'buy_food' || newEvt.action === 'auto_buy' || newEvt.action === 'feed') {
|
|
489
|
+
currentActivity = 'eating';
|
|
490
|
+
const l = loc('shop'); targetX = l.x; targetY = l.y;
|
|
491
|
+
}
|
|
492
|
+
if (newEvt.trigger === 'social') {
|
|
493
|
+
for (let i = 0; i < 3; i++) hearts.push({ x: agentX + (Math.random()-0.5)*20, y: agentY - 30, life: 1 });
|
|
494
|
+
}
|
|
336
495
|
}
|
|
496
|
+
prevEvents = events;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const ICONS = { hunger:'\u{1F35E}', fear:'\u{1F628}', boredom:'\u{1F971}', exhaustion:'\u{1F634}', social:'\u{1F44B}', token_limit:'\u{26A1}', revive:'\u{2728}' };
|
|
500
|
+
|
|
501
|
+
function showToast(evt) {
|
|
502
|
+
const container = document.getElementById('toasts');
|
|
503
|
+
const el = document.createElement('div');
|
|
504
|
+
el.className = 'toast';
|
|
505
|
+
el.textContent = `${ICONS[evt.trigger]||''} ${evt.reason}`;
|
|
506
|
+
container.appendChild(el);
|
|
507
|
+
setTimeout(() => el.remove(), 4500);
|
|
337
508
|
}
|
|
338
509
|
|
|
510
|
+
// === Fetch ===
|
|
339
511
|
async function fetchState() {
|
|
340
512
|
try {
|
|
341
513
|
const res = await fetch('/self/state');
|
|
342
|
-
if (!res.ok)
|
|
514
|
+
if (!res.ok) return;
|
|
343
515
|
const data = await res.json();
|
|
344
516
|
|
|
345
|
-
|
|
346
|
-
|
|
517
|
+
const isFirst = !state;
|
|
518
|
+
state = data;
|
|
347
519
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
// Computed
|
|
353
|
-
document.getElementById('aggression-val').textContent = data.computed.aggression.toFixed(2);
|
|
354
|
-
document.getElementById('sociability-val').textContent = data.computed.sociability.toFixed(2);
|
|
355
|
-
document.getElementById('mood-val').textContent = data.bio.mood;
|
|
356
|
-
document.getElementById('tasks-val').textContent = data.bio.taskCount;
|
|
357
|
-
|
|
358
|
-
// Events
|
|
359
|
-
detectNewEvents(lastState?.bioEvents, data.bioEvents);
|
|
360
|
-
updateEvents(data.bioEvents);
|
|
361
|
-
|
|
362
|
-
// Revive overlay
|
|
363
|
-
document.getElementById('revive-overlay').classList.toggle('show', !!data.bio.forcedOffline);
|
|
364
|
-
if (data.bio.forcedOfflineAt) {
|
|
365
|
-
document.getElementById('revive-msg').textContent =
|
|
366
|
-
`Agent went offline at ${data.bio.forcedOfflineAt.replace('T', ' ')}. Starved and exhausted. Adjust personality or directives after revival.`;
|
|
520
|
+
if (isFirst) {
|
|
521
|
+
const h = loc('home');
|
|
522
|
+
agentX = h.x; agentY = h.y;
|
|
523
|
+
targetX = h.x; targetY = h.y;
|
|
367
524
|
}
|
|
368
525
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
526
|
+
checkNewEvents(data.bioEvents);
|
|
527
|
+
updateHUD();
|
|
528
|
+
updateActivity();
|
|
529
|
+
} catch {}
|
|
373
530
|
}
|
|
374
531
|
|
|
375
532
|
async function revive() {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
533
|
+
await fetch('/self/revive', { method: 'POST' });
|
|
534
|
+
document.getElementById('revive-overlay').classList.remove('show');
|
|
535
|
+
showToast({ trigger: 'revive', reason: 'Agent revived!' });
|
|
536
|
+
setTimeout(fetchState, 500);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// === Main loop ===
|
|
540
|
+
function frame() {
|
|
541
|
+
drawScene();
|
|
542
|
+
drawAgent();
|
|
543
|
+
requestAnimationFrame(frame);
|
|
384
544
|
}
|
|
545
|
+
frame();
|
|
385
546
|
|
|
386
|
-
// Poll
|
|
387
547
|
fetchState();
|
|
388
548
|
setInterval(fetchState, 3000);
|
|
549
|
+
setInterval(updateActivity, 2000);
|
|
389
550
|
</script>
|
|
390
551
|
</body>
|
|
391
552
|
</html>
|