arona-agent 1.0.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.
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/assets/blue-archive/arona/spine/arona_spr.atlas.txt +1168 -0
- package/assets/blue-archive/arona/spine/arona_spr.png +0 -0
- package/assets/blue-archive/arona/spine/arona_spr.skel +0 -0
- package/assets/blue-archive/arona/voice.mp3 +0 -0
- package/assets/blue-archive/hoshino/hoshino_spr.atlas.txt +174 -0
- package/assets/blue-archive/hoshino/hoshino_spr.json +15385 -0
- package/assets/blue-archive/hoshino/hoshino_spr.png +0 -0
- package/assets/blue-archive/hoshino/hoshino_spr.skel +0 -0
- package/assets/blue-archive/hoshino/robber/hoshino_robber_spr.atlas.txt +111 -0
- package/assets/blue-archive/hoshino/robber/hoshino_robber_spr.png +0 -0
- package/assets/blue-archive/hoshino/robber/hoshino_robber_spr.skel +0 -0
- package/assets/blue-archive/hoshino/swimsuit/hoshino_swimsuit_spr.atlas.txt +251 -0
- package/assets/blue-archive/hoshino/swimsuit/hoshino_swimsuit_spr.png +0 -0
- package/assets/blue-archive/hoshino/swimsuit/hoshino_swimsuit_spr.skel +0 -0
- package/assets/blue-archive/hoshino/voice.mp3 +0 -0
- package/assets/blue-archive/plana/spine/plana_spr.atlas.txt +1266 -0
- package/assets/blue-archive/plana/spine/plana_spr.png +0 -0
- package/assets/blue-archive/plana/spine/plana_spr.skel +0 -0
- package/assets/blue-archive/plana/voice.mp3 +0 -0
- package/assets/blue-archive/shiroko/ridingsuit/shiroko_ridingsuit_spr.atlas.txt +97 -0
- package/assets/blue-archive/shiroko/ridingsuit/shiroko_ridingsuit_spr.png +0 -0
- package/assets/blue-archive/shiroko/ridingsuit/shiroko_ridingsuit_spr.skel +0 -0
- package/assets/blue-archive/shiroko/robber/shiroko_robber_spr.atlas.txt +174 -0
- package/assets/blue-archive/shiroko/robber/shiroko_robber_spr.png +0 -0
- package/assets/blue-archive/shiroko/robber/shiroko_robber_spr.skel +0 -0
- package/assets/blue-archive/shiroko/shiroko_spr.atlas.txt +167 -0
- package/assets/blue-archive/shiroko/shiroko_spr.json +10702 -0
- package/assets/blue-archive/shiroko/shiroko_spr.png +0 -0
- package/assets/blue-archive/shiroko/shiroko_spr.skel +0 -0
- package/assets/blue-archive/shiroko/voice.mp3 +0 -0
- package/bin/arona.mjs +43 -0
- package/package.json +37 -0
- package/pet/agents.cjs +197 -0
- package/pet/main.cjs +462 -0
- package/pet/preload.cjs +21 -0
- package/pet/renderer/fx.html +30 -0
- package/pet/renderer/fx.js +41 -0
- package/pet/renderer/gallery.html +18 -0
- package/pet/renderer/gallery.js +31 -0
- package/pet/renderer/index.html +18 -0
- package/pet/renderer/renderer.js +382 -0
- package/pet/renderer/spine_layer.js +609 -0
- package/pet/renderer/spinetest.html +24 -0
- package/pet/renderer/spinetest.js +295 -0
- package/pet/renderer/style.css +63 -0
- package/pet/tools/gallery_capture.cjs +267 -0
- package/pet/tools/gen_sway.cjs +170 -0
- package/pet/tools/meshify_region.cjs +105 -0
- package/pet/tools/skel_inspect.cjs +112 -0
- package/pet/tools/skel_to_json.cjs +410 -0
- package/pet/tools/spine_node.cjs +69 -0
- package/pet/tools/visual_test.cjs +262 -0
- package/pet/vendor/spine/spine-canvas.js +8472 -0
- package/pet/vendor/spine/spine-webgl.js +10839 -0
- package/python/__pycache__/_i18n.cpython-313.pyc +0 -0
- package/python/__pycache__/_i18n.cpython-314.pyc +0 -0
- package/python/__pycache__/computer_use.cpython-314.pyc +0 -0
- package/python/__pycache__/hotkey.cpython-313.pyc +0 -0
- package/python/__pycache__/hotkey.cpython-314.pyc +0 -0
- package/python/__pycache__/stt.cpython-313.pyc +0 -0
- package/python/__pycache__/stt.cpython-314.pyc +0 -0
- package/python/__pycache__/tts.cpython-313.pyc +0 -0
- package/python/__pycache__/tts.cpython-314.pyc +0 -0
- package/python/__pycache__/tts_stream.cpython-313.pyc +0 -0
- package/python/__pycache__/tts_stream.cpython-314.pyc +0 -0
- package/python/__pycache__/voice_clone.cpython-314.pyc +0 -0
- package/python/_i18n.py +18 -0
- package/python/computer_use.py +190 -0
- package/python/hotkey.py +196 -0
- package/python/stt.py +240 -0
- package/python/tts_say.py +177 -0
- package/python/voice_clone.py +98 -0
- package/requirements.txt +6 -0
- package/src/agent.ts +791 -0
- package/src/agent_registry.ts +109 -0
- package/src/commands.ts +641 -0
- package/src/config.ts +245 -0
- package/src/in_memory_credentials.ts +61 -0
- package/src/index.ts +78 -0
- package/src/locale.ts +131 -0
- package/src/logo.ts +120 -0
- package/src/mcp.ts +192 -0
- package/src/memory.ts +303 -0
- package/src/pet.ts +346 -0
- package/src/renderer.ts +350 -0
- package/src/repl.ts +902 -0
- package/src/setup.ts +371 -0
- package/src/skills.ts +96 -0
- package/src/slash_menu.ts +371 -0
- package/src/slash_registry.ts +53 -0
- package/src/speaker_context.ts +41 -0
- package/src/text_split.ts +79 -0
- package/src/tools/computer_use.ts +142 -0
- package/src/tools/emotion_tool.ts +55 -0
- package/src/tools/keep_silent_tool.ts +26 -0
- package/src/tools/memory_tool.ts +24 -0
- package/src/tools/skill_tools.ts +100 -0
- package/src/tools/tavily_tools.ts +431 -0
- package/src/tools/voice_tools.ts +28 -0
- package/src/tts_stream.ts +260 -0
- package/src/tui_select.ts +146 -0
- package/src/undo.ts +451 -0
- package/src/utils/python.ts +211 -0
- package/src/utils/spawn.ts +56 -0
- package/src/voice.ts +83 -0
- package/src/voice_cli.ts +198 -0
- package/src/voices.ts +135 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// 视觉复现 harness:复刻桌宠真实运行时(同 spine_layer.js/renderer.js),合成光标 + 情绪指令时间线 + 定时截屏
|
|
2
|
+
// 用法:env -u ELECTRON_RUN_AS_NODE ./node_modules/.bin/electron --no-sandbox pet/tools/visual_test.cjs
|
|
3
|
+
// 产物:/tmp/petcap_<标签>_<ms>.png(capturePage)+ stdout 打印每帧 renderer 内部状态(CAP 行)与断言结果(CHECK 行)
|
|
4
|
+
// 断言模型(情绪 = track4 数字预设):
|
|
5
|
+
// - 每情绪:track4 = 预设名 + 特征 slot attachment(光环族/嘴型/Eye_Cover)符合期望
|
|
6
|
+
// - 情绪→reset:track4 摘除后 halo 回 halo_normal_00、Mouse_01 回 setup(验 V6 清理路径)
|
|
7
|
+
// - 眨眼:track5 截获;闭眼预设期间 SpineLayer.blink() 返回 false(互斥断言)
|
|
8
|
+
// - 摸头:合成摇动手势 → track0=Pat_01_A、patting=true;离开头部回 Idle(不变)
|
|
9
|
+
const { app, BrowserWindow, ipcMain } = require("electron");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
const fs = require("fs");
|
|
12
|
+
const PET = path.join(__dirname, "..");
|
|
13
|
+
const { AGENTS } = require(path.join(PET, "agents.cjs"));
|
|
14
|
+
|
|
15
|
+
// 测试角色:ARONA_AGENT 环境变量(缺省 arona)。时间线/断言按 Arona 定标,
|
|
16
|
+
// plana 仅保证加载/渲染链路可用(slot 期望值未普查,勿在此断言 plana 表情细节)。
|
|
17
|
+
const AGENT_ID = process.env.ARONA_AGENT && AGENTS[process.env.ARONA_AGENT] ? process.env.ARONA_AGENT : "arona";
|
|
18
|
+
const AGENT = AGENTS[AGENT_ID];
|
|
19
|
+
|
|
20
|
+
let win;
|
|
21
|
+
|
|
22
|
+
// 指令时间线:t 秒时向渲染层发送情绪/重置指令;offs 覆盖默认截屏偏移
|
|
23
|
+
// expect:落定后(+400ms)执行的断言(track4/slot attachment 期望值)
|
|
24
|
+
const TIMELINE = [
|
|
25
|
+
// —— 情绪进/出 + 瞬时切换(原溶解/中断语义已退役)——
|
|
26
|
+
{ t: 2.0, type: "emotion", name: "enjoy", tag: "enjoy",
|
|
27
|
+
expect: { track4: "13", coverL: "L_Eye_Cover_01", halo: "halo_normal_00", mouse: "Mouse_01", gaze: false } },
|
|
28
|
+
{ t: 3.0, type: "emotion", name: "smile", tag: "smile",
|
|
29
|
+
expect: { track4: "99", coverL: "L_Eye_Cover_01", halo: "halo_normal_00", mouse: "Mouse_01", gaze: false } },
|
|
30
|
+
{ t: 4.0, type: "reset", tag: "enjoyBack",
|
|
31
|
+
expect: { track4: null, coverL: null, halo: "halo_normal_00", mouse: "Mouse_01", gaze: true } },
|
|
32
|
+
{ t: 5.0, type: "emotion", name: "love", tag: "love",
|
|
33
|
+
expect: { track4: "11", coverL: "L_Eye_Cover_01", halo: "halo_love_00", gaze: false } },
|
|
34
|
+
{ t: 5.4, type: "reset", tag: "loveBack",
|
|
35
|
+
expect: { track4: null, halo: "halo_normal_00", coverL: null, gaze: true } },
|
|
36
|
+
// —— 禁跟随预设:angry(05)/jealous(07) 睁眼但 gaze=false ——
|
|
37
|
+
{ t: 5.9, type: "emotion", name: "angry", tag: "angry",
|
|
38
|
+
expect: { track4: "05", halo: "halo_angry", mouse: "Mouse_06", gaze: false } },
|
|
39
|
+
{ t: 6.4, type: "reset", tag: "angryBack",
|
|
40
|
+
expect: { track4: null, halo: "halo_normal_00", gaze: true } },
|
|
41
|
+
// —— 说话/疑惑(旧介导断言改为 track4 预设 + 特征 slot)——
|
|
42
|
+
{ t: 7.0, type: "emotion", name: "saying", tag: "saying", offs: [100, 300, 700],
|
|
43
|
+
expect: { track4: "20", mouse: "Mouse_02", halo: "halo_normal_00", gaze: true } },
|
|
44
|
+
{ t: 8.2, type: "reset", tag: "sayingBack", offs: [100, 300, 700],
|
|
45
|
+
expect: { track4: null, mouse: "Mouse_01", gaze: true } },
|
|
46
|
+
{ t: 9.6, type: "emotion", name: "doubt", tag: "doubt", offs: [100, 300, 700],
|
|
47
|
+
expect: { track4: "27", coverL: "L_Eye_Cover_02", halo: "halo_depressed2_00", shadow: "Face_Shadow_01", mouse: "Mouse_11", gaze: false } },
|
|
48
|
+
{ t: 10.2, type: "reset", tag: "doubtBack", offs: [100, 300, 700],
|
|
49
|
+
expect: { track4: null, halo: "halo_normal_00", shadow: null, coverL: null, gaze: true } },
|
|
50
|
+
{ t: 10.8, type: "emotion", name: "jealous", tag: "jealous", offs: [100, 300],
|
|
51
|
+
expect: { track4: "07", halo: "halo_depressed2_00", shadow: "Face_Shadow_01", gaze: false } },
|
|
52
|
+
{ t: 11.4, type: "reset", tag: "jealousBack",
|
|
53
|
+
expect: { track4: null, halo: "halo_normal_00", shadow: null, gaze: true } },
|
|
54
|
+
// —— 中途改写目标:12.2 进 saying,+150ms 改 smile,落定后 preset 应为 smile ——
|
|
55
|
+
{ t: 12.2, type: "emotion", name: "saying", tag: "midIrqA", offs: [100] },
|
|
56
|
+
{ t: 12.35, type: "emotion", name: "smile", tag: "midIrqB", offs: [400, 900],
|
|
57
|
+
expect: { track4: "99", mouse: "Mouse_01", gaze: false } },
|
|
58
|
+
// —— 回 spine 稳定态,给眨眼/摸头留出窗口 ——
|
|
59
|
+
{ t: 14.0, type: "reset", tag: "finalBack",
|
|
60
|
+
expect: { track4: null, halo: "halo_normal_00", mouse: "Mouse_01", gaze: true } },
|
|
61
|
+
// —— 摸头:合成摇动手势(头部区域)→ 锁窗 + Pat + 头部跟随;16.9 光标离开头部 → 结束摸头 ——
|
|
62
|
+
{ t: 16.0, type: "pat", tag: "pat", offs: [200, 600] },
|
|
63
|
+
{ t: 16.9, type: "patExit", tag: "patExit", offs: [200, 600] },
|
|
64
|
+
];
|
|
65
|
+
// 每条指令后 +50/+150/+400ms 截屏;带 offs 的步骤用覆盖
|
|
66
|
+
const CAP_OFFSETS = [50, 150, 400];
|
|
67
|
+
|
|
68
|
+
// 空闲验收超时(眨眼截获等待上限)
|
|
69
|
+
const IDLE_WATCH_TIMEOUT_MS = 45000;
|
|
70
|
+
|
|
71
|
+
const CAP_JS = `JSON.stringify({
|
|
72
|
+
spine: window.SpineLayer.getState(),
|
|
73
|
+
slots: {
|
|
74
|
+
halo: window.SpineLayer.getSlotAttachment("halo_normal_00"),
|
|
75
|
+
mouse: window.SpineLayer.getSlotAttachment("Mouse_01"),
|
|
76
|
+
coverL: window.SpineLayer.getSlotAttachment("L_Eye_Cover_01"),
|
|
77
|
+
shadow: window.SpineLayer.getSlotAttachment("Face_Shadow_01"),
|
|
78
|
+
sweat: window.SpineLayer.getSlotAttachment("Sweat_01"),
|
|
79
|
+
},
|
|
80
|
+
anims: document.getAnimations().length
|
|
81
|
+
})`;
|
|
82
|
+
|
|
83
|
+
async function capNow(tag, ms) {
|
|
84
|
+
if (!win || win.isDestroyed()) return;
|
|
85
|
+
try {
|
|
86
|
+
// 先读状态再截图:眨眼/闭眼等瞬时状态不会在截图时刻已过期
|
|
87
|
+
const dbg = await win.webContents.executeJavaScript(CAP_JS);
|
|
88
|
+
const img = await win.webContents.capturePage();
|
|
89
|
+
fs.writeFileSync(`/tmp/petcap_${tag}_${ms}.png`, img.toPNG());
|
|
90
|
+
console.log("CAP", tag, ms, dbg);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
console.log("CAP", tag, ms, "ERR", e.message);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function cap(tag, ms) {
|
|
97
|
+
setTimeout(() => void capNow(tag, ms), ms);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 落定断言:读 getState + slot attachment,对照 expect 逐项输出 CHECK
|
|
101
|
+
function check(tag, ms, expect) {
|
|
102
|
+
setTimeout(async () => {
|
|
103
|
+
if (!win || win.isDestroyed()) return;
|
|
104
|
+
try {
|
|
105
|
+
const st = JSON.parse(await win.webContents.executeJavaScript(
|
|
106
|
+
`JSON.stringify({ spine: window.SpineLayer.getState(), slots: {
|
|
107
|
+
halo: window.SpineLayer.getSlotAttachment("halo_normal_00"),
|
|
108
|
+
mouse: window.SpineLayer.getSlotAttachment("Mouse_01"),
|
|
109
|
+
coverL: window.SpineLayer.getSlotAttachment("L_Eye_Cover_01"),
|
|
110
|
+
shadow: window.SpineLayer.getSlotAttachment("Face_Shadow_01"),
|
|
111
|
+
sweat: window.SpineLayer.getSlotAttachment("Sweat_01") } })`
|
|
112
|
+
));
|
|
113
|
+
const fails = [];
|
|
114
|
+
if (expect.track4 !== undefined && st.spine.track4 !== expect.track4)
|
|
115
|
+
fails.push(`track4=${st.spine.track4} != ${expect.track4}`);
|
|
116
|
+
if (expect.preset !== undefined && st.spine.preset !== expect.preset)
|
|
117
|
+
fails.push(`preset=${st.spine.preset} != ${expect.preset}`);
|
|
118
|
+
if (expect.gaze !== undefined && st.spine.gaze !== expect.gaze)
|
|
119
|
+
fails.push(`gaze=${st.spine.gaze} != ${expect.gaze}`);
|
|
120
|
+
for (const k of ["halo", "mouse", "coverL", "shadow", "sweat"]) {
|
|
121
|
+
if (expect[k] !== undefined && st.slots[k] !== expect[k])
|
|
122
|
+
fails.push(`${k}=${st.slots[k]} != ${expect[k]}`);
|
|
123
|
+
}
|
|
124
|
+
console.log("CHECK", tag, fails.length ? "FAIL " + fails.join("; ") : "OK");
|
|
125
|
+
} catch (e) {
|
|
126
|
+
console.log("CHECK", tag, "ERR", e.message);
|
|
127
|
+
}
|
|
128
|
+
}, ms);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 摸头手势合成:mousedown 于头部中心 → 左右交替 mousemove(≥3 次换向)→ 光标固定偏右
|
|
132
|
+
async function synthPat(downX, downY, shakeAmp) {
|
|
133
|
+
const seq = [];
|
|
134
|
+
for (let i = 0; i < 7; i++) seq.push(downX + (i % 2 === 0 ? -1 : 1) * shakeAmp);
|
|
135
|
+
await win.webContents.executeJavaScript(
|
|
136
|
+
`document.dispatchEvent(new MouseEvent("mousedown", { clientX: ${downX}, clientY: ${downY}, screenX: ${downX}, screenY: ${downY}, bubbles: true }))`
|
|
137
|
+
);
|
|
138
|
+
for (const x of seq) {
|
|
139
|
+
await win.webContents.executeJavaScript(
|
|
140
|
+
`document.dispatchEvent(new MouseEvent("mousemove", { clientX: ${x}, clientY: ${downY}, screenX: ${x}, screenY: ${downY}, bubbles: true }))`
|
|
141
|
+
);
|
|
142
|
+
await new Promise((r) => setTimeout(r, 60));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
app.whenReady().then(() => {
|
|
147
|
+
win = new BrowserWindow({
|
|
148
|
+
width: 320,
|
|
149
|
+
height: 674,
|
|
150
|
+
transparent: true,
|
|
151
|
+
frame: false,
|
|
152
|
+
alwaysOnTop: true,
|
|
153
|
+
webPreferences: {
|
|
154
|
+
preload: path.join(PET, "preload.cjs"),
|
|
155
|
+
contextIsolation: true,
|
|
156
|
+
nodeIntegration: false,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
// isMain:false:回归只测摸头路径,明确禁用 dizzy 检测,合成手势不受新逻辑干扰
|
|
160
|
+
ipcMain.handle("pet:get-agent-config", () => ({ id: AGENT_ID, isMain: false, ...AGENT }));
|
|
161
|
+
ipcMain.on("pet:drag", () => {});
|
|
162
|
+
ipcMain.on("pet:dragend", () => {});
|
|
163
|
+
ipcMain.on("pet:shake", () => {});
|
|
164
|
+
ipcMain.on("pet:dizzy", () => {});
|
|
165
|
+
// 忽略真实鼠标输入:测试期间用户鼠标划过窗口会触发真实 mousemove,干扰合成手势
|
|
166
|
+
// (合成事件走 document.dispatchEvent,不受影响)
|
|
167
|
+
win.webContents.on("console-message", (event) => console.log("[R]", event && event.message));
|
|
168
|
+
win.webContents.on("did-finish-load", () => win.setIgnoreMouseEvents(true));
|
|
169
|
+
win.loadFile(path.join(PET, "renderer", "index.html"), { search: "idledebug&agent=" + AGENT_ID });
|
|
170
|
+
win.webContents.on("did-finish-load", async () => {
|
|
171
|
+
// 等 Spine 基底就绪后再起时间线
|
|
172
|
+
const tReady = Date.now();
|
|
173
|
+
while (!win || !win.isDestroyed()) {
|
|
174
|
+
try {
|
|
175
|
+
const ok = await win.webContents.executeJavaScript("window.SpineLayer && window.SpineLayer.getState().track0 === 'Idle_01'");
|
|
176
|
+
if (ok) break;
|
|
177
|
+
} catch {}
|
|
178
|
+
if (Date.now() - tReady > 15000) {
|
|
179
|
+
console.log("FATAL spine init timeout");
|
|
180
|
+
app.quit();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
184
|
+
}
|
|
185
|
+
const t0 = Date.now();
|
|
186
|
+
console.log("READY after", Date.now() - tReady, "ms");
|
|
187
|
+
|
|
188
|
+
// 光标注入:0~15.9s 绕窗口转圈(空闲注视用);摸头窗口期 16.0~16.9s 固定偏右(头部跟随可断言)
|
|
189
|
+
const timer = setInterval(() => {
|
|
190
|
+
if (!win || win.isDestroyed()) return;
|
|
191
|
+
const t = (Date.now() - t0) / 1000;
|
|
192
|
+
if (t < 15.9) win.webContents.send("pet:cursor", 160 + 200 * Math.cos(t * 3), 200 + 150 * Math.sin(t * 3));
|
|
193
|
+
else if (t < 17.5) win.webContents.send("pet:cursor", 300, 80); // 头部右侧远处 → 头部右转
|
|
194
|
+
}, 16);
|
|
195
|
+
|
|
196
|
+
for (const step of TIMELINE) {
|
|
197
|
+
setTimeout(async () => {
|
|
198
|
+
if (!win || win.isDestroyed()) return;
|
|
199
|
+
if (step.type === "emotion") win.webContents.send("pet:emotion", step.name);
|
|
200
|
+
else if (step.type === "reset") win.webContents.send("pet:reset");
|
|
201
|
+
else if (step.type === "pat") {
|
|
202
|
+
// 头部区域(窗口比例 26%-78% x,6%-29% y)中心
|
|
203
|
+
await synthPat(160, 90, 25);
|
|
204
|
+
} else if (step.type === "patExit") {
|
|
205
|
+
// 光标离开头部区域(90px 缓冲外)→ 结束摸头,窗口仍锁定
|
|
206
|
+
await win.webContents.executeJavaScript(
|
|
207
|
+
`document.dispatchEvent(new MouseEvent("mousemove", { clientX: 300, clientY: 400, screenX: 300, screenY: 400, bubbles: true }))`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}, step.t * 1000);
|
|
211
|
+
for (const off of step.offs || CAP_OFFSETS) cap(step.tag, step.t * 1000 + off);
|
|
212
|
+
if (step.expect) check(step.tag, step.t * 1000 + 400, step.expect);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 眨眼互斥断言:
|
|
216
|
+
// t=2.6s(enjoy=13 闭眼预设中)blink() 必须返回 false
|
|
217
|
+
// t=7.6s(saying=02 睁眼预设中)blink() 必须返回 true
|
|
218
|
+
// 注意:只截获不退出——时间线后面的步骤还要跑,收尾由 FINAL 定时器负责。
|
|
219
|
+
let gotBlink = false;
|
|
220
|
+
setTimeout(async () => {
|
|
221
|
+
try {
|
|
222
|
+
const r = await win.webContents.executeJavaScript("window.SpineLayer.blink()");
|
|
223
|
+
console.log("CHECK blinkClosed(enjoy) ", r === false ? "OK" : "FAIL got " + r);
|
|
224
|
+
} catch (e) { console.log("CHECK blinkClosed ERR", e.message); }
|
|
225
|
+
}, 2600);
|
|
226
|
+
setTimeout(async () => {
|
|
227
|
+
try {
|
|
228
|
+
const r = await win.webContents.executeJavaScript("window.SpineLayer.blink()");
|
|
229
|
+
console.log("CHECK blinkOpen(saying) ", r === true ? "OK" : "FAIL got " + r);
|
|
230
|
+
} catch (e) { console.log("CHECK blinkOpen ERR", e.message); }
|
|
231
|
+
}, 7600);
|
|
232
|
+
|
|
233
|
+
// 眨眼截获:轮询 spine.track5 === "Eye_Close_01"(idledebug 下 1~2s 一次,睁眼窗口期可中),命中即截闭眼中间帧。
|
|
234
|
+
const idleTimer = setInterval(async () => {
|
|
235
|
+
if (!win || win.isDestroyed()) return;
|
|
236
|
+
try {
|
|
237
|
+
const st = JSON.parse(await win.webContents.executeJavaScript(
|
|
238
|
+
`JSON.stringify({ t5: window.SpineLayer.getState().track5 })`
|
|
239
|
+
));
|
|
240
|
+
const elapsed = Date.now() - t0;
|
|
241
|
+
if (!gotBlink && st.t5 === "Eye_Close_01") {
|
|
242
|
+
gotBlink = true;
|
|
243
|
+
console.log("IDLE blink caught at", elapsed, "ms");
|
|
244
|
+
await capNow("idleBlink", elapsed);
|
|
245
|
+
setTimeout(() => void capNow("idleBlink_mid", elapsed + 60), 60); // 闭眼中间帧(Eye_Close_01 0.03~0.10s 为闭眼段)
|
|
246
|
+
clearInterval(idleTimer);
|
|
247
|
+
}
|
|
248
|
+
} catch {}
|
|
249
|
+
}, 40);
|
|
250
|
+
// 收尾:时间线全部跑完(patExit 落定后)打印收尾状态并退出
|
|
251
|
+
setTimeout(async () => {
|
|
252
|
+
try {
|
|
253
|
+
const dbg = await win.webContents.executeJavaScript(CAP_JS);
|
|
254
|
+
console.log("FINAL", dbg);
|
|
255
|
+
} catch {}
|
|
256
|
+
clearInterval(timer);
|
|
257
|
+
app.quit();
|
|
258
|
+
}, 19500);
|
|
259
|
+
setTimeout(() => { clearInterval(timer); clearInterval(idleTimer); app.quit(); }, IDLE_WATCH_TIMEOUT_MS);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
app.on("window-all-closed", () => app.quit());
|