desktop-pet-app 0.3.27 → 0.4.3

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.
@@ -170,12 +170,15 @@ class SpriteRenderer {
170
170
  }
171
171
  ctx;
172
172
  img = new Image();
173
+ source = this.img;
173
174
  ready = false;
174
175
  load(skin) {
175
176
  this.ready = false;
177
+ this.source = this.img;
176
178
  this.canvas.width = skin.displaySize?.width ?? Math.round(skin.frameWidth * SPRITE_SCALE);
177
179
  this.canvas.height = skin.displaySize?.height ?? Math.round(skin.frameHeight * SPRITE_SCALE);
178
180
  this.img.onload = () => {
181
+ this.source = skin.chromaKey ? this.removeChromaKey(skin) : this.img;
179
182
  this.ready = true;
180
183
  this.onReady();
181
184
  };
@@ -202,14 +205,17 @@ class SpriteRenderer {
202
205
  ctx.scale(-1, 1);
203
206
  }
204
207
  ctx.imageSmoothingEnabled = skin.smoothing ?? true;
205
- const sx = skin.imageLayout === "single" ? 0 : frame * skin.frameWidth;
206
- const sy = skin.imageLayout === "single" ? 0 : s.row * skin.frameHeight;
208
+ const sourceRect = skin.sourceRect;
209
+ const sourceWidth = sourceRect?.width ?? skin.frameWidth;
210
+ const sourceHeight = sourceRect?.height ?? skin.frameHeight;
211
+ const sx = (sourceRect?.x ?? 0) + (skin.imageLayout === "single" ? 0 : frame * sourceWidth);
212
+ const sy = (sourceRect?.y ?? 0) + (skin.imageLayout === "single" ? 0 : s.row * sourceHeight);
207
213
  ctx.drawImage(
208
- this.img,
214
+ this.source,
209
215
  sx,
210
216
  sy,
211
- skin.frameWidth,
212
- skin.frameHeight,
217
+ sourceWidth,
218
+ sourceHeight,
213
219
  0,
214
220
  0,
215
221
  w,
@@ -231,6 +237,28 @@ class SpriteRenderer {
231
237
  ctx.fillText("Z", w - 16, 16 - off);
232
238
  }
233
239
  }
240
+ /**
241
+ * 生成图使用纯品红背景以避免透明通道被错误烘焙为棋盘格。
242
+ * 图片只在加载时处理一次,绘制循环复用离屏画布。
243
+ */
244
+ removeChromaKey(skin) {
245
+ const key = skin.chromaKey;
246
+ const tolerance = key.tolerance ?? 24;
247
+ const canvas2 = document.createElement("canvas");
248
+ canvas2.width = this.img.naturalWidth;
249
+ canvas2.height = this.img.naturalHeight;
250
+ const ctx = canvas2.getContext("2d", { willReadFrequently: true });
251
+ ctx.drawImage(this.img, 0, 0);
252
+ const imageData = ctx.getImageData(0, 0, canvas2.width, canvas2.height);
253
+ const pixels = imageData.data;
254
+ for (let i = 0; i < pixels.length; i += 4) {
255
+ if (isChromaPixel(pixels[i], pixels[i + 1], pixels[i + 2], key, tolerance)) {
256
+ pixels[i + 3] = 0;
257
+ }
258
+ }
259
+ ctx.putImageData(imageData, 0, 0);
260
+ return canvas2;
261
+ }
234
262
  drawHeart(ctx, px, py) {
235
263
  for (let y = 0; y < HEART.length; y++) {
236
264
  for (let x = 0; x < HEART[y].length; x++) {
@@ -239,6 +267,9 @@ class SpriteRenderer {
239
267
  }
240
268
  }
241
269
  }
270
+ function isChromaPixel(red, green, blue, key, tolerance = key.tolerance ?? 24) {
271
+ return Math.abs(red - key.red) <= tolerance && Math.abs(green - key.green) <= tolerance && Math.abs(blue - key.blue) <= tolerance;
272
+ }
242
273
  function proceduralMotion(state, elapsedMs) {
243
274
  const wave = Math.sin(elapsedMs / 240);
244
275
  switch (state) {
@@ -358,6 +389,37 @@ const bird = {
358
389
  failed: [FAILED$9]
359
390
  }
360
391
  };
392
+ const yierBubuUrl = "" + new URL("yier-bubu-v2-BaEcMG7v.png", import.meta.url).href;
393
+ const still$2 = { row: 0, frames: 1, frameMs: 180 };
394
+ const bubu = {
395
+ id: "bubu",
396
+ name: "布布",
397
+ image: yierBubuUrl,
398
+ imageLayout: "single",
399
+ sourceRect: { x: 800, y: 160, width: 600, height: 720 },
400
+ frameWidth: 600,
401
+ frameHeight: 720,
402
+ displaySize: { width: 120, height: 144 },
403
+ recommendedWindow: { width: 200, height: 190 },
404
+ smoothing: false,
405
+ chromaKey: { red: 255, green: 0, blue: 255, tolerance: 150 },
406
+ states: {
407
+ idle: { ...still$2, frameMs: 900 },
408
+ walk: { ...still$2, frameMs: 180 },
409
+ sleep: { ...still$2, frameMs: 900 },
410
+ excited: { ...still$2, frameMs: 160 },
411
+ drag: { ...still$2, frameMs: 160 },
412
+ working: { ...still$2, frameMs: 300 },
413
+ success: { ...still$2, frameMs: 160 },
414
+ failed: { ...still$2, frameMs: 400 }
415
+ },
416
+ anchors: {
417
+ ground: { x: 0.5, y: 0.99 },
418
+ bubble: { x: 0.5, y: 0.04 },
419
+ effects: { character: { x: 0.5, y: 0.5 } },
420
+ contentBounds: { x: 0.08, y: 0.03, width: 0.83, height: 0.96 }
421
+ }
422
+ };
361
423
  const IDLE$8 = [
362
424
  "...oo......oo...",
363
425
  "..oiao....oaio..",
@@ -1426,7 +1488,7 @@ const whale = {
1426
1488
  }
1427
1489
  };
1428
1490
  const robotBlueUrl = "" + new URL("robot-blue-CFH9bWjZ.png", import.meta.url).href;
1429
- const still = { row: 0, frames: 1, frameMs: 180 };
1491
+ const still$1 = { row: 0, frames: 1, frameMs: 180 };
1430
1492
  const robotBlue = {
1431
1493
  id: "robot-blue",
1432
1494
  name: "蓝色协作机器人",
@@ -1438,14 +1500,14 @@ const robotBlue = {
1438
1500
  recommendedWindow: { width: 260, height: 320 },
1439
1501
  smoothing: true,
1440
1502
  states: {
1441
- idle: { ...still, frameMs: 220 },
1442
- walk: { ...still, frameMs: 110 },
1443
- sleep: { ...still, frameMs: 260 },
1444
- excited: { ...still, frameMs: 110 },
1445
- drag: { ...still, frameMs: 120 },
1446
- working: { ...still, frameMs: 150 },
1447
- success: { ...still, frameMs: 110 },
1448
- failed: { ...still, frameMs: 180 }
1503
+ idle: { ...still$1, frameMs: 220 },
1504
+ walk: { ...still$1, frameMs: 110 },
1505
+ sleep: { ...still$1, frameMs: 260 },
1506
+ excited: { ...still$1, frameMs: 110 },
1507
+ drag: { ...still$1, frameMs: 120 },
1508
+ working: { ...still$1, frameMs: 150 },
1509
+ success: { ...still$1, frameMs: 110 },
1510
+ failed: { ...still$1, frameMs: 180 }
1449
1511
  },
1450
1512
  anchors: {
1451
1513
  ground: { x: 0.5, y: 0.955 },
@@ -1458,6 +1520,36 @@ const robotBlue = {
1458
1520
  contentBounds: { x: 0.08, y: 0.02, width: 0.84, height: 0.94 }
1459
1521
  }
1460
1522
  };
1523
+ const still = { row: 0, frames: 1, frameMs: 180 };
1524
+ const yier = {
1525
+ id: "yier",
1526
+ name: "一二",
1527
+ image: yierBubuUrl,
1528
+ imageLayout: "single",
1529
+ sourceRect: { x: 144, y: 160, width: 600, height: 720 },
1530
+ frameWidth: 600,
1531
+ frameHeight: 720,
1532
+ displaySize: { width: 120, height: 144 },
1533
+ recommendedWindow: { width: 200, height: 190 },
1534
+ smoothing: false,
1535
+ chromaKey: { red: 255, green: 0, blue: 255, tolerance: 150 },
1536
+ states: {
1537
+ idle: { ...still, frameMs: 900 },
1538
+ walk: { ...still, frameMs: 180 },
1539
+ sleep: { ...still, frameMs: 900 },
1540
+ excited: { ...still, frameMs: 160 },
1541
+ drag: { ...still, frameMs: 160 },
1542
+ working: { ...still, frameMs: 300 },
1543
+ success: { ...still, frameMs: 160 },
1544
+ failed: { ...still, frameMs: 400 }
1545
+ },
1546
+ anchors: {
1547
+ ground: { x: 0.5, y: 0.99 },
1548
+ bubble: { x: 0.5, y: 0.04 },
1549
+ effects: { character: { x: 0.5, y: 0.5 } },
1550
+ contentBounds: { x: 0.07, y: 0.03, width: 0.84, height: 0.96 }
1551
+ }
1552
+ };
1461
1553
  const skins = {
1462
1554
  codex,
1463
1555
  claude,
@@ -1470,7 +1562,12 @@ const skins = {
1470
1562
  whale,
1471
1563
  penguin
1472
1564
  };
1473
- const spriteSkins = { hoot, "robot-blue": robotBlue };
1565
+ const spriteSkins = {
1566
+ hoot,
1567
+ "robot-blue": robotBlue,
1568
+ yier,
1569
+ bubu
1570
+ };
1474
1571
  const defaultSkin = codex;
1475
1572
  function resolveSkin(id) {
1476
1573
  const ascii = skins[id];
@@ -238,7 +238,7 @@
238
238
  }
239
239
  }
240
240
  </style>
241
- <script type="module" crossorigin src="./assets/index-BWDd9Gk_.js"></script>
241
+ <script type="module" crossorigin src="./assets/index-CUcY_qKX.js"></script>
242
242
  </head>
243
243
  <body>
244
244
  <canvas id="pet"></canvas>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "desktop-pet-app",
3
- "version": "0.3.27",
3
+ "version": "0.4.3",
4
4
  "description": "AI desktop pet with MCP support and a self-hosted relay server",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -27,6 +27,7 @@
27
27
  "dev": "electron-vite dev",
28
28
  "build": "electron-vite build && npm run build:mcp",
29
29
  "build:mcp": "esbuild src/mcp-server/index.ts --bundle --platform=node --format=esm --outfile=out/mcp-server/index.js && node -e \"require('fs').writeFileSync('out/mcp-server/package.json', JSON.stringify({ type: 'module' }))\"",
30
+ "e2e:capabilities": "node scripts/e2e-capability-modes.mjs",
30
31
  "e2e:duty": "node scripts/e2e-duty-sessions.mjs",
31
32
  "e2e:strict-handoff": "vitest run src/mcp-server/strict-handoff.e2e.test.ts",
32
33
  "e2e:codebuddy:git": "node scripts/e2e-codebuddy-git-task.mjs",