diff --git a/README.md b/README.md index 047b409..095a11d 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ - **完整實作 Codex V2 pet contract** — 8 欄 × 11 列、192×208 cell 的 spritesheet,9 個標準動作列 + 16 個看向方向,**每一幀的時長都照 Codex 規格**,動起來跟在 Codex app 裡一模一樣。 - **16 方向追視** — 游標在哪,她就看哪(000 = 正上方、順時針 22.5° 一格)。游標靠太近或靜止 6 秒就回到 idle 呼吸動畫。 +- **與 Codex 同尺寸** — 100% 就是 Codex 桌面寵物的大小(126 × 137),高度用 Codex 的 `ceil(寬 × 208/192)` 算,兩邊並排看起來一樣大。 - **真正的桌面寵物** — 透明、無邊框、永遠置頂;透明區域**點穿**,只有壓在人物像素上滑鼠才會被吃掉,不擋你操作底下的視窗。 - **不搶焦點** — 視窗設為 non-focusable,點她不會讓終端機失焦。 - **拖曳 / 點擊 / 右鍵** — 拖曳時依方向播 running-left / running-right 並記住位置;點一下揮手;右鍵或系統匣開選單。 @@ -73,7 +74,7 @@ npm run autostart:install # (可選)開機自動啟動 **選單功能** - **切換寵物** — 列出所有找到的寵物;v1(9 列)寵物會標示「無追視」 -- **大小** — 50% / 75% / 100% / 125% / 150%,以腳底為基準縮放 +- **大小** — 50% / 75% / 100% / 125% / 150%,以腳底為基準縮放(**100% = Codex 桌面寵物的大小**) - **跟著游標看**、**永遠置頂**、**開機自動啟動** — 勾選即生效 - **動作測試** — 不用真的跑 Claude Code 也能看每個動畫(idle / running / waiting / review / failed / jumping / waving) - **Claude Code hooks** — 安裝 / 移除 hooks @@ -172,7 +173,7 @@ Spritesheet:**1536 × 2288**,8 欄 × 11 列,每格 **192 × 208**,透 |---|---|---| | `activePetId` | `"xiao-nian"` | 目前使用的寵物 id | | `petSources` | `[/pets, ~/.codex/pets]` | 掃描寵物的資料夾清單;程式自己的 `pets/` 永遠會被加在最前面 | -| `scale` | `1` | 縮放:0.5 / 0.75 / 1 / 1.25 / 1.5 | +| `scale` | `1` | 縮放:0.5 / 0.75 / 1 / 1.25 / 1.5。`1` 表示 126 × 137,與 Codex 桌面寵物同大小 | | `position` | `null` | 視窗左上角座標;`null` 為主螢幕右下角。若座標落在所有螢幕之外會自動拉回 | | `alwaysOnTop` | `true` | 永遠置頂 | | `followCursor` | `true` | 追視游標 | @@ -208,6 +209,7 @@ Claude Code ──hook(stdin JSON)──▶ hook/claude-pet-hook.js ──PO - **點穿**:視窗預設 `setIgnoreMouseEvents(true, { forward: true })`,renderer 在 mousemove 時讀 canvas 該點 alpha(或是否在氣泡上)來即時切換,所以透明處永遠點得到底下的視窗。 - **拖曳**:由主程序用 `screen.getCursorScreenPoint()` 每 16 ms 更新視窗位置,游標離開視窗也不會掉;方向由水平位移決定。 +- **顯示尺寸與 sprite 來源尺寸分開**:`CELL_W/CELL_H`(192 × 208)只用來從 spritesheet 取格子,畫到畫面上的是 `PET_W/PET_H`(126 × 137,Codex 的 mascot 尺寸)。視窗寬度另有 `MIN_WIN_W = 240` 的下限,免得寵物變小後氣泡被截掉。 - **Spritesheet 以 data URL 傳給 renderer**,避免 `file://` 跨來源污染 canvas 導致 `getImageData` 失敗。 - **hook 合併規則**:以指令中是否含 `claude-pet-hook` 辨識自己的項目,重裝只替換自己的、不動別人的。 diff --git a/main.js b/main.js index 177600c..d928cc6 100644 --- a/main.js +++ b/main.js @@ -13,11 +13,15 @@ const CONFIG_DIR = path.join(HOME, ".claude-pet"); const CONFIG_PATH = path.join(CONFIG_DIR, "config.json"); const LOG_PATH = path.join(CONFIG_DIR, "events.log"); -const CELL_W = 192; +const CELL_W = 192; // spritesheet 單格來源尺寸 const CELL_H = 208; +// 100% 時的顯示尺寸,對齊 Codex 桌面寵物:寬度為基準,高度用 Codex 的 ake() 公式 ceil(w * 208/192) +const PET_W = 126; +const PET_H = Math.ceil((PET_W * CELL_H) / CELL_W); // 137 const BUBBLE_H = 72; // 氣泡保留高度 -const PAD_X = 24; // 左右透明邊 +const PAD_X = 24; // 左右最小透明邊 const PAD_BOTTOM = 8; +const MIN_WIN_W = 240; // 視窗最小寬度,保留氣泡的可讀寬度(Codex 的氣泡也遠寬於寵物本身) const SCALES = [0.5, 0.75, 1, 1.25, 1.5]; const DEFAULT_CONFIG = { @@ -125,8 +129,8 @@ function petPayload(pet) { function windowSizeFor(scale) { return { - width: Math.round(CELL_W * scale) + PAD_X * 2, - height: Math.round(CELL_H * scale) + BUBBLE_H + PAD_BOTTOM, + width: Math.max(Math.round(PET_W * scale) + PAD_X * 2, MIN_WIN_W), + height: Math.round(PET_H * scale) + BUBBLE_H + PAD_BOTTOM, }; } @@ -237,7 +241,7 @@ function startCursorPolling() { lastCursor = p; const b = win.getBounds(); const cx = b.x + b.width / 2; - const cy = b.y + BUBBLE_H + CELL_H * config.scale * 0.3; // 大約頭部的高度 + const cy = b.y + BUBBLE_H + PET_H * config.scale * 0.3; // 大約頭部的高度 send("pet:cursor", { dx: p.x - cx, dy: p.y - cy }); }, 50); } diff --git a/renderer/index.html b/renderer/index.html index 5e15f0d..f9a858d 100644 --- a/renderer/index.html +++ b/renderer/index.html @@ -9,7 +9,7 @@
- +
diff --git a/renderer/renderer.js b/renderer/renderer.js index 36f382d..316778b 100644 --- a/renderer/renderer.js +++ b/renderer/renderer.js @@ -2,8 +2,11 @@ // Claude Pet renderer:Codex V2 pet contract 的動畫引擎 + 狀態機 // 規格來源:8 欄 x 11 列、192x208 cell,列 0-8 標準動作、列 9-10 為 16 個順時針看向方向(000 = 正上方) -const CELL_W = 192; +const CELL_W = 192; // spritesheet 單格來源尺寸 const CELL_H = 208; +// 100% 時的顯示尺寸,對齊 Codex 桌面寵物(與 main.js 的 PET_W / PET_H 一致) +const PET_W = 126; +const PET_H = Math.ceil((PET_W * CELL_H) / CELL_W); // 137 const ROW = { idle: 0, @@ -30,7 +33,7 @@ const TIMINGS = { review: [150, 150, 150, 150, 150, 280], }; -const LOOK_DEADZONE = 56; // 游標離寵物中心太近 → 回到 idle +const LOOK_DEADZONE = 38; // 游標離寵物中心太近 → 回到 idle(約寵物高度的 0.27) const LOOK_IDLE_AFTER = 6000; // 游標靜止多久後不再盯著看 const REVIEW_DURATION = 6000; // Stop 後 review 多久回 idle const SESSION_STALE = 30 * 60 * 1000; @@ -80,8 +83,8 @@ function loadPet(pet) { function applyScale(s) { scale = s; - canvas.width = Math.round(CELL_W * s); - canvas.height = Math.round(CELL_H * s); + canvas.width = Math.round(PET_W * s); + canvas.height = Math.round(PET_H * s); canvas.style.width = `${canvas.width}px`; canvas.style.height = `${canvas.height}px`; }