这是篡改猴四件套(核心脚本 / 辅助脚本 / 精简数据库 / 轻量杀手监控)的功能查阅书:每个功能做什么、大概怎么做、有哪些参数和命令、为什么会有它。
图例 测试线独有最早在测试线上线的功能(2026-09-19 已随测试线整体合并进主代码,标记保留作来历) | 已停用已停用,代码保留 | 仅观测仅观测:只打日志做诊断,不改变任何行为
条目格式 功能名〔标记〕— 做什么 + 怎么做的要点。参数:…|命令…|来历…|版本…(没有的字段不写)
总览
三层版本
本手册以测试线(beta)为准,它是功能最全的一层。2026-09-19 测试线已整体合并进主代码(两条线当天版本相同),合并前的主代码打成了稳定版 v1.4.0。之后测试线再有新改动,版本标签写"测试版 x.y.z"且晚于下表主代码版本的,才是测试线独有。
| 层 | 核心 | 辅助 | 精简数据库 | 轻量杀手监控 | 说明 |
|---|---|---|---|---|---|
| 测试线 beta | 1.2.61 | 1.2.19 | 1.2.6 | 1.2.14 | 2026-09-21,本手册依据 |
| 主代码 | 1.2.61 | 1.2.19 | 1.2.6 | 1.2.14 | 其他账户自动更新拿到的版本(2026-09-19 起与测试线同步) |
| 稳定版 Release v1.4.0 | 1.2.39 | 1.2.9 | 1.2.3 | 1.2.7 | 2026-09-19 合并前的主代码快照,回退锚点(更早的 v1.3.0 仍可下载) |
四个脚本的分工
- 核心脚本 整个套件的主流程。每轮扫一遍 kami:该停采的停采(日常批量 + 紧急停采两级)、休息的凑批部署、低血和饿死的喂食、死亡的复活,外加拾荒、XP 药水合成与喂食。所有链上 tx 都经过它的"紧急锁 + 普通锁"排队,gas 真值账本、死亡/停摆监控、保活、日志落盘也都在这里。
- 辅助脚本 核心的配套,必须和核心同时启用。在卡片上显示清算线并把危险的标红;每 6 小时全网扫描最强杀手,按官方公式重算全库精确清算线;自动升级、按标准路线加点、必要时洗点;步长够时自动合成;刷新后抢先复活;按规则在物品池自动买 Pine Cone(测试版 1.2.17 加,1.2.18 起默认开、每 7 天最多买一次);地块适配分析(
kamiAnalyze)和代码健康看板(showHealth)。 - 精简数据库 给整个套件建"精简数据库":扫描账户全部 kami,每只压成 17 个字段(编号、链上 ID、清算线、体型/手型、harmony、maxhp、出生属性等)存进
localStorage.kami_core_db。首次安装或数据异常时启用跑一次,建完建议停用;日常新 kami 由核心的syncKamiDb()增量补。 - 轻量杀手监控 盯一份自己维护的杀手 kami 名单,纯 API 轮询、不读页面。杀手出现在你的采集房间或隔壁时告警,并调用核心的
emergencyStopHarvest()紧急停采;会过滤掉"主人 24 小时没动作"和"隔壁 10 分钟既没杀人也没移动"的杀手,避免白停。自家杀手自动登记进MY_KILLER_KAMIS,让核心和辅助跳过它们。
使用须知
- 核心和辅助要同时启用:缺辅助时合成读不到步长会跳过,转移停采不可用。杀手监控没有核心时只告警不停采。
- 脚本会替你发真实链上交易、花 gas。批量操作按大账户(kami 多于 7 只)设计,凑够 6 只才发一批。
- 四个脚本共用一个日志缓冲区,
saveKamiLogs()一次导出全部。
一、核心脚本
核心脚本是整个套件的主流程:部署、停采、喂食、复活、拾荒、XP 药水都在这里,所有 tx 都经它的锁排队。
1.1 启动与页面生命周期
脚本注入后怎么等游戏加载、怎么防止双开、什么时候整页刷新,以及刷新或关页时怎么保住日志。
脚本头与运行前提
- 篡改猴元数据头 — 声明脚本名、版本、作者、匹配域名与注入时机,让篡改猴在游戏页自动注入核心脚本。@match https://*.kamigotchi.io/*,@grant none,@run-at document-idle,@version v1.2.58。注释(第92行)写「@name 固定不带版本号,篡改猴才会自动拉新版」,本地这份文件的 @name 实际带版本号「Kamigotchi核心脚本-测试版-1.2.58」(发布器可能改写,以发布产物为准)。触发:篡改猴加载页面时|版本测试版1.2.58
- 脚本总览与设计哲学说明仅观测 — 文件头的大框,列出九大功能:自动部署、停采(普通+紧急两级)、喂食、复活、XP药水、合成、拾荒、杀手检测、Gas统计,并写明设计哲学。设计哲学:少发 TX、省 gas、单个周期里尽量多采;所有批量决策都要落在 gas 成本曲线的甜区里。触发:文档(不执行)
- 运行前提与依赖说明仅观测 — 说明核心必须和辅助脚本一起启用、靠精简数据库
window.kami_core_db存清算线,脚本面向 kami 超过7只的大账户,而且会真发交易、真花 gas。缺辅助脚本时合成读不到步长会跳过,转移停采命令也用不了。数据库启动时自动恢复,有新 kami 时增量自愈,首次全量构建要跑精简数据库脚本。凑批门槛≥6只,小账户可能长期凑不满。建议先小规模观察几个循环。触发:文档(不执行)|参数凑批门槛≥6只;大账户定义>7只|存储kami_core_db(localStorage) - 测试版与公开版互斥警告仅观测 — 提醒测试版和公开版的 @name 不同,可以同时装上;两个核心同时跑会双份操作、互抢 nonce,所以装测试版前必须先停用公开版。文档提示。代码兜底见「单实例守卫」。触发:文档(不执行)|命令
安装说明()|来历两核心同跑=双份部署/停采/喂食+两条通道抢nonce白烧gas
单实例与启动提示
- 重复核心实例拒绝启动 — 第一个加载的核心在 window 上插旗;后加载的核心看到旗子就整体不启动,并用红底大字说明本页已有哪个线/版本在跑、该去关哪个。本线
SCRIPT_VARIANT='测试版'。window.__kamiCoreInstance存在且带 variant 时,打印红底提示,写window.__kamiCoreDuplicateBlocked={blocked,running,at},然后 return。否则插旗 {variant,version:null,at},version 稍后由版本检查回填。刷新页面会清空 window,iframe 也是独立 window,所以不误伤。只有先加载的实例同样会插这个旗时才识别得出。另外 'use strict' 写在守卫之后、不在函数首句,实际不生效(无行为影响)。触发:脚本注入时立即执行|参数SCRIPT_VARIANT='测试版'|来历0911起公开版/测试版两条发行线@name不同可被同时启用,同页双核心=双份部署/停采/喂食+nonce互抢,白烧gas甚至挤掉真正要发的交易 - 守卫fail-open — 守卫读旗子时出任何异常都照常启动核心,只有确实读到旗子才拦。整段包在 try 里,catch 为空,继续往下启动。触发:守卫执行异常时|来历宁可重复也不能让核心整个不跑——不跑就没人停采,kami会被清算
- 启动成功横幅仅观测 — 脚本注入后立即打一条绿底白字大号横幅:「Kamigotchi核心脚本-测试版 v版本(构建时间)已成功启动,等待网页加载完成…」。本脚本最早的控制台输出之一。触发:脚本注入后立即|版本1.1.20
- 防睡眠与Chrome强节流挂机提示仅观测 — 启动时提醒:夜里挂机要先关电脑自动睡眠(必做);Chrome 后台页强节流会让夜里紧急停采慢 1~2 分钟,测试版 1.2.59 起写明脚本按「默认会被节流」设计、关不关可选,并纠正 Mac 指引(defaults write 是推荐级不生效,要 ⌘Q 后带 --disable-background-timer-throttling 启动),附 Mac/Windows 睡眠设置路径。四条彩色 log,纯提示,无副作用。触发:脚本注入后立即|命令
安装说明()|来历电脑睡眠会让页面JS定时器全停,HP跌破清算线被清算;Chrome强节流会让夜里紧急停采慢1~2分钟 - 小账户kami数量警告仅观测 — 启动60秒后统计当前账户 kami 数:1~7只时用红字警告凑批策略可能不适用,超过7只时绿字确认凑批正常生效。地址兼容
connectedAddress.value_和 .value 两种字段,再用getByOperator取 kamis 数组长度;取不到地址直接返回,kamis 不是数组记-1、两个分支都不命中。红字警告共4行:凑批要攒≥6只 urgent 才批量停采,小账户可能反复「跳过本轮」让 kami 在边缘等死,建议养到7只以上或手动调emergencyStopHarvest。异常静默。触发:自动:启动60s后一次|参数延迟60s;阈值7只(对应凑批下限6)|命令emergencyStopHarvest()|来历小账户可能永远凑不满一批,紧急停采反复跳过,kami在清算边缘等死 - 启动命令清单banner仅观测 — 启动3秒后打印完整命令清单,按紧急控制/模式切换/黑名单/喂食重置/Gas·TX状态/Gas账本/数据库/日志调试/安装更新分组;每条命令独占一行可直接复制,高危高频命令红字高亮。纯打印,全部走 clog 进日志。文案与实现有两处不一致:①写「旧名 'starving' 仍兼容」,但
setKamiMode实际会拒绝 starving 并提示改用 greedy;②写setStopTxChannel「mud(默认)」,代码缺省是 raw。末尾附提示:切地块前用stopCurrentRoom、多账户分工先kamiAnalyze再stopMinorityForTransfer、新增 kami 用syncKamiDb、查 gas 用showGasReport、装机说明用安装说明()。触发:自动:启动3s后一次|参数延迟3000ms|命令emergencyStopHarvest();stopCurrentRoom();stopMinorityForTransfer();resumeDeploy();setKamiMode('greedy'|'normal');getKamiMode();showBlockedKamis();showStopBlockedKamis();clearBlockedKamis();clearStopBlockedKamis();clearFeedFails();clearStarvingStuck();clearXPPotionFed();clearFortifiedFed();feedXPPotionNow();showMyKillers();showGasRules();getTxLockStatus();setStopTxChannel('mud'|'raw');setKeepAlive('on'|'off');showGasReport();syncKamiDb();window.kami_core_db;saveKamiLogs();kamiDebugOn({ parse: true });kamiDebugOff();安装说明();kamiAnalyze()【辅助】|版本1.1.17 - 测试版双开人眼兜底横幅测试线独有仅观测 — banner 顶部打一条紫底醒目横幅「测试线独有 测试版核心运行中 v版本(构建时间)」,并说明若同一控制台同时看到「公开版」横幅就是双开了。作为代码守卫之外的人眼兜底。触发:自动:启动3s后|来历两个核心同跑会双份交易+抢nonce|版本测试版
版本号与版本检查
- 版本常量收敛与构建时间自报测试线独有 — 版本号、线名、构建时间收敛成三个常量
SCRIPT_VERSION、SCRIPT_LINE、SCRIPT_BUILT,启动 log、命令 banner、版本检查都引用它们。SCRIPT_BUILT由发布器打包时注入真实发布时间(版本没变就沿用旧日期),本地未发布时保持占位「(本地未发布)」。日志里看到这个占位,就说明这份不是从GitHub装的。触发:脚本注入时|参数SCRIPT_VERSION='1.2.58';SCRIPT_LINE='测试版'|来历0913某账户实盘日志:辅助@version已是1.2.10,启动log却打v1.2.8(数据库/监控同类),导致两次误判「beta没装全」「监控没更新」——日志撒谎比没有日志更糟|版本测试版1.2.47 - 启动版本检查仅观测 — 启动8秒后拉
GitHub上 beta 版的meta.js,对比本机版本,打「已是最新 / 请更新 / 本地比远端新」三种提示,并带上发布日期和本机安装时间。URL 加时间戳 cache-bust,并用 cache:no-store。正则解析 @version 和 @x-release-date,按段逐个数字比较。解析不到版本号就打一条跳过日志。触发:自动:启动8s后|参数延迟8000ms|版本1.1.18 - 本机版本首次运行时间记录仅观测 — 第一次见到某个版本时记下当时时间,近似篡改猴的安装/更新时间,显示在版本检查日志里。key 为
kami_ver_seen_核心脚本_版本号,不存在就写当前本地时间;localStorage不可用时记「未知」。触发:版本检查初始化时|存储kami_ver_seen_核心脚本_<版本>|来历无法直接读篡改猴的安装时间|版本1.1.18 - 版本号全局回写测试线独有 — 把本脚本版本号写回单实例旗子
__kamiCoreInstance.version,并写入window.__kamiCoreVersion。前者让重复实例的拦截提示能显示正在运行的版本,后者供安装说明()打印,省掉一处版本号维护。触发:版本检查初始化时|命令安装说明()|来历避免多出一处需同步修改的版本号|版本测试版1.2.47 - 版本检查CSP失败降噪仅观测 — 拉
GitHub失败(绝大多数是游戏页 CSP 拒绝外联)时,24小时内只提示一次「属正常,不影响自动更新」,并附手动检查路径。时间戳存在localStorage,24小时内命中就静默;localStorage不可用时每次都打。触发:版本检查 fetch 失败时|参数节流窗口86400000ms|存储kami_vercheck_csp_note_核心脚本|来历游戏SPA运行时注入CSP connect-src白名单,raw外联在游戏页永久失败,旧版每次页面加载都打一行=刷屏|版本1.1.21
启动主流程与页面自检
- 启动前等待 120~150 秒(随机错峰) — 脚本注入后先等页面充分加载,再启动各业务模块。
initialDelay= 120 秒 +getRandomDelayMs(30/60),后者是 0~30 秒随机,所以总共 120~150 秒;开始时打一条「等待 X 秒」日志。触发:脚本注入时排定|参数基础 120*1000ms;抖动getRandomDelayMs(30/60)=0~30s|来历随机化是为了多账户、多标签页错峰启动 - 启动倒计时日志仅观测 — 等待期间定期打印剩余秒数,方便确认脚本还活着。每秒检查一次,剩余秒数是 10 的倍数或只剩最后 10 秒时打「⌛ 剩余 N 秒后启动…」;到 0 清掉定时器,正式启动时也会先清一次。触发:脚本注入后每秒|参数打印规则
secondsLeft%10===0 或 ≤10|来历方便确认脚本存活 - 启动前页面自检(未就绪则整条启动链中断) — 倒计时结束后先做页面自检,没就绪就刷新重来,后续模块一个都不启动,绝不带病启动。await
checkAndRefreshIfNeeded(),返回 false 就 return;通过后打「✅ 页面已完全加载,继续启动」。触发:初始延迟结束|来历绝不带病启动 - 自检 1:钱包仍在连接中 — 钱包连接弹窗还显示着,就判定页面未就绪,原因是「钱包连接中」。#wallet-connector 存在且内联
style.display!== 'none'。代码实际读的是内联样式,和detectGameError用computedStyle不同:元素没有内联 display 时也会被当成显示中。触发:被checkAndRefreshIfNeeded()调用 - 自检 2:账户注册弹窗显示中 — 账户注册弹窗(#account-registrar)显示着,判定未就绪,原因是「需要注册账户」。同样读内联
style.display。按代码实际:命中后照样走 10~20 秒后刷新,刷新解决不了注册问题,所以在人工注册之前会一直循环刷新(每次还要先等启动延迟)。触发:被checkAndRefreshIfNeeded()调用 - 自检 3:Unknown error 崩溃界面 — 页面正文包含 "Unknown error" 就判定未就绪。
document.body?.innerText?.includes('Unknown error')触发:被checkAndRefreshIfNeeded()调用 - 自检 4:可见的「You are Kamiless」提示 — Party 里显示「You are Kamiless」(名下没有 kami 或列表没加载对),判定未就绪;隐藏在 DOM 里的这句提示不算。遍历 #party 下所有元素,找到文字含该提示的元素后,先看自身
computedStyle的 display 和 visibility,再沿父链一级级往上查到 body,任何一级 display:none 或 visibility:hidden 都算不可见。触发:被checkAndRefreshIfNeeded()调用|来历游戏会把这句提示隐藏着留在 DOM 里,只查元素自身会误判 - 未就绪时随机等 10~20 秒后自动刷新 — 自检不通过就打出原因,随机等 10~20 秒后刷新页面,从头再来;自检通过则放行启动。打橙色日志说明原因和等待秒数,等 10000 + random×10000 毫秒后
location.reload(),返回 false 让调用方中断启动;就绪返回 true。按代码实际:这条刷新路径不调saveLogsBeforeReload,也不走smartReload的计数和清缓存,日志只能靠卸载钩子保存(启动阶段离上次保存通常超过 30 秒,一般会存)。触发:启动主流程初始延迟结束后调用一次|参数等待 10000 + random*10000 ms|来历随机等待是为了错开多账户同时刷新 - 启动先做 kami 数据库增量同步 — 启动业务模块前,先把账户新入手的 kami 补进本地数据库,保证各模块启动时数据是全的。await
syncKamiDb(),外层再包一层 try/catch(函数内部也已捕获所有异常),失败只打「同步异常(不影响主流程)」。账户没变化时 0 新增、0 额外 API 调用,几乎没有开销。触发:页面自检通过后|来历保证后续模块启动时本地数据已补全 - 业务模块启动顺序 — 按固定顺序拉起 5 个常驻模块:防断连模拟 → 自己 kami 的死亡监控 → 自动 scavenge 领取 → 自动部署/停采主循环 → XP 药水全流程(合成 + 喂食)。打「🚀 启动自动化部署/采集」后依次调用
initIdleKeepAlive()、startMyKamiDeathMonitor()、autoScavenge()、startSequenceAfterDelay()、autoXPPotionFlow()。代码实际都没有 await,只是按顺序发起,几个模块并发运行。触发:数据库同步完成后|来历防断连最先拉起保证长时间不掉线;死亡监控先于部署,部署后一旦被杀能立刻发现 - 被喂食监控交给配套杀手监控脚本 — kami 被喂食(Feed)的监控不在本脚本启动,由配套的轻量杀手监控脚本负责,两个脚本需要一起运行。启动序列里没有 Feed 监控,只留注释说明分工。触发:启动主流程
等待游戏加载
- 游戏加载轮询等待 — 脚本入口处每 5 秒调用
checkGameLoaded()检查一次:钱包是否已连接、玩家 API 是否就绪,并打出最长等待时间。等待上限为 3 分钟加随机 0~3 分钟。触发:页面加载后由脚本入口调用一次|参数maxWaitTime=3 分钟 + 随机 0~3 分钟;checkIntervalMs=5000 - 错误界面随机延时刷新 — 检测到错误界面(如 Wallet Connector / Unknown error)时,红字提示,随机等 1~20 秒后以错误原因调用
smartReload刷新。随机延时 =Math.random()×19000 + 1000 毫秒。触发:启动轮询检测到错误界面|参数随机延时 1~20 秒|来历随机化避免多账户同一时刻一起刷新 - 加载成功复位错误刷新计数 — 游戏加载成功后,如果
kami_reload_count大于 0,就把它复位为 0,并打「[智能重载] 游戏加载成功,错误刷新计数已复位为 0」。读写都包在 try/catch 里,隐私模式等异常环境不影响启动。触发:启动检测到加载成功|存储kami_reload_count(读/写)|来历残留的旧计数会让下一次错误刷新直接清 ECSCache,触发全量重同步,启动明显变慢 - 游戏加载超时刷新 — 超过等待上限仍没进入游戏,打出检测详情(
connectedAddress、playerApiExists),然后smartReload('游戏加载超时')。触发:启动轮询超时|参数maxWaitTime=3 分钟 + 随机 0~3 分钟|来历不会无限等待 - 加载进度日志节流仅观测 — 等待期间,只在剩余秒数是 30 的倍数或只剩最后 10 秒时,才打「游戏加载中,剩余 N 秒…(钱包: 已连接/未连接, API: 可用/不可用)」。每 5 秒轮询一次,剩余秒数向上取整后命中才打,所以实际输出比较稀疏。触发:启动轮询每轮|来历避免刷屏
- 加载期顺手点加载按钮(带状态门)测试线独有 — 等待加载期间,发现 #
party_button按钮时顺手点击以加速进入游戏;但只在没有别处正在开列表、且列表确实处于折叠状态时才点,打「检测到加载按钮(列表未打开),点击…」。条件:按钮存在,且 !window.__ensurePartyInFlight,且__isPartyListClosed();simulateClick延时 500ms。触发:启动轮询每轮(尚未加载成功时)|参数simulateClick延时 500ms|来历复核 0914 时序推演:这颗开关按钮盲点会把紧急停采刚打开的列表点关|版本测试版1.2.53 - 钱包连接弹窗卡住识别 — 检查页面上的钱包连接弹窗(#wallet-connector)是否还显示着,显示着就判定为「Wallet Connector 认证卡住」(认证超时卡死)。用
getComputedStyle取实际显示状态,display 不为 none 即算卡住;按元素 id 定位,不依赖容易随前端改版变化的 class 名。触发:被checkGameLoaded()调用;游戏加载等待循环检测到该错误后随机等 1~20 秒再调smartReload(错误原因)|来历认证超时时弹窗会一直挂着,游戏进不去,必须识别出来刷新 - Unknown error 崩溃界面识别 — 页面正文里出现 "Unknown error" 字样就判定为游戏崩溃黑屏,返回「Unknown error 界面」;两种错误都没有时返回 null。该界面没有稳定 id,只能读
document.body.innerText按文字匹配;document.body还没就绪时用可选链兜底成空串,不会报错。触发:被checkGameLoaded()调用|来历崩溃界面没有稳定 id,只能靠文案判断 - 真实 Kami 条目判断(仅作诊断)仅观测 — 看 Party 列表里有没有渲染出真正的 kami 卡片,而不是提示文字。在 Party 列表条目(div#party>…>div)里找带 direction="row" 属性的子元素,找到任意一个就算有真实 kami。结果只放进 details 当诊断信息,不作为「进入游戏成功」的依据。触发:被
checkGameLoaded()调用|来历进游戏后没点开 Party 列表时 DOM 里检测不到 kami,但游戏其实已经进去了,拿它当判据会误判 - 进入游戏成功判据(钱包地址 + player API) — 综合判断是否真正进入了游戏:没有错误界面,并且钱包地址和 player API 都已就绪,才算成功。success =
connectedAddress(window.network.network.connectedAddress.value_有值)且playerApiExists(window.network.api.player存在)。返回 {success, error, details}。调用方(加载等待循环,范围外):出错走smartReload;成功把kami_reload_count复位;3 分钟仍未成功则smartReload('游戏加载超时')。触发:游戏加载等待流程循环调用|来历Kami 列表要手动展开才会渲染,所以成功标准只看钱包加 API - 错误界面优先短路 — 只要识别到错误界面,就直接判定失败,哪怕钱包地址和 API 已经就绪也不算成功。
checkGameLoaded先调detectGameError(),有错误就立即返回 success:false 和错误描述,不再做成功判断。触发:checkGameLoaded()内部 - 加载诊断信息包 details仅观测 — 每次判定都顺带收集 9 项页面和接口状态,方便排查加载失败的原因。9 项分别是:
partyExists、partyButtonExists、eyeButtonExists(#party 里的 eye- 图标按钮)、kamiListLength、hasRealKami、networkExists、apiExists、playerApiExists、connectedAddress。调用方在加载超时时会打印connectedAddress和playerApiExists。触发:checkGameLoaded()每次调用
打开列表、切眼睛、拉起主循环
- 按状态打开 Kami 列表测试线独有 — 有 Party 按钮时调用
__ensurePartyListOpen('启动'),列表折叠才点开;已经打开就不点,打「列表已打开(本处未点击)」。打开过程异常时打警告并继续启动;找不到 #party_buttonbutton 时打「未找到 Party 按钮,跳过点击」。触发:启动检测到加载成功后|参数Party 点击后等 2500ms、眼睛每下 1500ms(见__ensurePartyListOpen)|来历Party 是开关按钮,列表已开(例如紧急停采刚点开)时再点会被点关|版本测试版1.2.53 - 切到 eye-half 后启动主循环 — 打出当前眼睛状态,调用
waitForEyeHalf切到 eye-half;成功后等 3.5 秒,立即执行一次runAutomation,再用setInterval按checkInterval周期执行。主循环周期checkInterval= 10 分钟 + 随机 0~3 分钟(页面加载时定一次)。触发:启动检测到加载成功后|参数启动主循环前固定等待 3500ms - eye-half 失败终止启动 —
waitForEyeHalf返回失败时,红字打「waitForEyeHalf失败,已触发刷新,终止当前启动流程」并直接结束,不再重复刷新。触发:waitForEyeHalf返回 false|来历waitForEyeHalf内部已经触发刷新,这里再刷会双重刷新 - 读取眼睛显示模式
getEyeState— 读取 Party 面板眼睛按钮的图标,返回 open / half / closed 三态之一;找不到按钮或识别不了返回 null。选择器 #party button img[src*="eye-"],按 src 是否包含 eye-open / eye-half / eye-closed 判断。触发:启动序列和waitForEyeHalf调用,也可随时单独调用 - 自动切换到 eye-half 模式 — 轮询眼睛状态,已经是 half 就打「成功切换到 eye-half 状态」并返回 true;否则模拟点击眼睛按钮(三态循环切换),等 1 秒后下一轮再看。打「尝试点击眼睛按钮切换状态」,
simulateClick延时 300ms,点击后等 1000ms,再等轮询间隔 2000ms。触发:启动序列在列表打开后调用|参数checkIntervalMs=2000;stateDelay=1000;simulateClick延时 300ms|来历脚本的 DOM 状态检测(卡片状态图标等)都建立在 eye-half 模式的卡片结构上 - 眼睛状态变更日志仅观测 — 只有状态和上一次不同时才打「状态变更: 旧 → 新」(首次显示「初始」),便于回溯。用
lastState记录上一次状态,相同不打。触发:waitForEyeHalf每轮 - 找不到眼睛按钮时只等待不点击 — 找不到眼睛按钮就不点,打「找不到眼睛按钮,等待中…」继续轮询。每轮都会打这一行,点击日志同理。触发:
waitForEyeHalf每轮 - 切换超时自动刷新 — 超过 3 分钟基础时间加随机余量仍没进入 eye-half,打「超时仍未进入 eye-half 状态,触发刷新」,调用
smartReload刷新页面并返回 false。刷新原因为「眼睛按钮状态异常,未能切换到 eye-half」;随机余量由getRandomDelayMs(3)给出,为 0~3 分钟。触发:waitForEyeHalf超时|参数baseWaitTime=3 分钟 + 随机 0~3 分钟|来历防止挂机卡死在异常界面;随机化避免多账户同一时刻集体超时刷新 - 启动停采退避复读调度器 — 主循环启动后,同时启动一个每 15 秒扫描一次的退避复读调度器,只做零 gas 的 state 复读,并打出调度表(7s/20s/45s/90s/180s/300s)。用
window.__stopBackoffSchedulerStarted防止重复启动;整段 try/catch,不影响主循环。触发:启动主循环后一次|参数STOP_BACKOFF_SCAN_INTERVAL_MS=15000;STOP_BACKOFF_TABLE_MS=[7000,20000,45000,90000,180000,300000]|版本1.1.22 - 启动 gas 账本回执补录器仅观测 — 主循环启动后,同时启动 gas 账本 reconciler:每 3 分钟按 tx hash 只读补一次回执 gas,单轮最多 20 条,不新增任何 tx。用
window.__gasLedgerReconcilerStarted防止重复启动;整段 try/catch;启动日志提示报告命令showGasReport()。触发:启动主循环后一次|参数GAS_LEDGER_RECONCILE_MS=3 分钟;GAS_LEDGER_RECONCILE_BATCH=20|命令showGasReport()|版本1.2.7
定时刷新与智能重载
- 约 45~50 分钟定时整页刷新 — 每运行约 45 分钟加随机 0~5 分钟就整页刷新一次,让页面回到干净状态。
totalDelayMs= 45 分钟 +getRandomDelayMs(5),注入时打「页面将在 X 分钟后刷新」并排定performScheduledReload;刷新后脚本重新注入,等于自动续期。触发:脚本注入时排定|参数baseMinutes=45;getRandomDelayMs(5)=0~5分钟|来历长时间不刷新会积累内存泄漏、WebSocket断连、状态漂移;随机量用于多账户错峰 - 紧急停采让路(总上限 10 分钟)测试线独有 — 紧急锁被持有、紧急停采流程正在跑、或有刚登记(5 分钟内)的残留补停待补时,先不刷新,每 15 秒再查一次。判断条件
__reloadBlockedByEmergency()=hasEmergencyLock()|| (window.__emergencyStopRunning===true 且不是卡死的一轮) ||__residualRestopBlocksReload(),排在所有判断最前面,不占用 3 次延迟额度。测试版 1.2.60 起有总上限:从这次到点的刷新第一次被挡起累计 10 分钟(__RELOAD_EMERGENCY_MAX_MS,不因原因切换而清零),到点打红字「已给紧急停采让路 N 分钟,达到总上限…强制刷新」照常刷新;已卡死(20 分钟无进展)的一轮直接不让。加上「操作进行中」的 3×3 分钟,最坏约 19 分钟必刷新。让路日志带「已让路 Ns,上限 10 分钟」。触发:定时刷新到点时|参数__RELOAD_EMERGENCY_RECHECK_MS=15000;__RELOAD_EMERGENCY_MAX_MS=600000|来历1.2.54 加这道闸门时没设上限;0918 一台机器上一轮紧急停采卡在永不返回的请求上,运行标志挂了 4 小时,能救场的刷新被拦了 968 次,期间杀手进房死 24 只。用户 0918:「不能无限延长,有时候卡的东西刷新一下就好了」|版本测试版1.2.54;测试版1.2.60 - 刷新落地前二次复查紧急停采测试线独有 — 决定刷新后、真正 reload 之前再查一次,如果这期间紧急停采已经开始,就取消这次刷新。
__reloadIfStillClear:发现被紧急停采挡住就打橙色日志,15 秒后重新走完整的performScheduledReload(通过后会再保存一次日志,可能多出一份日志文件),否则location.reload()。触发:performScheduledReload保存日志后约 1 秒(后台节流时可能更久)|参数复查间隔 15s|来历0914 实录:后台节流把「等 1 秒再刷新」拖到约 1 分钟,期间紧急停采刚开始就被刷新切断,9 只已跌破线的 kami 拖到 5.6 分钟后才停|版本测试版1.2.54 - 操作进行中让路(最多 3 次,每次 3 分钟) — 有停采操作正在执行时,先推迟 3 分钟再刷新,不打断正在发送的 TX。
__kamiOperationInProgress为 true 且延迟次数 < 3 时,计数 +1,打橙色日志「第 n/3 次延迟」,3 分钟后重试。计数在本会话内不复位,刷新后自然归零。触发:performScheduledReload执行时(紧急停采判断之后)|参数__RELOAD_MAX_DELAYS=3;__RELOAD_DELAY_MS=3分钟|存储window.__kamiOperationInProgress(读)|来历半途刷新可能造成状态不一致或白烧 gas - 延迟额度用尽强制刷新(熔断) — 已经推迟满 3 次(共 9 分钟)但操作标记还没复位时,打红色警告并强制刷新。额度用尽且标记仍为 true 时打「已达到最大延迟次数(3次×3分钟=9分钟),强制刷新」,然后照常存日志并刷新。触发:延迟额度用尽后|参数
__RELOAD_MAX_DELAYS=3|来历防止业务异常导致标记没复位,刷新被无限推迟 - 定时刷新前存日志并等 1 秒 — 任何定时刷新之前都先把日志保存到下载文件夹,再等 1 秒让下载完成。调
saveLogsBeforeReload(),打「🔄 执行定时刷新」,setTimeout(__reloadIfStillClear, 1000)。卸载钩子会因为 30 秒内刚存过而不再重复保存。触发:通过紧急停采和操作让路判断后|参数刷新前等待 1000ms|来历刷新后内存日志会丢失 - 全局操作进行中标记
__kamiOperationInProgress— 一个全局布尔标记,表示当前有停采类操作正在执行。定时刷新看到它为 true 时会让路,避免刷新打断正在发送的 TX。脚本加载时初始化为 false。按代码实际(grep 结果),置位方有两处:emergencyStopHarvest开始时置 true、收尾置 false;批量停采/部署轮次在待停列表(apiList/domList)非空时置 true、结束置 false。注释写「停采/部署开始时置位」,代码实际只在有停采任务时置位,纯部署轮不置位。触发:脚本加载时初始化;业务流程写,定时刷新读|存储window.__kamiOperationInProgress(全局变量)|来历半途刷新可能造成状态不一致或白烧 gas - 带连续失败计数的智能重载 — 需要刷新页面时先记一次重载次数,再刷新;这个计数跨刷新保存,用来识别「连续失败」。读
localStorage的kami_reload_count(没有或非数字按 0)并 +1,打「🔁 第 N 次尝试重载(原因)」。调用方(范围外):钱包连接异常(死地址)、眼睛按钮没能切到 eye-half、游戏加载检测到错误界面、游戏加载超时、检测不到 Kami 列表。加载成功的流程会在计数 >0 时复位为 0。代码实际:smartReload本身不检查紧急锁和__kamiOperationInProgress,直接刷新。触发:异常检测流程调用smartReload(reason)|命令手动重置计数:localStorage.removeItem('kami_reload_count')|存储kami_reload_count|来历加载成功后要复位计数,否则残留计数会让下一次错误刷新直接清缓存、触发全量重同步,启动明显变慢 - 白屏保护:第 2 次重载清 ECS 缓存 — 连续第 2 次需要重载时,判定本地 ECS 缓存已损坏(典型表现是白屏),先删掉游戏的
IndexedDB缓存库再刷新,迫使客户端从链上全量重新同步。nextCount>=2 时用indexedDB.databases()枚举,只删名字以 ECSCache- 开头的库(不碰其他站点数据),每删一个打一条日志。删除请求是异步发出、没有 await,靠后面等 1.5 秒给它时间完成。触发:smartReload计数达到阈值时|参数阈值nextCount>=2|存储IndexedDBECSCache-*|来历本地 ECS 缓存损坏会导致白屏,普通刷新治不好 - 旧内核不支持枚举时退化为普通刷新 — 浏览器不支持
indexedDB.databases()时跳过清缓存,只普通刷新,不报错。!indexedDB.databases时打「当前环境不支持…跳过清除」,继续后面的计数归零和刷新。触发:清缓存分支内|来历旧内核不能枚举数据库,无法定位 ECSCache-* - 清缓存后计数归零 — 清完缓存立刻把重载计数写回 0,避免下一次正常刷新也误触发清缓存。
localStorage.setItem('kami_reload_count','0')触发:清缓存分支内|存储kami_reload_count|来历避免下次误触发全量重同步 - 刷新前先存日志并留出等待时间 — 刷新前先保存日志;普通重载等 1 秒、清缓存重载等 1.5 秒再刷新。函数开头先调
saveLogsBeforeReload()。普通分支写回计数后setTimeout(reload, 1000);清缓存分支setTimeout(reload, 1500),多出的 0.5 秒留给IndexedDB删除请求。触发:每次smartReload|参数1000ms / 1500ms|来历等待太短,日志下载或IndexedDB删除可能来不及完成
关页或刷新前抢救日志
- 页面卸载时自动保存日志 — 用户手动 F5、关标签页或浏览器崩溃前,也尝试把日志存下来。在 window 上挂 pagehide 和 beforeunload 两个监听,触发时调
saveLogsBeforeReload(),全程 try/catch 静默。挂好后打一条启动日志。已知限制:浏览器可能拦截卸载期间的下载(后台标签页尤其常见),这是尽力而为的兜底;Chrome 第一次会问「允许下载多个文件」,需要允许一次。触发:自动:页面 pagehide / beforeunload 事件|来历原来只有脚本自己的两条刷新路径会存日志,手动刷新、关标签或崩溃时最多丢 45 分钟日志;做法参考了其他脚本的 pagehide/beforeunload 打法|版本1.2.19 - 30 秒去重(与自动保存互斥) — 30 秒内已经存过日志,卸载时就不再存一次,避免同一次刷新下载两份文件。
Date.now()-__lastLogSaveAt< 30000 就跳过。因为__lastLogSaveAt是内存变量、新会话归零,连续手动刷新每次仍会各存一份,不会被上个会话的时间戳挡住。触发:卸载钩子触发时|参数MIN_GAP_MS=30000|来历自动刷新路径是「先保存,1~1.5 秒后 reload」,不去重会重复下载|版本1.2.19 - 卸载监听幂等挂载 — 脚本被重复注入时,先摘掉旧监听再挂新的,保证一次刷新只触发一次下载。处理函数存在
window.__kamiUnloadSave上;已存在时先removeEventListener摘掉 pagehide 和 beforeunload 的旧监听,每步单独 try/catch。触发:脚本注入时|存储window.__kamiUnloadSave(全局变量)|来历防止一次刷新触发多次下载|版本1.2.19
1.2 主循环总览
runAutomation 每 10~13 分钟跑一轮:读卡片 → 分池 → 停采 → 部署 → 喂食 → 复活。这一节是骨架和它依赖的通用工具,各环节细节见后面各节。
主循环骨架
- 主循环扫描→决策→执行闭环 — 脚本的心脏:每个检测周期读一遍 Party 面板里每只 kami 的状态和血量,依次完成停采、紧急停采升级、饿死救援、批量部署、低血喂食、批量复活。顺序:列表就绪检测→DOM 预检→遍历分池→停采分流→紧急硬触发→锁协调→锁内(单停凑批→饿死救援→API/DOM 停采→批量部署)→锁外(库存盘点→低血喂食→批量复活)。设计哲学:减少 TX、省 gas、采集更久,凑批/跳过都在 gas 甜区内争取最长采集。触发:检测周期定时器反复调用;页面加载后首轮同一入口|命令
syncKamiDb()重建/补全本地库;resumeDeploy()立即解除转移部署暂停窗口(两者定义在别处) - 主循环健康心跳仅观测 — 每轮开头写一个时间戳,让辅助脚本的健康看板分得清主循环是「没活干」还是「卡死了」。
window.__kamiHealthBeats['主循环']=Date.now()。触发:每轮开头|来历主循环平时可能整轮静默(无候选不打日志)|版本v1.1.5 - 每轮刷新前端冻结传感器仅观测 — 每轮顺手评估一次游戏前端数据流是否冻住。存在
window.__evalFrontendFrozen时调用(600000),纯日志并维护window.__frontendFrozen,异常吞掉。触发:每轮开头|参数参数 600000ms|版本v1.1.25 - 跨脚本全局变量约定(主循环读写) — 主循环与辅助脚本、杀手监控脚本靠一组 window 全局变量互相通气。读:
window.__kamiMode(greedy=贪婪)、window.__killerDetected(杀手告警)、window.MY_KILLER_KAMIS(自家杀手 Set)、window.__deployPausedUntil(转移暂停截止时间)、window.__txNormalLock(普通锁持有者)、window.kami_core_db。写:window.__kamiOperationInProgress、window.__deployPauseReminder、window.__kamiHealthBeats。触发:每轮 - 主监控循环间隔 10~13 分钟 — 主循环轮询间隔 = 基础 10 分钟 + 0~3 分钟随机,页面会话内固定(刷新才重新随机)。
checkInterval在脚本注入时算一次,启动时setInterval(runAutomation, checkInterval)使用。触发:脚本注入时计算一次|参数基础 10*60*1000;getRandomDelayMs(3)|来历监控检查本身不发 TX,拉长间隔减少链上/DOM 读取开销;HP 危险另有紧急停采线兜底,不依赖轮询密度 - 启动打印本轮检测间隔仅观测 — 打印「本轮检测间隔为:10 分钟 + 随机 X 分钟,共 Y 分钟」。计算完立即打印,方便确认。触发:脚本注入时
kamiHistory状态历史容器 — 按 kami 记录上一轮观测到的状态的 Map,供主循环跨轮次对比状态变化。newMap(),由主循环读写。触发:脚本加载时创建getRandomDelayMs随机抖动 — 生成 0~maxMinutes分钟之间的随机毫秒数,给周期性动作加抖动。Math.random()×分钟上限→换算毫秒向下取整;纯函数,maxMinutes默认 1。触发:被检测间隔计算等位置调用|参数maxMinutes(调用方传入)|来历避免每轮间隔完全固定,错峰执行、降低行为规律性
列表就绪与 DOM 预检
- 卡片列表就绪检测 + 定时重查 — 确认 Party 面板里的 kami 卡片已经渲染出来,没渲染就隔一会儿再查。选择器 div#party>div>div:nth-of-
type(3)>div:nth-of-type(2)>div:nth-of-type(2)>div;为空则计数并打「第N次检测:未发现任何 Kami」,setTimeout定时重查。代码实际:checkKamiList()没有 await,首次为空时主流程不等重查照常往下走,由 DOM 预检兜底。触发:每轮runAutomation开始|参数retryIntervalMs=10秒+随机0~10秒抖动|来历重试间隔加随机抖动,避免与其他定时任务同拍 - 连续查不到刷新页面自救 — 连续 10 次都查不到卡片,判定页面卡死,自动刷新。
retryCount达maxRetries时打红色「连续 10 次未检测到 Kami,刷新页面防止卡顿」,调smartReload('检测不到 Kami 列表')(带防抖,防无限刷新)。触发:重查计数达上限|参数maxRetries=10 - 检测成功打印账户与地块信息仅观测 — 查到卡片后报出数量,并打印账户与地块信息。打「检测到 N 个 Kami,正在监测血量百分比...」后 await
printAccountAndRooms()。触发:卡片列表非空时 - 卡片不完整率检测测试线独有 — 正式扫描前统计有多少卡片缺关键字段,超过一半就先修复再扫,避免大批 kami 被早期过滤、整轮白跑。
__countIncompleteDom(kamiList)统计缺状态图标/HP(N%)/图号/主按钮任一项的卡片,占比>阈值即进入修复重试。该函数已提升到顶层与紧急停采共用。触发:checkKamiList之后、遍历之前|参数__DOM_INCOMPLETE_THRESHOLD=0.5|版本测试版1.2.51 - 规模严重不足检测(历史峰值 10%) — 卡片数量少于历史峰值的一成时判为渲染异常,宁可先修也不在 DOM 不全时误操作;小账户不适用。读
localStorage峰值,峰值≥20 且当前数<峰值×0.1 → 打红色「规模严重不足 当前/峰值」进入修复。触发:每次 DOM 预检循环|参数__SCALE_ANOMALY_RATIO=0.1;峰值门槛≥20|命令localStorage.removeItem('kami_last_known_count')重置基线|存储kami_last_known_count(读)|来历列表只渲染出几只时会把渲染缺失误当真实状态做错决策 - 第 1 次修复:纯等待 8 秒 — 第一次发现 DOM 不对劲时只等 8 秒,多数情况只是渲染慢半拍。
__domRetry===0 时打「等待 8 秒让 DOM 充分加载」并delay(8000)。触发:预检不通过的第 1 次|参数8000ms - 第 2 次修复:按状态打开列表 + 眼睛切 half测试线独有 — 第二次仍不对时,确保 Party 列表是打开的、眼睛视图在 half 档,再等 5 秒。
__ensurePartyListOpen('DOM预检')(折叠才点开);getEyeState()!=='half' 时打日志并waitForEyeHalf();delay(5000)。触发:预检不通过的第 2 次|参数5000ms|来历原来盲点 Party 按钮,列表已经开着时会被点关|版本测试版1.2.53 - 修复后重查、修不好照常向下 — 每次修复后重新抓卡片列表;两项检查都通过就提前结束,修不好也继续往下走。重新
querySelectorAll并打「重新查询kamiList,共 N 条」;最多__maxDomRetry次,之后由遍历阶段的早期过滤兜底(不完整卡片只会被跳过)。触发:每次修复后|参数__maxDomRetry=2 - 历史规模峰值记录(只升不降) — 本轮卡片数≥20 且超过旧峰值时更新历史峰值,个别异常轮不会把基线拉低。
localStorage.setItem('kami_last_known_count',N),打「更新历史峰值: 旧 → 新」。触发:DOM 预检结束后|参数峰值记录门槛≥20|命令localStorage.removeItem('kami_last_known_count')重置|存储kami_last_known_count(写)|来历避免个别异常轮拉低基线
卡片遍历与分池
- 扫描统计器(早期过滤按字段细分)仅观测 — 记录本轮各种跳过原因的数量,停采/部署池意外为空时能直接看出是哪个字段没渲染。
__scanStats含earlyFilter及emptyState/nanHP/emptyImg/emptyBtn四个细分,以及restingTotal/restingLTSkip/restingHPLow/restingAlreadyActed;每轮在分流后打「扫描统计」一行。触发:每轮遍历中累加 - 卡片状态解析(含 STARVING 推断) — 从卡片图标和血量文字判断每只 kami 是死亡、饿死、采集中还是休息。img src 含
kami_dead→DEAD;含kami_harvesting且 HP=0%→STARVING,否则 HARVESTING;含kami_resting→RESTING;HP 取图标下一个兄弟元素的 (N%)。触发:每轮遍历每张卡片|来历游戏 DOM 没有独立的饿死图标,只能用「采集图标 + HP 归零」推断 - 个性化停采线
safePercent— 按每只 kami 的精确清算线加 3% 缓冲算出它自己的停采线,最高不超过 80%。record.LT有效时safePercent=min(floor(LT+LT_STOP_MARGIN),MAX_THRESHOLD),否则 undefined。触发:每只卡片解析时|参数LT_STOP_MARGIN=3;MAX_THRESHOLD=80|来历封顶 80% 是有意设计:目标是单周期采集时长最大化,清算线过高的 kami 应先升级而不是放宽上限 - 体质判定 — 判断 kami 是不是普通体质,用于没有清算线数据时选默认停采线。属性图标行 *[direction='row'] img 第 1 张 src 含 normal 即普通体质;图标缺失时回退本地库 body==='normal'。触发:每只卡片解析时
- DEAD 只收集,循环后统一复活 — 扫描中遇到死亡 kami 只记下来,整轮扫完再一次性批量复活;缺
kamiId的提示先补数据。有kamiId(或 id)则 push 进__deadToRevive后 continue;没有则打「已死亡但缺kamiId,无法复活(请跑syncKamiDb()补数据)」。不做地块检查。触发:遍历中遇到 DEAD|命令syncKamiDb()|来历逐只拿锁等确认时慢链下后面的死 kami 全被自己上一笔挡住,表现为「日志提示复活但迟迟没动作」;死亡 kami 任何地块都能复活且harvest.node基本读不到,查地块既无必要也不可靠 - 早期过滤不完整卡片 — 状态、血量、图号、主按钮任一缺失说明卡片没渲染完,直接跳过,不做任何操作。任一缺失时
earlyFilter+1,并按缺失字段分别计数后 continue;主按钮=harvest-/stop- 图标所在的 button。触发:每只非 DEAD 卡片 - 扫描阶段不检查操作面板 — 扫描时刻意不要求操作面板存在。只有注释说明,没有面板检查代码。触发:遍历中|来历面板只有点击卡片后才渲染,扫描阶段检查必然失败导致误跳过
- 卡片解析调试日志仅观测 — 调试开关打开时逐只输出解析结果。dlog('parse','[PARSE] #index/img state hp
isNormal')。触发:每只通过早期过滤的卡片 - 本地库状态与 DOM 状态并联判定 — 本地库记录的链上状态和卡片图标,任何一个显示采集中就按采集中处理,宁可多查一次也不漏停。
stateAPI=record.state大写;停采分支条件stateAPI==='HARVESTING'||DOM 为 HARVESTING/STARVING;部署分支条件stateAPI==='RESTING'||DOM 为 RESTING。代码实际:两个 if 并列不互斥,本地库与 DOM 不一致时同一只可能同时进停采池和部署池。触发:每只卡片 - 单只异常不影响整轮 — 某只 kami 处理出错时只记日志,继续处理其余 kami。每只包 try/catch,打「Kami 处理异常:错误」。触发:遍历中
通用工具
- 从 DOM 节点提取
imgNumber— 从 Kami 卡片(或头像 img 本身)提取头像 gif 编号,作为「DOM 卡片 ↔ 链上 Kami」唯一的对应桥梁。节点本身是 IMG 就直接用,否则找内部的 img[src*="/kami/"];用正则 /kami/(\d+).gif 取编号(string),取不到返回 null,不抛错。触发:所有需要把 DOM 卡片和链上数据对应起来的逻辑调用 - 同类动作去重窗口
alreadyActed— 同一只 Kami 最近一次动作与本次同类型,且距今不到一个主循环周期时,视为本周期已处理,调用方跳过,防止重复操作。只记最后一次动作,异类动作会覆盖记录;窗口到期自动失效,不用手动清理。触发:对 Kami 执行动作前查询|参数去重窗口 =checkInterval(10 分钟 + 随机 0~3 分钟) - 动作登记
recordAction— 动作执行成功后,以imgNumber为 key 记下 {动作类型, 时间戳},供去重查询使用。存在全局内存 MapkamiHistory里,页面刷新即清空。触发:动作成功后调用(如普通停采确认已停后登记stopHarvest) - 批量操作后 UI 沉降时长 — 批量 TX 之后等一段固定时间,再读游戏前端的界面和状态。调小流程更快但可能读到未刷新的旧状态;调大更稳但整轮耗时变长。普通停采确认后的复核也用这个值。触发:批量部署/停采后被引用|参数
UI_SETTLE_MS=25000 - TX 哈希合法性校验
_isHash64— 判断一个值是否是合法的交易哈希:字符串,且为 0x 加 64 位十六进制。正则 ^0x[0-9a-fA-F]{64}$。触发:被其他板块调用 - 小清单整批发送 — 清单为空返回空数组;清单长度不超过上限(10)时整体作为一批,即使不足下限(6)也不再拆。
arr.length≤maxSize时直接返回 [arr.slice()]。触发:部署/普通停采/紧急停采组批时|参数minSize=6 /maxSize=10|来历单独一小批也比再拆成更小的零头好 - 最少批数保证每批不低于下限(防小残批) — 清单超过上限时,先算出能让每批基础大小都不低于 6 的批数,避免拆出 3~5 只的小残批单独发 TX。批数从
ceil(L/10)开始,只要 floor(L/批数) < 6 就减一。触发:组批时|参数minSize=6 /maxSize=10|来历避免 L=13/14/15/23/25 这类长度出现小残批,小批 gas 单价高,违背省 gas 原则 - 11 只超上限整批例外 — 批数被减到 1 时(数学上只有 L=11 会发生),11 只整体作为一批发出,略超上限。因此算法最大批就是 11 只,不需要额外的硬上限常量。触发:组批且 L=11 时|来历N=11 停采约 16.15M gas,低于 N=12 的约 17.40M,仍在安全区
- 零头随机指派错峰 — 均匀分批后,把余数随机分给若干批次各多 1 只,保留批量大小的随机性。基础大小 floor(L/批数),用 Fisher-Yates 洗牌批次序号,取前「余数」个批次各 +1。注释写「每批 6-10 随机错峰」,代码实际每批大小由 L 决定,只可能是基础大小或基础大小+1,随机的只是哪几批多 1 只。触发:组批时|来历固定的大批量在链上 gas 飙升时段失败率偏高,错峰可降低整批失败概率
- 链上状态归一
getKamiState— 把链上返回的状态字符串统一成小写状态码:harvesting(采集中)、resting(休息中)、dead(已死亡)、external(已作为 ERC-721 转出到外部钱包,不归本账户管)。其余一律 unknown。取res.state转大写后 switch 映射;res 为空或没有 state 时经 ?. 和 || '' 兜底落到 unknown,不抛异常。触发:所有需要判断链上状态的地方调用|来历避免各处散落大小写比较 isHarvesting/isResting/isDead判断函数 — 三个便捷判断,分别判断是否采集中、休息中、已死亡,供停采、喂食、复活等决策统一调用。都基于getKamiState的结果做等值比较。触发:被决策逻辑调用
1.3 停采
日常停采:主循环按停采线挑出候选,凑批后优先走链上 API 批量停采,失败的走 DOM 模拟点击兜底;另有两个一键停采命令。血量已经触线的紧急情况见 1.4。
停采决策与候选分流
- 停采线三选一 — 按模式和杀手情况为每只采集中的 kami 选停采线:贪婪无杀手用极限线,否则用个性化线,没数据用默认线。
__kamiMode==='greedy' 且无杀手→GREEDY_THRESHOLD(来源GREEDY_EXTREME);否则有 LT→safePercent,无 LT→普通体质 65%、其他体质 76%;来源在贪婪+有杀手时为GREEDY_SAFE,其余为 NORMAL。触发:本地库或 DOM 判定为采集中/饿死时|参数GREEDY_THRESHOLD=5;DEFAULT_THRESHOLD_NORMAL=65;DEFAULT_THRESHOLD_OTHER=76;MAX_THRESHOLD=80|命令setKamiMode等模式命令定义在别处|来历有杀手时即使贪婪模式也退回安全线;非普通体质受亲和克制更易被清算,默认线更高 - 预警带入停采池 — 血量离停采线不到 1 个百分点(或已饿死)且不在动作冷却期,就放进停采池。delta=HP-阈值(保留两位);delta≤1 或 STARVING,且 !
alreadyActed(img,'stopHarvest')→ push {imgNumber,dbIndex,harvestId,delta,isStarving};alreadyActed的冷却窗口为主循环周期checkInterval;dlog 输出 [STOP?] 判定明细。触发:每只采集中 kami|参数入池预警带 delta≤1|来历isStarving标记随候选带入:饿死的必须先喂食才能停采 - 普通模式停采线独立计数 — 不管当前是不是贪婪模式,都再按普通停采线数一遍有多少只该停,用于尽早识别批量受攻击。普通线=
safePercent或 65%/76% 默认线;delta≤1 或 STARVING 且不在冷却期则__normalLineStopCount+1。注释写「>25 即触发紧急停采」,代码实际动手线是STOP_TRIGGER_ACT=42(公开版口径;测试线 1.2.59 为 106、贪婪线 30%)。触发:每只采集中 kami|来历贪婪无杀手时停采线仅 5%(公开版),按当前模式几乎凑不出大批候选,「候选过多→紧急停采」保护会形同虚设 - 停采池按危险程度排序 — 停采候选按离停采线的距离从小到大排,最危险的最先停。过滤掉 delta 非数字的,按 delta 升序 sort。触发:遍历结束后
- 按
harvestId分 API/DOM 两路 — 有合法harvestId的走链上 API 批量停采(快、省 gas),没有的走模拟点击兜底。_isHash64(harvestId)为真进apiList,否则进domList。触发:排序后 - 每轮分流与池子统计日志仅观测 — 每轮打出 API/DOM 两路停采名单、部署池大小和扫描统计。「stop 分流 API=N → 列表」「stop 分流 DOM=N → 列表」「部署池
kamiList/strong/warm/stop」「扫描统计 …」,另有 dlog 汇总。触发:每轮分流后
凑批省 gas(单只停采决策)
- 单只停采凑批四分支 — 本轮只有 1 只要停(且 DOM 路为空)时,判断是跳过等凑批省 gas,还是马上单停。饿死→打「必须立刻喂食+停采,不凑批」不跳过;贪婪无杀手→打「停采线仅5%…立刻停采」不跳过;普通线且 delta>-1→清空
apiList跳过本轮等凑够≥2 只;delta≤-1→打「HP已低于停采线超过1%…立刻单独停采」。板块注释写「低于停采线超过 3%」,代码实际危险线是 -1。饿死分支日志说「立刻喂食+停采」,但 1.2.26 起 API 停采会剔除 STARVING,本轮实际只喂不停。触发:apiList恰好 1 只且domList为空|参数SINGLE_STOP_DANGER_DELTA=-1;GREEDY_THRESHOLD=5|来历为 1 只单独发 TX 最不划算;危险线配合 +3 薄垫收紧,再等一轮就可能踩到清算线
批量修剪硬触发
- 候选过多硬触发紧急停采(修剪到 100)测试线独有 — 一轮该停的 kami 多到异常(按当前线或按普通线任一达到 106 只),判定可能遭批量攻击,后台启动紧急停采,只停最危险的超额部分,留 100 只继续采。
__totalStopCount≥STOP_TRIGGER_ACT或__normalLineStopCount≥STOP_TRIGGER_ACT,且当前无紧急锁→打红色「紧急触发 原因,升级为紧急停采流程接管(修剪到 100)」,emergencyStopHarvest({trimTo:100})不 await;主流程继续,在锁协调处让路。只有无杀手警戒的这条路径带trimTo,杀手监控等调用方不修剪。公开版仍为 HARD=36、ACT=42。触发:每轮分流后自动判断|参数STOP_TRIGGER_HARD=100(公开版 36);STOP_TRIGGER_ACT=106(HARD+6);测试版 1.2.59 起两个常量在模块级(残留补停也读)|来历0718 用户定案 25→36;0914 用户定案 36→80,分档 80→120→160 每档跑两夜——实测 36 时贪婪线从未生效、修剪停的是线下 1~2 点的 kami,线下池天花板约 150~160;0915~0917 三夜保留 80 零死亡后,0917 用户定案 80→100|版本1.2.17;测试版1.2.57;测试版1.2.59 - 硬触发去重与异常隔离 — 已经有紧急锁时不重复触发;启动紧急停采时的同步或异步异常只记日志,不中断主流程。条件带 !
hasEmergencyLock();外层 try/catch 记「紧急停采启动失败」,.catch 记「紧急停采异常」。触发:硬触发时 - 杀手警戒中硬触发全撤不修剪测试线独有 —
window.__killerDetected为真(死亡监控发现我方被杀后置位)时,安全线候选只要 ≥6 只就硬触发,且不带trimTo,把越线的全部撤下。打「紧急触发 杀手警戒中,安全线候选 N 只,升级为紧急停采流程接管(全撤,不修剪)」。触发:每轮分流后自动判断|参数候选≥6(=TARGET_BATCH_MIN)|来历0914 审查——切安全线那一刻候选从 0 跳到上百只,不够动手线会掉进普通停采路径(全局 maxAttempts=5 只发得出 5 批),够动手线又会被修剪留一堆在场|版本测试版1.2.57 - 批量预警(超额不足 6 只)仅观测 — 候选超过 HARD 但不到 ACT 时只打一条预警,不硬触发,交给普通停采流程。当前线计数或普通线计数落在 (HARD,ACT) 时打「批量预警 停采线候选N只(>HARD)但超额<6,暂不硬触发…」,每轮最多一条。贪婪模式下普通流程按 30% 线,实际不会停这些 kami,等候选到 ACT 再修剪(测试版 1.2.59 起日志写明这一点)。触发:未硬触发且无紧急锁时|参数
STOP_TRIGGER_HARD=100(公开版 36);STOP_TRIGGER_ACT=106(公开版 42)
API 批量停采
- STARVING 只喂不停 + 喂活转健康不停 — API 批量停采名单剔除仍饿死的 kami,以及喂食后血量已回到停采线以上的 kami。
__apiNoStarv=apiList过滤掉isStarving为真或 delta>1 的;有被剔除时打橙色「N 只 STARVING 已交喂食救援,本轮不停采(0血停不了采)」。触发:饿死救援之后|来历0 血 kami 停采链上必 revert;喂活后血量健康的应继续采集|版本1.2.26 / 1.2.27 - 停采随机切批 6~10 只 — 把待停名单切成每批 6~10 只的随机批次,批量落在 gas 甜区,随机大小也避免链上行为有固定指纹。
chunkRandom(list,6,10):总数≤10 一批装下(不足 6 也接受);11 只也一批;更多时算批数、余数随机分到各批。触发:每轮 API 停采|参数批量下限 6;上限 10 - 每批停采前紧急锁中断 — 每批 API 停采发出前检查紧急锁,有就中断剩余批次。
hasEmergencyLock()为真打「检测到紧急锁,中断普通停采」并 break。触发:每批前 - 退避重试批量停采 + 失败件交 DOM 兜底 — 每批用带退避重试的批量停采发出,最终仍失败的条目收集起来交给 DOM 兜底。打「批量停止/API - 第 i/N 笔 计划 x 个 → 列表」,await
stopWithBackoff(batch,5),返回值累加进needDomAll。触发:每批|参数退避重试上限 5 次
普通停采工具(AllowFailure + 退避重试)
- DOM 卡片采集中判断(不确定按未采集) — 在 Party 面板(eye-half 模式)的卡片列表里,按
imgNumber找到卡片,读状态小图标,src 含kami_harvesting就算采集中。卡片找不到、图标读不到或出异常,一律返回 false(fail-open)。只适合做筛选(最坏少重试一只、下轮再捞),不能拿来打「已停」标记。触发:普通停采粗筛、复核、失败重试、残留收集时调用 - DOM 确证已停
_cardStopConfirmed(不确定不算已停)测试线独有 — 只有卡片找得到、状态图标读得到、图标不是 harvesting 三条同时成立,才确认这只 Kami 已停采。三种不确定(找不到卡、读不到图标、异常)全部返回 false(fail-closed),宁可少打一个标记下轮重新处理,也不误锁。触发:普通停采复核后决定是否recordAction时|来历三态修复激活了原本是死代码的确认分支,旧的取反判据会把 DOM 抖动这种不确定当成已停去打标锁住,导致漏停被清算(风险不对称律:停采侧宁发勿漏)|版本测试版1.2.41 AllowFailure容错批量停采 — 绕过游戏封装,直接调用停采 System 合约的executeBatchedAllowFailure,一笔 TX 批量停采;某只失败时合约自动跳过,其余照常停,不整批回滚。返回三态:false=确定失败,null=待 state 复读裁决,true 只在 API 回退通道出现。触发:stopWithBackoff每轮发送时调用|来历普通批量调用只要一只失败就整批回滚连坐- 合约句柄/target/签名器守卫,缺失回退 API测试线独有 — 停采合约句柄的 interface、合约地址 target、签名器任意一个不可用,就打警告并改走游戏自带
api.stop回退通道。守卫同时检查 .interface 和 .target 和 signer。触发:_allowFailureStop入口|来历0911 审计:原守卫只看 interface 不看 target,句柄有 interface 却没 target 时会放行,发出一笔 to=undefined 的交易(等于部署合约)|版本测试版1.2.35 - MUD 通道发送耗时观测(D4)仅观测 — 走 MUD 队列时,记录 enqueue 到 resolve 的耗时,打「本批经 MUD 队列发送(nonce统一) tx=前10位… mud enqueue+resolve=Xms」。只在拿到 tx 时打印。触发:mud 通道每次发送|来历用实测数据裁决队列 resolve 的语义(拿 hash 还是等上链)|版本1.1.19
- 发送前清单日志与 ID 调试输出仅观测 — 发送前打「[
AllowFailure停采] N 个 → 清单」,并通过dlog('api')输出完整harvestId数组。触发:_allowFailureStop每次发送前 - 空交易判失败 — 发送后拿到的 tx 为空时,打「[
AllowFailure停采/返回空Tx]」并返回 false。触发:发送后 - 停采 gas 真值记账仅观测 — tx 非空后立即以动作 stop 登记到 gas 账本,mud 和 raw 两个分支都覆盖,之后由 reconciler 按 hash 补回执 gas。调用
_gasLedgerRecord('stop', harvestIds, tx)。触发:每笔普通停采 tx 发出后|命令showGasReport()|版本1.2.7 - 回执形状归一 — 不管队列返回的是带 wait 的 tx 还是直接是 receipt,都通过
_awaitStopReceipt统一成 {receipt, hash, shape} 再判断。raw 分支的 tx 带 wait,走wait(),行为与旧版逐字节等价。触发:tx 入队后确认阶段|来历0710 停采事故:MUD 队列等上链才 resolve,返回的对象没有 .wait,旧码写死tx.wait()导致假失败|版本1.1.21 - 回执形状未知或缺
gasUsed转 state 复读(I2 红线) — 回执形状未知、没有回执或gasUsed为空时,无法按 gas 判级,本批不计成功也不计失败,返回 null,交由之后复读 state≠HARVESTING 裁决。待确认批数__pendingVerifyBatchCount+1,打「回执形状未知/缺gas,转 state 复读裁决」。触发:确认阶段|来历旧码缺 gas 时按 0 算,会被误判成 revert 级而记失败|版本1.1.21 - 交易整体 revert 判定 — 回执 status 缺失不当失败;只有明确读到非 1 才判「交易上链但执行失败」,返回 false。raw 回执的 status 恒为 0/1,行为与旧版等价。触发:确认阶段
- gas 像全执行只观测、不作停成凭据 — 每只均摊 gas ≥ 120 万且估算执行只数 ≥ 全员时,只打黄色 gas 观察日志,返回 null,不据此判定已停,交由下轮复读 state 确认。估算执行只数 = round((
gasUsed− 100000) / 1500000);待确认批数 +1。判据和紧急停采_emergencyConfirmBatch同款双条件,两处必须一起改。单只真停约 1.54M/只会命中;单只 revert 约 261k/只落不到这里。触发:确认阶段 gas 判级|参数GAS_FULL_EXEC_PER_KAMI=1200000(v1.1.14 从 800000 收紧);GAS_REVERT_BASE=100000;GAS_PER_KAMI_ESTIMATE=1500000|来历0709 审计:receipt.status===1 只说明交易没有整体 revert,已停或不可停的成员同样 status=1,旧版看到 status=1 就报成功是误报根因|版本1.1.12 / 1.1.14 / 1.1.17 - revert 级 gas 判未生效 — 每只均摊 gas ≤ 30 万时,打「上链但未生效(revert级gas),多半该 kami 已停,待索引器确认」,返回 false。触发:确认阶段 gas 判级|参数
GAS_FULL_REVERT_PER_KAMI=300000|版本1.1.12 - 混合 gas 按未完全确认处理 — 均摊 gas 介于 revert 级和全执行之间时,打出均摊值和估算执行只数,按未完全确认返回 false,交调用方复核。触发:确认阶段 gas 判级|来历多个 id 合发时,无法单从 gas 确认每个成员是否都生效|版本1.1.12
- 发送失败与确认异常分开处理 — tx 已入队(进入确认段)之后才抛错的,返回 null 交 state 复读裁决,不记失败;入队之前抛错的才算真正发送失败,打「发送失败 全部 N 个未停采」并返回 false。用
reachedConfirm标志区分,错误信息截取前 80 字。代码里确认异常日志的前缀写的是「[紧急停采/确认异常]」,实际位于普通停采函数中(文案沿用)。触发:_allowFailureStop捕获异常时|来历codex Q5#5:确认阶段偶发抛错曾被误记为失败并导致拉黑|版本1.1.21 - API 回退停采
_apiStopOnceFallback— 合约句柄不可用时,改走游戏自带的api.player.pet.harvest.stop批量停采;成功打「批量停止/完成(API fallback)」返回 true,这是唯一会返回 true 的路径。api.stop不存在时直接 throw;由于_allowFailureStop调它的位置在 try 之外,这个异常会一路冒出stopWithBackoff交给上层。空 tx 返回 false;tx 有 wait 就等上链,没有就固定等 15 秒兜底;调用异常打「API fallback失败」返回 false。发送前同样打清单日志并 dlog 输出 ids。触发:_allowFailureStop守卫不通过时|参数tx 无 wait 时固定等待 15000ms - 普通停采入场静默期 —
stopWithBackoff一进来先等 5 秒,再检查清单并动手。清单为空也会先等满 5 秒再返回 []。触发:主循环判定有 Kami 达到普通停采条件后调用stopWithBackoff|参数STATE_CHECK_DELAY_MS=5000|来历让链上和 UI 状态沉淀,拉开 TX 间隔,避免与其他交易抢 nonce - 停采包队列与连续未收敛计数 — 整份停采清单作为一个「包」入队,每个包带
failStreak(连续未收敛次数),作为二分拆包的依据。队列元素结构为 {items,failStreak},逐包 shift 处理。触发:stopWithBackoff - 上链尝试轮数熔断 — 最多上链尝试 5 轮,用完后退出循环,进入残留逐只排查。只有真正发出 tx 才计一轮;包级跳过(无采集中、链上已无采集、无有效
harvestId)不消耗次数。触发:stopWithBackoff循环|参数maxAttempts=5(调大更执着,但失败场景多耗 gas) - 每轮开头检查紧急锁并让路 — 每轮处理前先查紧急锁,命中就打「[TX锁] 检测到紧急锁,中断
stopWithBackoff」立即退出,把 TX 通道让给紧急停采。代码实际:退出后,队列里剩下的包仍会进入末尾的逐只预检排查(会做预检、可能拉黑)。触发:stopWithBackoff每轮开头|来历紧急停采优先级最高(TX 双锁机制的一环) - DOM 粗筛仍显示采集中 — 本包只保留界面上仍显示「采集中」的 Kami;一只都没有就打「本包已无 HARVESTING」跳过。用
_isCardHarvestingByImg过滤。触发:每包处理时 - 发送前链上预检:已死/已停跳过 — 逐只查链上实时状态:已死亡打「链上已死亡,跳过」,休息中或状态不是 HARVESTING 打「链上已停采,跳过」。
getByIndex带 harvest 选项;状态比较用原始大写字符串 'HARVESTING'。触发:每包发送前 - 地块不匹配跳过并汇总警告 — 只停当前所在地块上的采集;Kami 在其他地块采集的跳过,本包末尾用橙色汇总「[地块不匹配] 当前地块: X,N 个kami在其他地块,已跳过: #编号(地块N)…」。当前地块或 Kami 采集节点号任一取不到时不拦截。触发:每包发送前预检|来历防止误停其他地块的采集
- 停采冷却预筛 — 用本次已查到的
harvest.time.last算剩余冷却,剩余大于 0 的本次跳过停采。冷却按 180 秒算;time.last缺失或非正数按无冷却处理。注释说冷却解除后由主循环下一两轮重新捞;代码实际上,如果同包有其他成员发出了 tx,被冷却或跨地块跳过的成员在复核时仍在采,会随包回队,本函数内还会再试。触发:每包发送前预检|参数KAMI_ACTION_COOLDOWN_SEC=180|来历冷却中此刻发停采 tx 必败,跳过只是省一次必败 tx,不是永久拉黑,主循环间隔小于 10 分钟,不存在漏停风险|版本1.1.11 - 冷却跳过日志防刷屏仅观测 — 冷却跳过不超过 8 只时逐只打「#编号 冷却剩余 Xs(
time.lastage=Ys)」;超过 8 只时只详细打前 5 只,其余合成一行样例,最后打统计「本批 N 只候选,M 只在冷却中已跳过(下轮重停)」。触发:本包有冷却跳过时|参数逐只阈值 8 / 详细样例 5|来历防刷屏|版本1.1.11 harvestId兜底重刷 — 发送前、以及失败重试筛选时,用链上最新的harvest.id覆盖清单里的harvestId。行内注释仍写「更新harvestId(可能变了)」,但板块头 0913 勘误已说明harvestId固定不变,这里只是防库记录缺失或损坏的兜底。触发:预检和失败重试筛选时|来历0913 实测两账户 354 只在采 Kami、跨 2 个地块,与建库时存的值 100% 一致、0 例外,与 0709 实盘结论一致;原注释「重新部署后会变化」是错的,别据此写「不能缓存harvestId」|版本0913 实测勘误- 预检查询失败保守保留 — 发送前查链上状态出错时,不跳过这只,照样加入发送清单。catch 分支直接 push。触发:预检查询异常时|来历停采侧宁发勿漏
- 预检过滤结果处理 — 链上预检后全部被过滤掉就打「本包链上已无HARVESTING」跳过;数量减少时打「链上状态过滤: N → M」;有效
harvestId全空就打「无有效harvestId,跳过本批」。harvestId用filter(Boolean)去空。AllowFailure通道下不做逐只estimateGas预检,坏个体由合约跳过。触发:每包发送前 - tx 发出后再次检查紧急锁 — 停采 tx 发出后立刻再查紧急锁,命中就打「停采Tx已发送,检测到紧急锁,跳过确认让路紧急停采」,不等确认直接退出。当前包已出队、不再回队;队列里其余包进入末尾残留排查。触发:每次发出 tx 后|来历保证紧急停采永远优先占用 TX 通道
- 停采结果三态分流修复测试线独有 — 发送结果为 true 或 null(待确认)时都进入「沉降后复核」分支,只有 false 才走失败重试分支。true 打「API链上确认成功」;null 打「待确认…已上链(gas 特征像全执行),等 25s 沉降后复核」。null 也可能来自回执形状未知或确认异常,这句文案在那些情况下并不准确。触发:每次发送返回后|来历
ChatGPT审计 A11 + 0912 复核:raw 主通道从不返回 true,一批全部真停的标准结果是 null,旧的二态if(ok)让成功分支成了死代码,每次停采都掉进失败分支,5 秒后回队重发,最多白烧 4 次 gas|版本测试版1.2.41 - 沉降后链上 + DOM 双重复核 — 等 UI 沉降后逐只复核:链上既不是休息也不是死亡,且 DOM 仍显示采集中,才算仍在采集。链上查询异常时
console.warn并保守当作仍在采。遍历的是整包原始成员(包括预检时被跳过的)。触发:发送结果非 false 时|参数UI_SETTLE_MS=25000 - 确证已停才登记动作标记测试线独有 — 只有 DOM 确证已停的 Kami 才
recordAction('stopHarvest')登记防重复,并打「停采成功(API):Kami #编号(img:x) Δ=」。用_cardStopConfirmed判断(不确定不算已停);遍历整包,本来在预检前就已停的成员也会被登记并打成功日志;单只登记异常忽略。触发:复核阶段|来历旧判据会把 DOM 不确定当作已停去打标锁住,锁期内不再处理,导致漏停被清算|版本测试版1.2.41 - 未收敛回队重试 — 复核后仍在采集的,
failStreak+1,打「UI未收敛 仍在采集 x/y → 清单」后回队;全部停下则打「最终确认成功」。不满足二分条件时整体回队,保留当前failStreak,打「重试排队 回队 N 个」。触发:复核发现仍有采集中 - 二分拆包隔离坏个体 — 连续 2 轮未收敛且剩余不止 1 只时,把剩余清单对半拆成两个新包(
failStreak归零)分别回队,打「二分拆包 N → a + b」。复核分支和失败分支用同一套条件。触发:failStreak≥ 2 且剩余 > 1 只|参数二分条件failStreak>=2 且包内>1(写死)|来历把可能的坏个体隔离到更小的包里,逐步定位 - 发送失败后逐只核实真实状态 — 发送结果为 false 时,先打「API失败,正在查询真实状态」,逐只筛出真正还需要重试的:DOM 不显示采集中的直接剔除,链上不是 HARVESTING 的打「实际状态: X,跳过重试」;查询失败保守加入重试。全部被剔光就打「重试放弃 本包已无真正HARVESTING;不再回队」;否则打「重试筛选 原N个 → 真正需重试M个」,
failStreak+1 后按二分规则回队,并打「回队 N 个(failStreak=x)」。触发:发送结果为 false 时|来历避免二分拆包时「假采集中」的问题 Kami 反复污染重试队列 - 失败重试间隔 — 失败分支回队后等 5 秒再进入下一轮;成功复核分支不额外等待(已经等过 25 秒沉降)。触发:发送失败回队后|参数
RETRY_DELAY_MS=5000|来历调小会提高 nonce 冲突概率,调大则整体停采变慢 - 残留逐只预检排查 — 循环结束后,把队列里 DOM 仍显示采集中的 Kami 收集起来,打「对 N 个未停采的kami逐个预检」,逐只用
_preCheckStop([harvestId])做模拟调用预检。没有harvestId的直接交给 DOM 兜底;预检通过的通过 dlog 记录后加入交 DOM 清单。触发:stopWithBackoff循环结束(轮数用尽或因紧急锁中断)且有残留时|来历找出真正卡住的个体 - 预检失败拉入停采黑名单 — 残留预检失败的 Kami,把
kamiId加入停采黑名单并记下拉黑时刻,由外部逻辑按时限解封。写入__stopBlockedKamis(Set)和__stopBlockedTime(Map);没有kamiId的只打「无kamiId,无法加入黑名单」。触发:残留预检失败且前端未冻结时|来历防止无限重试 - 前端冻结时不拉黑、改下轮再处理 — 判定前端疑似失真(冻结)时,残留预检失败的 Kami 不加入黑名单,只打「前端疑似失真,不加入停采黑名单,defer 到下轮」。由
_isFrontendFrozen()判断。被推迟的成员不进交 DOM 清单,但汇总日志仍把它算在「失败(已拉黑)」里。触发:残留预检失败且_isFrontendFrozen()为真|来历前端冻结时预检和读数不可信,避免冤枉拉黑 - 预检汇总与交 DOM 兜底 — 有预检失败时打「预检结果 通过: N个(交DOM), 失败: M个(已拉黑)」;最终只把预检通过的清单返回给上层,走 DOM 点击兜底;没有残留则返回空数组。触发:
stopWithBackoff结束时
DOM 兜底停采
- 兜底名单合并、过滤与去重 — 把没有
harvestId的候选和 API 失败件合在一起,只留仍在预警带且没饿死的,同一只不重复处理。[...domList,...needDomAll] 过滤 delta≤1 且 !isStarving;按imgNumber|dbIndex去重。触发:API 停采之后|来历0 血点停采按钮同样无效|版本1.2.26 - DOM 停采切批 + 每批紧急锁中断 — 兜底名单同样随机切成 6~10 只一批,每批前遇紧急锁就中断。
chunkRandom(uniqDom,6,10);hasEmergencyLock()为真打「检测到紧急锁,中断DOM停采」break;非空批打「批量停止/DOM - 第 i/N 批」。触发:DOM 兜底停采|参数批量 6~10 - 点击前双重确认仍在采集 — 点停采按钮前再查一次,链上和卡片都不显示采集中就跳过,避免对 API 路其实已停成功的重复操作。
getByIndex读 state,state 非 HARVESTING 且_isCardHarvestingByImg也为假才 continue(任一显示采集中就继续点)。触发:每只 DOM 停采前 - 模拟点击主按钮→面板→Stop Harvest — 找到卡片主按钮点开操作面板,1.2 秒后在面板里找到可见的 Stop Harvest 点下去,并记动作冷却。主按钮缺失打「DOM 停止找不到主按钮」跳过;
simulateClick(主按钮,0);1200ms 后取主按钮 closest div[cursor='pointer'] 父节点第 2 个子节点为面板,找不到打「找不到操作面板」;找文本为 Stop Harvest 且offsetParent非空的元素simulateClick(…,1000),recordAction(img,'stopHarvest'),打「停采成功(DOM)」。代码实际:成功日志在点击时就打出,不是链上确认;找不到只记日志不重试,交下轮再收集。触发:通过双重确认后|参数面板等待 1200ms - DOM 停采只间节流 — 每点完一只歇 0.8 秒,防止点击过快、面板来不及渲染。await
delay(800)。代码实际:800ms 短于面板回调的 1200ms,下一只的主按钮点击会早于上一只的面板回调执行,时序交错。触发:每只 DOM 停采后|参数只间 delay 800ms - DOM 停采单只异常只记日志 — 某只点击停采出错时记日志继续下一只。catch 打「DOM 停止异常:错误」。触发:DOM 停采时
一键停采当前地块 stopCurrentRoom
- 一键停采当前地块命令 — 把本账户在当前所在地块上所有正在采集的 kami 一次性批量停采,按血量从低到高发送,停完方便换到别的地块采集。流程:查账户地块和 kami 列表 → 挑出 HARVESTING → 从 DOM 读实时 HP% → 10 路并发查
harvestId/采集地块 → 剔除其他地块、查不到harvestId的和冷却中的 → 按 HP 升序 → 加紧急锁 → 每批随机 6~10 只用AllowFailure停采,批间隔 6.5 秒 → 汇报,并暂停自动部署 10 分钟。触发:命令(纯手动,不会自动触发)|参数CONC=10;chunkRandom(6,10);批间隔 6500ms|命令stopCurrentRoom()执行;resumeDeploy()提前结束停采后的 10 分钟部署暂停 - 与紧急停采共用运行标志防重入 — 紧急停采或另一个一键停采正在跑时,直接拒绝本次调用,提示稍后再试。
window.__emergencyStopRunning为真就打日志返回;否则置真,finally 里无条件清掉。紧急停采、一键停采、转移停采共用这个标志,互斥运行。触发:调用stopCurrentRoom()时|来历互斥运行,防止 nonce 冲突 - 读账户地块失败或地块未知即放弃 — 用操作员地址查账户的当前地块和 kami 列表;查询出错,或者地块号读不到,都直接放弃不发 tx。
connectedAddress.value_→accounts.getByOperator取roomIndex和 kamis(非数组按空数组处理);异常打「获取账户信息失败」返回,roomIndex==null 打「当前地块未知,放弃」返回。触发:stopCurrentRoom第 1 步 - 当前地块名日志仅观测 — 在日志里打印当前地块的名字和编号。
__getRoomNameByIndex取名,取不到时显示 #编号。触发:stopCurrentRoom第 1 步之后 - 只挑采集中的 kami,没有就直接结束 — 只处理 state 为 HARVESTING 的 kami;一只都没有时打「无需停采」然后结束。state 转成大写后比较 HARVESTING。触发:
stopCurrentRoom第 2 步 - 从 DOM 读实时 HP%(图片编号→卡片映射) — 先扫一遍 Party 列表卡片,建立「图片编号→卡片」的映射,再按图片编号读卡片上的实时 HP 百分比,作为排序依据。卡片选择器 div#party>div>div:nth-of-
type(3)>div:nth-of-type(2)>div:nth-of-type(2)>div;从 img[src*=/kami/] 提取编号;状态小图标 img[src*=/assets/kami_] 的下一个兄弟节点就是 HP 文本「123/456 (78%)」,取括号里的百分比。触发:stopCurrentRoom第 2~3 步|来历API 的stats.health.sync是链上同步值,有滞后;UI 上的 HP% 是客户端实时算出来的 - DOM 读不到 HP 时按满血排最后 — 某只 kami 在 DOM 里读不到 HP% 时,当作 100% 处理,排到最后停,不挤占危险 kami 的优先位置。
_readHpPctFromDom(imgNumber)?? 100;查询异常时hpPct也记为 100。触发:并发查详情时|来历保守处理,不挤占危险 kami 的优先级 - 10 路并发查单只详情 — 开 10 个 worker 共同消费一个队列,逐只查
harvestId、采集地块号和图片编号,顺便算出冷却剩余时间。共享下标nextI,Promise.all同时启动 10 个workerLoop;getByIndex(index,{harvest:true})取harvest.id、harvest.node.index、image、harvest.time.last。触发:stopCurrentRoom第 3 步|参数CONC=10(调大查得快但节点压力大)|版本v1.1.11 - 详情查询异常的 kami 被静默丢弃 — 某只 kami 查详情抛异常时,既不停采,也不出现在「其他地块」或「查不到
harvestId」的提示里。注释说查不到harvestId的会跳过并列出;代码实际上异常时nodeIdx记为 null,这种项三个分流条件都不满足(要么要求nodeIdx===当前地块,要么要求nodeIdx!=null),所以不会进任何名单,也没有日志。触发:分流时 - 其他地块的采集中 kami 只提示不处理 — 在别的地块采集的 kami 本命令不停,只在日志里列出来。条件
nodeIdx!=null 且不等于当前地块;最多列 10 个「#编号(地块N)」,超过 10 个加省略号。触发:stopCurrentRoom第 4 步 - 查不到
harvestId的跳过并列出 — 在当前地块、但查不到harvestId的 kami 跳过不停,把编号全部列在日志里。条件nodeIdx===当前地块 且harvestId为空。触发:stopCurrentRoom第 4 步 - 180 秒操作冷却预筛 — 还在 180 秒操作冷却里的 kami 从本批剔除,这次不发 tx。只是跳过,不拉黑。用同一次查询拿到的
harvest.time.last算_cooldownRemainSec,大于 0 就剔除;读不到time.last按无冷却处理,回到旧行为。冷却解除后,下次手动调用或自动化主流程(10 分钟内扫一次)会重新捕获。触发:stopCurrentRoom第 4 步之后|参数操作冷却 180s|来历冷却中发停采 tx 必败;跳过只是省一次必败 tx,不会永久漏停|版本1.1.11 - 冷却预筛日志防刷屏仅观测 — 冷却中的 kami 少时逐只打印;超过 8 只时只打印前 5 只的详情,其余合成一行样例,最后再给一句汇总。
showN= 数量>8 ? 5 : 数量;逐条日志包含冷却剩余秒数和time.last距今多少秒。触发:冷却预筛命中时|参数阈值 8 只 / 样例 5 只|版本1.1.11 - 没有可停的 kami 时友好结束 — 剔除后当前地块没有要停的 kami,就打一句完成然后结束;如果是因为冷却才剩 0 只,会注明「扣除冷却中的以外」。
inThisRoom.length===0 时 return(此时还没加锁)。触发:冷却预筛之后 - 按 HP% 升序、危险优先停 — 血量最低、最接近被清算的 kami 最先停,日志显示本批 HP 范围。
inThisRoom.sort((a,b)=>a.hpPct-b.hpPct)。触发:stopCurrentRoom第 5 步|来历越低越危险,优先停 - 发 tx 前加紧急锁,只释放自己加的锁 — 开始发停采 tx 前设置紧急锁,让
runAutomation主循环暂停让路;结束时只在自己确实加过锁的情况下才释放。setEmergencyLock()后emergencyLockSet=true,finally 里if(emergencyLockSet)releaseEmergencyLock()。和紧急停采不同,这里不等普通锁释放,也不设__kamiOperationInProgress。触发:stopCurrentRoom第 6 步|参数主循环见到紧急锁最长等 300 秒(注释所写)|来历防止与主流程的 tx 撞 nonce - 每批随机 6~10 只分批 — 待停的 kami 按每批 6~10 只随机分组发送。
chunkRandom先算出最少批数,保证每批不少于 6 只再均匀分配,零头随机分散;总数不超过 10 时一批发完。触发:stopCurrentRoom第 7 步|参数chunkRandom(inThisRoom,6,10)|来历避免固定节奏的批量 tx 被识别为机器人 AllowFailure批量停采,返回 true 直接计成功 — 每批用容错批量停采发一笔 tx,返回值严格为 true 时整批计为成功。_allowFailureStop(harvestIds, fmt);日志列出每只的 #编号(HP%)。触发:逐批- 三态沉降等待(已上链等 25 秒 / 真失败等 5 秒)测试线独有 — 批次返回不是 true 时先等一会儿再核查:返回 null(tx 已上链、等确认)等 25 秒,返回 false(真失败)等 5 秒。
__settle= ok===null ?UI_SETTLE_MS: 5000;两种情况日志文案不同(🟡已上链待确认 / 🔄整批失败)。raw 主通道下 ok 永远不是 true,所以每批都会走这个分支。触发:批次返回非 true 时|参数UI_SETTLE_MS=25000;真失败等待 5000ms|来历索引器确认滞后实测 17~99 秒,旧版只等 5 秒时状态还没翻过来,就把已停的又发了一遍|版本测试版1.2.41 - 核查后只重发仍在采集的 — 等待结束后逐只查链上状态,只留下仍是 HARVESTING 的去重试,并换上最新的
harvestId。逐只getByIndex;state 为 HARVESTING 时取res.harvest.id(取不到用旧的)加入stillNeed。触发:沉降等待之后|来历第一次 tx 可能部分成功,不重发已停的,免得浪费 gas - 核查时查询失败保守保留 — 核查阶段某只 kami 查询出错时,保守地把它留进重试名单。catch 分支里
stillNeed.push(d)。触发:核查阶段|来历宁可多试一次,也不能漏停 - 核查结果:全部已停或部分已停计入成功 — 核查发现全部已停,就整批记成功不重试;只停了一部分,就把已停数计入成功,只重试剩下的。
stillNeed为空时totalOk+= 整批数量;数量少于整批时totalOk+= 差值,并打「核查后 N 只已停,剩 M 只重试」。触发:核查之后 - 只重试一次,仍失败交给主循环 — 对剩下的 kami 再发一次批量停采;这次还失败就不再坚持,留给下一轮
runAutomation自动处理。retryOk为真值才计成功。注释说失败才计失败;代码里if(retryOk)只认真值,返回 null(已上链待确认)也会被记成「重试仍失败」计入totalFail,没有二次核查。触发:核查后仍有未停|来历重试后仍失败不再死磕,下一轮常规流程会兜底 - 批间隔 6.5 秒 — 两批之间固定等 6.5 秒,最后一批发完不等。bi <
batches.length-1 时delay(6500)。触发:每批之后|参数delay(6500)|来历避免连续 tx 撞 nonce 或触发限流 - 完成汇报仅观测 — 用绿色大字打印成功数/总数、失败数和总耗时(毫秒加分秒两种写法)。
_fmtMinSec(elapsedMs)。触发:全部批次结束 - 停采后暂停自动部署 10 分钟 — 停完后自动部署暂停 10 分钟,给转移或换地块留出时间;日志显示到几点恢复,并提示可以用命令提前恢复。
_pauseDeployAfterStopAll('一键停采') 设置window.__deployPausedUntil=现在+10 分钟,按配置时区显示 HH:MM;成功数为 0 时也会触发。触发:汇报之后|参数DEPLOY_PAUSE_AFTER_STOPALL_MS=10 分钟|命令resumeDeploy()立即清零暂停,下一轮恢复自动部署|来历防止刚停的 kami 又被自动部署回去 - 可以去其他地块的提示仅观测 — 至少停成 1 只时,用青色大字提示当前地块的 kami 已停采,可以去别的地块采集了。
totalOk>0 时打印。触发:汇报之后 - 异常兜底 — 整个流程中出现任何未捕获异常都只打日志;锁和运行标志照常清掉。catch 打「[一键停采] 异常」,finally 释放锁、清标志。触发:异常时
转移停采 stopMinorityForTransfer
- 转移停采命令 — 配合多账户分工:每个账户只专注一种地块类型(主流),其他类型(少数派)的 kami 要转给对应账户。本命令把本账户所有正在采集的少数派 kami 批量停采,再按目标地块类型分组打印可转移清单,照着清单用游戏内转移功能发送即可。流程:取分类 → 分出 HARVESTING 和 RESTING → 读 DOM HP% → 并发查
harvestId→ 按 HP 排序 → 加锁 → 冷却预筛 → 第一轮全部发 → 等 60 秒核查 → 第二轮重试 → 等 8 秒最终核查 → 汇报、暂停部署、失败名单、可转移清单。和stopCurrentRoom的区别是按非主流类型筛,不按当前地块筛。触发:命令(纯手动)|参数CONC=10;chunkRandom(6,10);批间隔 6500ms;COOLDOWN_WAIT_MS=60000;终核等待 8000ms|命令stopMinorityForTransfer()执行,失败后可重复调用,只处理仍在采集的;syncKamiDb()同步本地精简数据库;resumeDeploy()提前结束部署暂停 - 防重入 — 紧急停采或一键停采正在运行时拒绝执行。
window.__emergencyStopRunning为真则打日志返回;和紧急停采、一键停采共用这个标志,finally 里清除。触发:调用时|来历与紧急停采互斥 - 检查辅助脚本接口是否可用 — 辅助脚本没加载、版本太旧、拿不到少数派数据时,直接红字报错退出,不做半吊子降级。typeof
window.__getMinorityKamis!== 'function' 时打两行红字(提示先确认辅助脚本已开启并加载完成)然后返回。这是跨脚本全局接口约定。触发:调用时|来历不做半吊子降级 - 按目标地块类型分组、着色打印转移清单仅观测 — 把 kami 按目标地块类型分组,每组一种颜色,列出全部编号;超过 25 个自动换行,方便完整复制不遗漏。
_printByTarget:按 terrain 分组;颜色 normal 灰 #9e9e9e / eerie 紫 #c678dd / scrap 橙 #e5a13a / insect 绿 #4ec94e(和辅助脚本kamiAnalyze同色系),未知类型用灰色;标题写「N 只 → 转给 XXX 主流账户」。触发:打印 RESTING 清单或最终可转移清单时|参数PER_LINE=25;TERRAIN_COLORS|来历单行过长不方便复制;按类型着色,转移时不容易看混 - 取分类结果失败即放弃 — 调用辅助脚本接口取主流/少数派分类,出错就放弃。await
window.__getMinorityKamis(),返回 {majority,majorityCount,minorityKamis,excludedKillers};抛异常时打日志返回。触发:第 1 步 - 识别不出主流类型时提示先同步数据库 — 本地精简数据库为空、识别不出主流类型时放弃,并提示先运行同步命令。!
r.majority时打「无法识别 majority(db 可能为空,先运行syncKamiDb())」然后返回。触发:第 1 步|命令syncKamiDb() - 主流/少数派统计日志仅观测 — 打印主流类型名、主流 kami 数量和少数派数量。青色加粗。触发:取到分类后
- 杀手保护:担任杀手的 kami 不转走 — 担任杀手职责的 kami 不会被本命令停采转走,日志会说明排除了几只。过滤在辅助脚本
__getMinorityKamis内部完成,本处只根据excludedKillers.length打「🛡️ 杀手保护」日志。触发:取到分类后 - 少数派没有在采集的,直接给出 RESTING 转移清单 — 少数派里没有 HARVESTING 时不发任何 tx:有 RESTING 的就直接打印「已可立即转移」清单;连 RESTING 都没有就说明全是死亡或升级中等状态,无可操作。
minHarvesting.length===0 时分支,结束后 return。触发:第 2 步 - 从 DOM 读实时 HP%(和一键停采同款) — 建立图片编号到卡片的映射,读卡片上括号里的实时 HP%,用来排序。选择器和解析逻辑与
stopCurrentRoom完全一致;读不到按 100 处理。触发:第 3 步|来历UI 实时值比链上同步值新 - 10 路并发查
harvestId— 10 个 worker 并发查每只少数派 kami 的harvestId和图片编号;查询失败的harvestId记为空。getByIndex(index,{harvest:true});异常时记 {harvestId:null,hpPct:100},同时带上 terrain。触发:第 4 步|参数CONC=10 - 查不到
harvestId的跳过,全都查不到则放弃 — 查不到harvestId的列出编号后跳过;一只都查不到时整个命令放弃。stoppable=有harvestId的,missingId=没有的;stoppable 为空时返回(此时还没加锁)。触发:第 5 步 - 按 HP 升序、危险优先 — 血量低的少数派先停,日志显示 HP 范围。
stoppable.sort按hpPct升序,和stopCurrentRoom顺序一致。触发:第 5 步 - 紧急锁(在冷却预筛之前就加) — 冷却预筛和发 tx 前先设置紧急锁,让主循环让路;finally 里只释放自己设的锁。
setEmergencyLock(),emergencyLockSet=true。锁在冷却重查(10 路查询)之前就已加上;全部冷却中提前 return 时也由 finally 释放。触发:第 6 步 - 首轮发送前重新查一次,做冷却预筛 — 第一轮发 tx 前再并发查一次
time.last,把仍在 180 秒冷却里的 kami 剔出首轮。只是跳过,不拉黑。单独再开 10 路cdWorker查询,保证数据新鲜(第 4 步到这里可能已过了几秒到十几秒);查询失败时 remain=0,回到旧行为。触发:第 6.5 步|参数CONC2=10;冷却 180s|命令stopMinorityForTransfer()冷却解除后重调|来历冷却中发停采 tx 必败;本命令设计上就是失败后重复调用、只处理仍在采集的|版本1.1.11 - 冷却预筛日志:带预计解除时刻,并防刷屏仅观测 — 逐只打印冷却剩余秒数、
time.last距今秒数和预计几点解除;超过 8 只时只详列 5 只,其余合成一行样例,最后给汇总。clearAt= 现在 + remain 秒,toLocaleTimeString显示。触发:冷却预筛命中时|参数阈值 8 / 样例 5|来历记录首轮预测的解除时刻,方便事后核对|版本1.1.11 - 全部在冷却中时提示稍后重调 — 候选全部在冷却里时,本次不发任何 tx,提示稍后再调一次。
readyNow.length===0 时 return;不会触发暂停部署。触发:冷却预筛之后|命令stopMinorityForTransfer()|版本1.1.11 - 第一轮全部发送,不假设成功 — 把所有能停的少数派按每批 6~10 只全部发出去,不看返回值,成功与否以之后的链上核查为准。
chunkRandom(readyNow,6,10);await_allowFailureStop但不接返回值;批间delay(6500);日志写「#编号(→目标类型)」。批内不再做「发完等 5 秒立即重试」,统一留到第 8 步处理。触发:第 7 步|参数批间隔 6500ms|来历容错批量模式下部分 kami 可能因冷却没停成,要以链上结果为准 - 等 60 秒后核查链上真实状态 — 第一轮发完固定等 60 秒,再逐只查链上状态,仍在采集的进入第二轮(换上最新
harvestId)。COOLDOWN_WAIT_MS=60000 之后逐只顺序getByIndex。注释已更正:冷却实测 180 秒,60 秒小于 180 秒,二轮仍可能撞上冷却,靠第 6.5 步预筛降低概率,剩下的靠失败提示引导重调。触发:第 8 步|参数COOLDOWN_WAIT_MS=60000|来历冷却实测 180 秒(0708 定案),不是旧估计的「最长约 3 分钟 / 60 秒覆盖大部分」|版本v1.1.11 - 一轮核查时查询失败保守进二轮 — 第一轮核查中查询出错的 kami 保守地放进第二轮重试。catch 时
stillH.push(d);之后打印「已停采 X/Y,剩 N 只需二轮」。触发:一轮核查 - 第二轮批量重试一次,等 8 秒最终核查 — 只对仍在采集的再批量发一次,等 8 秒后做最终核查;这次查询出错的保守计为失败。
chunkRandom(stillH,6,10),批间 6.5 秒;delay(8000)后逐只getByIndex,仍 HARVESTING 或查询异常的都进finalFailed。触发:第 9 步(一轮核查后仍有未停)|参数delay(8000)|来历宁可让用户多跑一次,也不能把没停的当成可转移 - 两轮战果汇报仅观测 — 根据链上真实状态汇报「第一轮 X + 第二轮 Y = 总数/候选数」,或者「一轮全部成功」,再加失败数和耗时。分母是
readyNow(实际发过 tx 的)。触发:第 10 步 - 停采后暂停自动部署 10 分钟 — 停完后开一个 10 分钟的转移窗口,这段时间不自动部署。
_pauseDeployAfterStopAll('转移停采')。触发:汇报之后|参数10 分钟|命令resumeDeploy()|来历防止刚停的 kami 又被自动部署回去 - 失败名单和重跑建议仅观测 — 两轮后仍没停的 kami 用红字列出(带目标类型,每行 25 个),并建议等 2~3 分钟再调一次命令。失败原因写的是可能仍在 180 秒冷却内、链上拥堵或其他问题。触发:
totalFail>0|参数PER_LINE=25|命令stopMinorityForTransfer()|来历措辞已同步为实测的 180 秒冷却,不再写旧估计的「cooldown > 3 分钟」|版本v1.1.11 - 可转移清单(原本 RESTING 的 + 本次真正停成的) — 最后打印可以转移的少数派清单:原本就在休息的,加上本次经链上核查确认停成的;并提示等约 30 秒让链上同步后再用游戏内转移功能发送。
successfullyStopped从readyNow(实际发过 tx 的)里去掉finalFailed,而不是从 stoppable 里去;再映射回minorityKamis的原始条目。触发:汇报末尾|来历冷却预筛剔除的 kami 从没发过 tx、仍在采集,如果误用 stoppable 会把它们也当成停采成功列进清单|版本v1.1.11 - 不写停采黑名单 — 转移停采失败不会把 kami 拉进停采黑名单。本函数全程不调用任何失败计数或拉黑记账函数(板块说明所写的设计)。触发:始终|来历转移停采的失败是临时性的(冷却、链上拥堵),不应该像常规停采失败那样拉黑
- 异常兜底 — 未捕获异常只打日志;finally 释放自己设的锁并清运行标志。catch 打「[转移停采] 异常」。触发:异常时
1.4 紧急停采
emergencyStopHarvest 只停血量触及停采线(≤ 停采线 +1%)和正在饿死的 kami,健康的不动。触发来源有四个:杀手监控发现杀手靠近(见 4.5)、主循环候选多到异常的硬触发(见 1.3)、死亡监控发现有 kami 死亡(见 1.13)、手动在控制台调用。
入口与总流程
- 控制台命令
emergencyStopHarvest— 把紧急停采入口挂到 window,可在浏览器控制台手动立即跑一轮完整紧急停采:DOM 扫描、凑批、批量发送、链上复核、cooldown 重试。触发:命令|命令emergencyStopHarvest()|来历方便控制台测试和手动救急 - 紧急停采设计要点总述 — 板块注释总述紧急停采策略:每批随机 6~10 只;并行发送不等
tx.wait()先抢时间窗口;用链上 API 查询确认结果;无harvestId先补查;失败≥10 走 API 整批重试、少量走 DOM 兜底;RPC 超时先查链上状态再决定是否重发。本段只是注释总述,具体实现在紧急停采主流程(不在本段范围,未在此核实)。触发:紧急停采时|来历gas 飙升时大批量易整批 revert,随机批量错峰提高成功率;盲目重试可能两笔都上链,双倍 gas 甚至 nonce 冲突 - 紧急停采主入口 — 扫描页面上所有采集中的 kami,找出血量已跌破或逼近停采线的,按凑批省 gas 的策略批量停采;饿死的先喂食救援。delta = 当前 HP% − 停采线,越负越危险。流程:DOM 扫描 → STARVING 可疑重扫 → 剔除退避队列成员 → 可选修剪 → 凑批决策 → 分级日志 → 链上预检 → 加锁、等普通锁、等喂食排空 → 喂 STARVING → 剔除 STARVING 后二次预检 → 冷却预筛 → 最多 3 轮「门禁 → 背靠背发送 → 并行确认 → 首批强化 → 动态等待 → 复读」→ 冷却收尾 → 汇总 → finally。
opts.trimTo只有硬触发路径会传。触发:自动:危险 HP 监控、杀手监控、批量硬触发(传 {trimTo:25})调用,也会被盲扫重试setTimeout调用|参数EMERGENCY_CONFIG(CRITICAL_DELTA=-20 /HIGH_RISK_DELTA=-10 /DANGER_DELTA=0 /MAX_ROUNDS=3 等)|命令无独立命令 - 防重入:已在运行就跳过 — 同一时刻只允许一个紧急停采实例运行,重复调用直接跳过。
window.__emergencyStopRunning为真时打「已在运行中,跳过」并返回;否则置真,finally 里无条件清除。触发:每次调用 - 被跳过的全撤请求自动补撤测试线独有 — 不带
trimTo的全撤请求(杀手监控/死亡监控/手动)撞上正在跑的一轮时,记到window.__pendingFullEvac(次数+时间)并打「已在运行中,跳过(本次是全撤请求,已记待办…)」;当前轮 finally 释放锁后 1 秒自动再调一次emergencyStopHarvest()(不修剪),打「[紧急停采/补撤] 本轮运行期间有 N 次全撤请求被跳过…」。修剪请求撞上照旧丢弃不记。触发:重入被拒时记、本轮结束时补|参数延时 1s|来历0914 全库「已在运行中,跳过」805 次,含同房间杀手告警撞上修剪被吞的样本;保留数抬高后单轮更长、被吞窗口更大|版本测试版1.2.57 - 启动时刷新前端冻结信号 — 每次紧急停采开始时先刷新一次前端冻结判断,供后续的冻结门闩读取。
window.__evalFrontendFrozen存在就调用,出错静默吞掉。触发:每次调用开头|版本v1.1.25 - 「kami 操作进行中」全局标记 — 紧急停采运行期间设置一个全局标记,供其他模块避让。开始时
window.__kamiOperationInProgress=true,finally 里置 false。触发:每次调用 - 调用级状态重置 — 每次调用都把去重、熔断、卡链、成功集、冷却队列和各项统计清零,让本次的记账和上一次互不干扰。
__stopInvocationId++;清零__stopConsecutiveRevertBatches、__stopCircuitBroken;清空__stopStuckThisInvocation、__stopConfirmedThisInvocation、__stopCooldownDeferredThisInvocation;__pendingVerifyBatchCount=0、__stopChanFallbackCount=0;清空 tx 确认耗时和索引滞后样本、revert 原因去重集、群体滞后日志标记。__stopPendingVerify(退避复读队列)跨调用保留,不清。触发:每次调用开头|来历熔断计数、疑似卡链名单只在单次调用范围内生效|版本1.1.12 / 1.1.14 / 1.1.17 / 1.1.19 / 1.1.22 - 没有要停的就直接返回 — 扫描结果为空时打「没有需要停采的 kami」然后返回,不加锁。
stopList.length===0 时 return。触发:第 1 步 - STARVING 过多时防误判:等 5 秒重扫 — 首次扫描发现超过 10 只饿死时,怀疑页面没加载完,等 5 秒重扫一次,以重扫结果为准;重扫后仍然很多就当作真的大规模饿死继续处理。重扫后 STARVING 数下降打绿色「以最新结果为准」,没下降打「可能确实发生大规模饿死」;重扫结果为空则返回。触发:首次扫描 STARVING 数 > 10|参数
SUSPICIOUS_STARVING=10;等待 5000ms|来历大量 STARVING 通常是网页没加载好、HP 文本解析成 0%,要避免误停一大批健康 kami - 剔除已在退避确认队列的 kami,不重发 — 已经发过停采、正在等索引器追上的 kami,本轮不再重发。
kamiId在__stopPendingVerify里的从stopList移除,日志最多列 12 个编号;全部被剔除时返回。真没停的由estimateGas裁决快速放回候选。整段 try/catch 包住,出错不影响主流程。触发:扫描之后、修剪之前|来历对已停的 kami 重发必然 revert 白烧 gas(0711 实测 24 批浪费全出在这里)|版本1.2.1 - 危险分级统计日志仅观测 — 按 delta 把名单分为极危、高危、危险、一般四档并打印数量,只用于展示。≤-20 极危;-20~-10 高危;-10~0 危险;>0 一般。触发:凑批放行后|参数
CRITICAL_DELTA=-20,HIGH_RISK_DELTA=-10,DANGER_DELTA=0 - 延迟加锁,只释放自己加的锁 — 扫描、凑批、预检全程只读不加锁,确认确实有停采目标后才设置紧急锁;finally 里只在本次加过锁时才释放。
setEmergencyLock(),emergencyLockSet=true;finally 中if(emergencyLockSet)releaseEmergencyLock()。触发:第 4 步|来历把紧急锁的占用时间压到最短,也避免误释放其他流程持有的锁 - 加锁后等普通锁释放(最多 30 秒) — 加锁后如果普通操作还占着锁,等它把当前这笔 tx 做完,最多等 30 秒。
window.__txNormalLock存在时打出占锁的脚本名和操作名,然后waitForNormalLockRelease(30000);返回值没人接,超时照样往下走。触发:加锁后|参数30000ms|来历普通操作检测到紧急锁会主动让路,避免 nonce 冲突 - finally 兜底清理 — 不论成功、失败还是提前 return,最后都释放本次加的紧急锁,并清掉「操作进行中」和「运行中」两个标记。
if(emergencyLockSet)releaseEmergencyLock();__kamiOperationInProgress=false;__emergencyStopRunning=false。触发:每次调用结束|来历避免误释放其他流程持有的锁
DOM 扫描、列表打开与盲扫守卫
- 扫描前先判断 DOM 是否就绪测试线独有 — 扫描卡片前先确认页面 DOM 可读:卡片不为 0、不完整比例不超过一半、数量没有低于历史峰值的 10%;未就绪就每 3 秒重扫,最多 6 次,期间最多主动打开一次 Party 列表或切换眼睛状态。
__emergencyScanWithDomGuard('首次扫描') 返回 {ready, list, reason};判断不要求主按钮存在(列表折叠时血量仍可读)。阈值和主循环同源,改一处必须同步另一处。触发:第 1 步,以及 STARVING 可疑重扫时|参数MAX_TRIES=6,GAP_MS=3000,DOM_INCOMPLETE_THRESHOLD=0.5,SCALE_ANOMALY_RATIO=0.1(历史峰值≥20 才判断)|存储kami_last_known_count(读,历史卡片峰值)|来历读不到卡片时绝不能报「没有需要停采」|版本测试版1.2.51 - 盲扫告警与自动重试测试线独有 — 页面没就绪时红字大写说明「本次无法判断,这不是没有需要停采」,20 秒后自动再调一次紧急停采,连续最多 3 次;只要读到了真实 DOM,计数就清零。
__scheduleEmergencyBlindRetry(opts, reason):window.__emergencyBlindRetryCount达到 3 次就停止自重试,交给下一轮杀手监控或主循环,并提示检查 Party 列表是否展开;否则setTimeout20 秒后重调,带原 opts。触发:DOM 预检未就绪时|参数MAX=3,RETRY_MS=20000|来历读不到卡片时绝不能报「没有需要停采」|版本测试版1.2.51 - Kami 列表折叠判定
__isPartyListClosed测试线独有 — 判断 Party 列表是不是折叠或还没打开:div#party 计算样式为 display:none,或者元素不存在,都算没打开。getComputedStyle抛错时按已打开处理(返回 false)。触发:__ensurePartyListOpen和 DOM 预检调用|来历0914 探针:刷新后 94 秒、启动流程还没点开列表时,#party 是 display:none,只有 1 张卡、读不到血量、眼睛=open,于是每次刷新后约 2~3 分钟紧急停采读不到血量|版本测试版1.2.53 - 按状态打开 Kami 列表
__ensurePartyListOpen测试线独有 — 列表折叠时才点 Party 按钮展开(点完等 2.5 秒);眼睛不在 half 时才点眼睛切换(最多点 3 下,每下等 1.5 秒)。返回本次有没有真的动过手,有的话调用方立刻重扫。找不到 Party 按钮(游戏页没就绪)直接返回 false;点过眼睛后再等 1 秒复读,还不是 half 只记日志,不再点(启动路径另有waitForEyeHalf兜底)。触发:启动流程、主循环 DOM 修复、紧急停采 DOM 预检共三处调用|来历Party 按钮和眼睛都是开关,盲点会把已经打开的点关。1.2.51 为此干脆不点,1.2.53 改为先读状态再点|版本测试版1.2.53 - 打开列表三方串行锁
window.__ensurePartyInFlight测试线独有 — 同一时刻只允许一个调用方在点列表开关。后来的先等前一个做完,再按当前状态重新判断;判据是幂等的,不会重复点。最多循环等 5 次;把当前任务挂到window.__ensurePartyInFlight,finally 里只清除自己挂的那个任务。触发:__ensurePartyListOpen每次调用|来历三处调用同时盲点开关会把列表点关|版本测试版1.2.53 - DOM 不完整计数
__countIncompleteDom(requireButton选项)测试线独有 — 统计有多少张卡片不完整:缺状态图、缺「(NN%)」血量文字、缺 kami 编号、缺主按钮,任一即算。紧急停采传requireButton:false,不把缺主按钮算作不完整。默认仍要求主按钮,因为主循环要点按钮(DOM 兜底部署/停采);紧急停采只走 API 发交易,从不点按钮。触发:主循环 DOM 预检、紧急停采 DOM 预检|来历0913 23:34 探针:列表折叠时 313 张卡的状态图、血量、编号都还在,只有主按钮不渲染。不加这个选项,列表一折叠紧急停采就整晚「无法判断」,一只都不停(0913 23:23、23:28 实录)|版本测试版1.2.52 - 紧急停采 DOM 就绪预检
__emergencyScanWithDomGuard测试线独有 — 扫描前先判断页面有没有准备好。三个条件同时满足才算就绪:卡片数>0、不完整占比≤50%、规模不低于历史峰值的 10%(峰值≥20 时才判规模)。就绪才调_emergencyScanKamis。判据逐字照搬主循环 DOM 预检,另加一条主循环没有的「卡片数>0」(主循环 0 张卡时占比算 0,历史峰值<20 会被放行)。常量与主循环同值,注释要求改一处必须同步另一处。触发:emergencyStopHarvest首次扫描、STARVING 可疑重扫|参数DOM_INCOMPLETE_THRESHOLD=0.5;SCALE_ANOMALY_RATIO=0.1|存储kami_last_known_count(只读,历史规模峰值)|来历0913 14:48:18 杀手监控拉起紧急停采时页面还没就绪:匹配 div 数=0,却报「没有需要停采的kami」,而前后两次扫描实际分别需停 2 只和 6 只。停采侧 fail-open,正好违背「宁发勿漏」|版本测试版1.2.51 紧急停采盲扫修复 - 预检未就绪:每 3 秒重扫,最多 6 次测试线独有 — 页面没就绪时打橙色日志,写明原因(匹配div数=0 / 规模异常 N/峰值 / 不完整 N/M 及百分比),3 秒后重扫,最多检查 6 次(约 15 秒)。最后一次不再等待,直接返回 {ready:false, list:[], reason}。触发:DOM 预检每次未就绪|参数
MAX_TRIES=6;GAP_MS=3000|来历要覆盖实测开机竞态 +2~+14 秒:核心启动要等 120 秒加随机 0~30 秒才点 Party,监控固定 150 秒首启就开扫,0913 十个会话里有 3 个余量 ≤0 秒|版本测试版1.2.51 - 预检中主动打开列表(每次预检最多一次)测试线独有 — 未就绪,且列表折叠或眼睛不在 half 时,调
__ensurePartyListOpen主动打开。真的动过手就立即重扫,不干等启动流程。每次预检最多主动打开一次(__ensured标记);没动手就照常等 3 秒再扫。触发:DOM 预检未就绪时|来历刷新后启动流程还没点开列表时,紧急停采会读不到血量|版本测试版1.2.53 - 眼睛不在 half 但卡片可读时照常扫描测试线独有 — DOM 已就绪但眼睛不是 half,或者有少量不完整卡片时,照常扫描,只打一行 ℹ️ 日志;重试多次后才就绪的,打 ✅「第 N 次检查就绪」。和主循环一致:DOM 完整时也不看眼睛。触发:DOM 预检就绪时|版本测试版1.2.51
- 盲扫后有界自重试
__scheduleEmergencyBlindRetry测试线独有 — 预检仍不就绪时,20 秒后自动再调一次emergencyStopHarvest(opts),最多连续 3 次。到上限后打红字:停止自重试,交下一轮杀手监控或主循环,请检查 Party 列表是否展开。计数存在window.__emergencyBlindRetryCount,调用方读到真实 DOM 时清零;重试异常会被 catch 打日志。预检失败时调用方输出 🚨「无法判断」,绝不输出「没有需要停采」。触发:紧急停采扫描判定页面未就绪后|参数MAX=3;RETRY_MS=20000|来历不能只等约 2 分钟后的下一轮杀手监控。计数挂在 window 上而不是模块级 let:脚本是 async IIFE,模块级 let 初始化前被调用会 TDZ 崩溃,而紧急停采恰恰可能在启动早期被监控拉起|版本测试版1.2.51 - DOM 扫描开头诊断日志仅观测 — 每次扫描开始打印眼睛状态、匹配到的卡片 div 数、
maxDelta,扫描为 0 张时方便排查 selector 是否失效。卡片 selector:div#party>div>div:nth-of-type(3)>div:nth-of-type(2)>div:nth-of-type(2)>div。触发:_emergencyScanKamis入口 - 只处理采集中的卡片 — 状态图 src 不含
kami_harvesting的卡片直接跳过;只有采集中(包括 STARVING)的 kami 才可能需要停采。触发:逐卡扫描 - 血量解析失败时跳过并记原始文本 — 从状态图相邻节点解析「(NN%)」,解析不出时记下标签名和原始文本(截 60 字),跳过这张卡。触发:逐卡扫描|来历方便排查游戏前端 DOM 改版
- STARVING 判定与无条件入选 — 采集中但血量为 0% 判为 STARVING(正在饿死,最高优先级),不看停采线,无条件入选并打上
isStarving标记,同时记录hpRaw原始文本和停采线。触发:逐卡扫描|来历单独记原始文本,便于核对是否是 DOM 渲染异常造成的误判 - 按形象编号匹配本地库 — 从卡片形象图提取
imgNumber,在window.kami_core_db里找对应记录,拿到 index、kamiId、harvestId、LT、body。找不到就跳过(没法定位链上 id)。两边都转成字符串再比较。触发:逐卡扫描 - 个体停采线计算 — 有有效清算线 LT 时,停采线 =
min(LT+3, 80);没有 LT 时按体型回退:normal 用 65%,其他用 76%。触发:逐卡扫描|参数LT_STOP_MARGIN=3;MAX_THRESHOLD=80;DEFAULT_THRESHOLD_NORMAL=65;DEFAULT_THRESHOLD_OTHER=76|来历在精确清算线上方留 3 个百分点安全垫,同时封顶 80%,保证每个周期至少能采 20 个百分点|版本v1.1.8 起安全垫回归3 - 入选宽容度
maxDelta(默认 1) — delta = 当前血量 − 停采线。delta ≤maxDelta就入选,默认只收距停采线 1% 以内的临界 kami。调用方可以放宽到 5 或 8,把接近临界的也凑进同一批,摊薄单笔交易的 gas;放太宽就等于提前停采,缩短采集时长。触发:逐卡扫描|参数maxDelta默认 1|来历批量化要落在 gas 甜区 - 单张卡异常隔离 — 某一张卡解析时抛错,只记一行日志(带扫描序号),不影响其余卡片。触发:逐卡扫描
- 扫描汇总日志与 STARVING 过多告警仅观测 — 扫完打印汇总:采集中数量、STARVING 数量及占比、需停采数量。STARVING 超过 10 只时打红色告警,提醒先核对每只的
hpRaw,确认 DOM 是否正常。告警只提示,不影响停采行为。触发:扫描结束|参数STARVING 告警阈值=10|来历大面积 STARVING 更可能是 DOM 渲染异常导致的集体误判,不应对着假数据狂发停采 - 按危险程度排序(delta 升序) — 需停采名单按 delta 从小到大排序,最贴近或已跌破停采线的排在最前,优先进批。触发:扫描结束返回前
- 按形象编号重新定位最新卡片 — 按
imgNumber在当前 DOM 里重新找到对应卡片,不复用扫描时缓存的元素引用。从形象图 src 用正则 /\/kami\/(\d+).gif/ 提取编号,转字符串后比较;imgNumber为空或找不到返回 null,由调用方兜底。触发:_isKamiInCooldown调用|来历紧急停采可能跨多轮、持续较久,缓存的kamiDiv会因前端重渲染被替换失效
凑批与修剪
- 硬触发修剪:只停超出目标数的部分 — (测试版 1.2.59 起:修剪进行中来了全撤请求,会在下一轮发送前提前收尾让全撤先跑;冷却中非高危 1~2 只「推迟到下轮合批」只在修剪来源生效,全撤来源直接发)批量硬触发时,只停超出目标数(主循环硬触发传入
trimTo=STOP_TRIGGER_HARD,测试版 100、公开版 36)的那部分,剩下的继续采。饿死的优先占停采名额,其余按 delta 从低到高挑。公开版只在opts.trimTo存在且候选数 >trimTo时生效;测试版 1.2.59 起带trimTo就一律按修剪算:超额数 = max(0, 候选数 −trimTo),超额为 0 时只停 STARVING,没有 STARVING 就打「✂️ [批量修剪] 候选N只 ≤ 保留数M,无需修剪,本轮不停(没有需要停采的kami)」直接返回——旧写法下二次扫描(C1 剔掉退避中的)候选一旦 ≤trimTo,会跳过修剪把整池当危险全停(1370 次历史触发里没真发生过,最险一次只差 1 只)。实际停采数 = max(超额数, STARVING 数),普通 kami 按 delta 升序补足。STARVING 多于超额数时会停得更多,保留数少于trimTo属于预期。修剪后的名单仍要过凑批门槛。杀手/危险路径不传参,行为逐字节不变。触发:无杀手警戒的批量硬触发路径传 {trimTo:STOP_TRIGGER_HARD}|参数trimTo=100(测试版)/36(公开版),由调用方传入;残留补停模式(opts.residual)忽略trimTo|来历用户 0710 定案:这个触发器没有杀手威胁,纯粹是数量管理,贪婪模式下 Δ≤-1 本来就可以继续采|版本1.1.24;测试版1.2.59 - 凑批决策:有危险 kami 立即发 — 名单里只要有一只饿死,或 delta 不高于 -1,就立刻批量停采,不等凑批。
hasDangerous= 任一isStarving或 delta ≤EMERGENCY_DANGER_DELTA;红字日志写明其中几只危险。名单保持原样,不会把中危 kami 拉进来。触发:修剪之后|参数EMERGENCY_DANGER_DELTA=-1|来历危险护栏:保命优先 - 凑批决策:够 6 只立即发 — 没有危险 kami,但数量达到 6 只时立即批量停采。
stopList.length≥TARGET_BATCH_MIN,日志注明每只约 1.5M gas。触发:修剪之后|参数TARGET_BATCH_MIN=6|来历批量停采的固定 gas 要摊到足够多的 kami 上才划算 - 凑批决策:不够 6 只且无危险就跳过本轮 — 没有危险、数量又不到 6 只时本轮不停采、不加锁,等下一轮积累到 6 只再合批发;同时提醒小账户可能永远凑不够。日志显示 Δ 范围(
toFixed(2),避免浮点长小数),并提示账户 kami ≤7 时可以考虑切回手动模式;然后 return。不会放宽 delta 去拉中危 kami 凑数。触发:修剪之后|参数TARGET_BATCH_MIN=6|来历小批量停采白付固定 gas;等积累后合批更省,现有 urgent 还能多采几分钟;拉中危凑数会过度停采,损失采集时长 - 按 delta 升序,最危险的进第一批 — 待停名单按 delta 从低到高排序,最危险的排进第一批优先处理。
recheck.validList.sort((a,b)=>a.delta-b.delta)。触发:第 5 步
链上预检
- 首次链上预检(不加锁) — 发 tx 前先查链上真实状态,剔除已死、已停、在黑名单、在其他地块的 kami;剔除后没有剩余就结束。
_emergencyPreCheck(stopList)返回 {validList,filteredCount}。罕见竞态下预检后只剩 1 只也允许单只停采(约 2.43M gas,保命优先)。触发:第 3 步 - 喂食后二次预检 — 喂食期间链上状态可能变了,用户也可能已经手动停采,所以发 tx 前再预检一次;没有剩余就打总耗时后结束。
_emergencyPreCheck(__stopListNoStarv);recheck.validList为空时打「重新预检后无需停采」和总耗时,然后 return。触发:第 4.6 步 - 停采黑名单 30 分钟到期自动解除 — 在停采黑名单里的 kami 满 30 分钟后自动移出黑名单,回到正常预检。对比
__stopBlockedTime里的加入时间,超过STOP_BLOCK_COOLDOWN_MS就从__stopBlockedKamis和__stopBlockedTime里删掉,并打「黑名单已过期」。触发:紧急停采首次预检和喂食后二次预检|参数STOP_BLOCK_COOLDOWN_MS=30 分钟|来历防止对停不掉的 kami 反复浪费 gas,同时不永久拉黑 - 黑名单中先探测,通过的并入主批 — 还在黑名单冷却期但有
harvestId的 kami,用estimateGas探测一次;通过就并入主停采批,加锁后跟大批一起发,不在这里单独发 tx。_preCheckStop([harvestId])结果 ok 时放进blacklistToRetry,最后合并回主清单并打「黑名单回流」日志;停成后由_emergencySendBatch的okList处理移出黑名单。触发:预检黑名单处理|来历预检发生在紧急锁之前,无锁发写 tx 有与普通操作撞 nonce 的窗口;并入主批还能省一笔单独 tx 的固定 gas(约 1.07M);不必干等冷却到期 - 黑名单预检仍失败或没有
harvestId就跳过 — 黑名单里探测仍失败,或者没有harvestId的 kami,本次跳过,日志显示还剩几分钟解除。blacklistSkipped++,计入filteredCount。触发:预检黑名单处理 - 黑名单跳过统计,全部被拦时提前返回 — 打印黑名单跳过了几只;如果名单全部被黑名单拦下,直接返回空结果。
itemsAfterBlacklist为空时返回 {validList:[],filteredCount:blacklistSkipped}。触发:黑名单处理之后 - 限流并发查链上状态,刷新
harvestId、地块和time.last— 限流并发地逐只查链上状态,顺带刷新harvestId、采集地块号和冷却所需的time.last。_emergencyRunPool并发 8;getByIndex(dbIndex,{harvest:true}),_emergencyGetState归一化成 harvesting / resting / dead / unknown;查询异常时 state='unknown'。触发:黑名单处理之后|参数EMERGENCY_CONFIG.CONFIRM_CONCURRENCY=8|来历限制并发,防止压垮查询接口;顺手取time.last不增加查询|版本v1.1.10 - 其他地块的 kami 过滤并醒目提示手动处理 — 正在其他地块采集的 kami 当前页面没法批量处理,过滤掉,用橙色加粗日志列出当前地块名和这些 kami 所在的地块,请用户手动处理。
myRoom取自__getMyRoomIndex();只有myRoom和harvestRoom都读得到且不相等时才过滤,任一读不到则放行。触发:状态分类时 - 查询异常时有
harvestId就保守放行 — 链上状态查不到(unknown)的 kami,只要有harvestId就留在停采名单里;没有harvestId的才过滤。r.state==='unknown' 分支。触发:状态分类时|来历宁可多发一次停采,也不漏掉危险的 kami - 已死、已停的过滤并打日志 — 已死亡的打「☠️ 已死亡,跳过」,已停采的打「💤 已停采,跳过」,都计入过滤数;状态是采集中但拿不到
harvestId的也被过滤,但不打日志。else 分支filteredCount++,只对 dead 和 resting 打日志;最后返回 {validList,filteredCount}。触发:状态分类时|来历防止把注定失败的成员混进批量交易,白付 gas 还拖累整批成功率 - 预检全程只读,不发写 tx — 预检运行在紧急锁设置之前,整个过程只查询、不发任何写 tx;黑名单里恢复的 kami 也只并入主批,由加锁后的主流程发送。本函数内只有
getByIndex和estimateGas预检,没有任何停采调用。触发:始终|来历延迟加锁设计的锁纪律,避免无锁写 tx 与普通操作撞 nonce - 并发查链上真实状态并分组 — 并发查询一批 kami 的链上状态,分成仍在采集(需要停)和已停止(不用发 tx)两组,同时用链上返回的
harvest.id刷新harvestId(本地库或 DOM 缓存可能过期)。名单为空直接返回两个空数组;走并发池,状态经_emergencyGetState归一,harvesting 进仍在采,其余进已停。读状态只查本地索引器,不花 gas。触发:紧急停采主流程发送前确认名单、发送后复核战果时|参数EMERGENCY_CONFIG.CONFIRM_CONCURRENCY=8 - 单只查询抛错按 unknown 归入已停一侧 — 单只查询失败时记为 unknown,不中断整批,并归入
alreadyStopped。发送前状态不明时宁可不发。注释说明,这和发送后复核「查不到保守算未停」方向相反:一个为省 gas,一个为保命。触发:_emergencyQueryStatus内|来历避免 RPC 抖动时对同一只 kami 重复烧 gas - 已停项挂
_resolvedState(防假记成功) — 归入已停的项目上挂一个_resolvedState字段,写明归一后的链上状态。调用方据此区分「确实读到了非 HARVESTING」和「查询抛错的 unknown」,只有前者才能记成功清计数。触发:_emergencyQueryStatus分组时|来历读取失败绝不能被当成已停,假记成功|版本1.1.21 轮末已停实锤记账 - 链上状态三态归一 — 把索引器返回的原始对象归一成 harvesting、resting、dead 三种。state 字段缺失或异常时,回退看 harvest 子对象:存在且没结束算 harvesting,已结束算 resting。state 转大写比较;全程可选链,传入 null 也不抛错。触发:被
_emergencyQueryStatus调用 - 判不出状态默认 resting — 完全判断不出状态时,默认按已停(resting)处理,上层就不会再对它发停采。触发:
_emergencyGetState兜底分支|来历避免对状态不明的 kami 重复发停采空烧 gas - 固定并发池(结果按输入顺序) — 以固定并发上限执行一批异步任务,results[i] 严格对应 items[i]。多个 worker 共用一个游标抢活,启动 min(并发数, 任务数) 个协程;单个任务抛错写入 {error} 占位,不会让整个
Promise.all失败。默认并发 6,实际调用处传CONFIRM_CONCURRENCY=8。触发:_emergencyQueryStatus调用|参数concurrency 默认6;CONFIRM_CONCURRENCY=8|来历调大更快但 RPC/索引器压力大,调小更温和但拖慢紧急响应
饿死 kami 先喂后停
- 等喂食通道排空再发停采(超时照发)测试线独有 — 如果另一条路径正在喂食(走 queue 通道),先等它排空再发停采批,最多等 120 秒;等超时也照发停采。每 500ms 轮询
window.__starvingFeedRunning;超时红字「漏停会被清算,代价高于撞号」;等了超过 1 秒才排空时打「通道干净,继续」。触发:加锁后|参数上限 =FEED_PHASE_CAP_MS(90000)+FEED_TAIL_GRACE_MS(30000)= 120s;轮询间隔 500ms|来历普通锁的粒度是整个batch_stop_deploy大块,喂食本身不单独持锁,所以 30 秒等待管不到正在飞的喂食 tx;超时照发是用户定的「停采侧宁发勿漏」|版本测试版1.2.45 - 饿死 kami 先喂食救援(喂食阶段有时限)测试线独有 — 预检后的名单里有饿死的 kami,就先批量喂食(按库存自动在 11 种回血食物中选),喂食排在停采前面,一秒不推迟;喂食阶段 90 秒封顶,喂不完的交给下一轮。
_starvingFeedKamis(starvingList,'紧急/饿死救援',{deadlineMs: 现在+FEED_PHASE_CAP_MS}),返回喂成数量。成功后日志写「直接进入停采流程」,但代码实际上 STARVING 随后会被剔出停采名单(见「只喂不停」)。触发:第 4.5 步|参数FEED_PHASE_CAP_MS=90000|来历0 血 kami 只有喂食能救(停采链上必 revert),用户 0825 定「饿死的不能等下一轮」;封的只是尾巴,换来后面停采一定有预算|版本测试版1.2.43 - 停采阶段独立计时测试线独有 — 停采阶段的 180 秒硬上限从喂食结束时开始算,喂食花多久都不会挤占停采预算;汇总里的「总耗时」仍从调用开始算。
__stopPhaseStart=Date.now()放在喂食之后;主循环硬上限和冷却收尾硬上限都拿它比。锁占用上界约 120(喂)+180(停)+93(冷却补停)≈393 秒,远低于紧急锁 600 秒超时自愈线(TX_EMERGENCY_TIMEOUT=600000;源码注释写 720s,按代码实际为 600 秒)。触发:喂食之后|参数INVOCATION_HARD_CAP_MS=180000|来历旧版喂食超过 180 秒时停采循环第一次判断就 break,一批 tx 都没发,日志只留一句「达到硬上限」看着像正常收尾,实际整轮停采被吞掉了|版本测试版1.2.43 - 饿死的只喂不停,喂活转健康的也不停 — 停采名单里剔除所有 STARVING,以及 delta>1(喂活后已高于停采线)的 kami;它们只走喂食救援,下一轮扫描时自然回到正常逻辑。
__stopListNoStarv=validList过滤掉isStarving以及 delta 为数字且 >1 的;剔除数量用橙色日志说明。触发:喂食之后、二次预检之前|来历0 血 kami 停采链上必 revert(0825 实测 10/10CALL_EXCEPTION);也化解了 1.2.23 互斥的副作用(跳过喂食时曾把「跳过」当「喂完」去停 0 血 kami,造成 16 批必败 tx)|版本1.2.26 / 1.2.27
参数与风险分级
- 紧急停采风险分级阈值 — 按 delta=实时HP%-停采线 分级排序:≤-20 极度危险最优先,≤-10 高危,≤0 刚触线。
EMERGENCY_CONFIG中定义,由紧急停采流程读取排序。触发:紧急停采排序时|参数CRITICAL_DELTA=-20;HIGH_RISK_DELTA=-10;DANGER_DELTA=0 - 预检/状态查询并发数 — 紧急停采预检与状态查询并发 8 路。调大更快但 RPC 压力大、可能被限流。触发:紧急停采查询时|参数
CONFIRM_CONCURRENCY=8 - 动态确认等待(基础+每批增量) — 确认等待 = 自适应基础等待 + 批次数×1.5 秒,随批次数放宽。
BASE_WAIT_MS=9000 仅在样本<5笔冷启动或计算异常时使用,平时由_adaptiveStopWaitMs()滚动 p90 决定。触发:紧急停采发送后等待确认|参数BASE_WAIT_MS=9000;PER_BATCH_WAIT_MS=1500|来历固定短超时会误判失败而重发(重发=双倍 gas);9 秒来自 0712 实测 tx 确认 p50≈5.8s+索引≈3.1s|版本1.2.4 - 重试轮数熔断与轮间隔 — 紧急停采最多重试 3 轮,轮间隔 3 秒。防止无限重试烧 gas。触发:紧急停采重试时|参数
MAX_ROUNDS=3;ROUND_INTERVAL_MS=3000 - 批间隔 500ms — 批次之间发送间隔 500ms。让钱包 nonce 排队稳定。触发:紧急停采分批发送时|参数
BATCH_INTERVAL_MS=500 - 混合批执行只数估算仅观测 — (
gasUsed−10万)/150万 ≈ 实际执行只数,仅用于日志展示。不参与判定。触发:混合批日志|参数GAS_PER_KAMI_ESTIMATE=1500000;GAS_REVERT_BASE=100000|版本1.1.12 - 同轮背靠背发送随机错峰 — 同一轮多批发送之间随机间隔 800~1500ms。随机抖动错峰。触发:紧急停采同轮多批发送|参数
SEND_STAGGER_MIN_MS=800;SEND_STAGGER_MAX_MS=1500|来历防 RPC 限流|版本1.1.12 - 紧急路径喂食阶段时间上限测试线独有 — 紧急停采里的饿死救援喂食最多 90 秒,api 补发段额外宽限 30 秒(总上限 120 秒),剩下的交下一轮。喂食失败不写
__starvingFedRecord,下轮必被重新捞起。1.2.46 量级修正:喂食单笔实测 722~2148ms,N 只约 N×2.4s,本上限很少触发,属安全阀不是常态闸。触发:紧急停采调用_starvingFeedKamis时传deadlineMs|参数FEED_PHASE_CAP_MS=90000;FEED_TAIL_GRACE_MS=30000|来历断网夜一次攒出十几二十只 STARVING 时喂食能吃光 180 秒预算,停采循环第一轮都进不去、一只都没停成;90 秒保证至少约 9 只能喂到|版本测试版1.2.43 - 连续全 revert 批熔断 — 连续 2 批「全 revert /
estimateGas全部不可停」→ 本次调用熔断,不再发新 tx。配合__stopConsecutiveRevertBatches/__stopCircuitBroken状态变量,由紧急停采主循环短路后续发送。触发:紧急停采调用内|参数CIRCUIT_BREAK_STREAK=2|来历防对卡链 kami 批量白烧 gas|版本1.1.12 - 索引器滞后诊断自适应轮询仅观测 — 按 3/6/12/24/45 秒间隔轮询测索引器滞后,上限 90 秒,超时只警告。纯观测,不改判。触发:紧急停采确认阶段|参数
INDEX_LAG_POLL_MS=[3000,6000,12000,24000,45000];INDEX_LAG_POLL_CAP_MS=90000|版本1.1.12 - gas 判级:像全执行阈值 — 每只均摊 gas ≥120万 → 判「像全执行」,但只作观测,交由下轮 state 复读确认;低于则落进 mixed 档改走逐只
estimateGas裁决。_allowFailureStop与批量判级共用同一常量,单点生效。注释 1.1.12 段仍写「gas 是唯一不受索引器滞后影响的真值」,但同常量注释与退避复读 I3 红线已改为 gas 永不作成功凭据。触发:停采批回执返回后判级|参数GAS_FULL_EXEC_PER_KAMI=1200000|来历0709 grok+Claude 交叉审:旧阈值 80 万在「3只真执行+3只真revert」混合批均摊≈90万时被误判全执行,真失败被漏掉;0709 夜 47 批满批误判+140 次误拉黑+约 87M gas 空烧|版本1.1.14 - gas 判级:全 revert 阈值 — 每只均摊 gas ≤30万 → 判「全 revert」,转
estimateGas逐只裁决。必须盖住单只调用固定 revert 成本≈26.1万(单笔固定开销全摊在一只身上)。触发:停采批回执返回后判级|参数GAS_FULL_REVERT_PER_KAMI=300000|来历阈值过低会让单只重试的真 revert 被误判为混合,多打冗余estimateGas并让诊断标签失真|版本1.1.14
多轮停采主循环
- 多轮停采,最多 3 轮 — 每轮发送、确认、复读状态,没停的进入下一轮,最多 3 轮;两轮之间隔 3 秒。while(
remaining.length>0 && round≤MAX_ROUNDS);还有剩余且未到上限时delay(ROUND_INTERVAL_MS)。触发:第 5 步|参数MAX_ROUNDS=3;ROUND_INTERVAL_MS=3000|来历熔断,防止无限重试烧 gas - 停采阶段 180 秒硬上限测试线独有 — 每轮开始前检查停采阶段已用的时间,超过 180 秒就强制收尾,剩下的交给下一次调用(测试版 1.2.59 起:不在退避队列里的交残留补停,约 1 分钟后补)。这条上限只在每轮发送之前判;整批失败后降级的「逐只单发」循环有意不按它截断(测试版 1.2.60 验证过:慢 RPC 下两次整批失败加两道逐只 estimateGas 门禁就要约 285 秒,截断会让单发一只都发不出去,而那种时候只有单发能把 kami 停下来)。
Date.now()−__stopPhaseStart>INVOCATION_HARD_CAP_MS时红字日志后 break,让 finally 释放紧急锁。1.2.43 起从「喂食结束」起算(见__stopPhaseStart)。触发:紧急停采调用内计时|参数INVOCATION_HARD_CAP_MS=180000|来历防止单次调用长期占锁|版本1.1.12 / 测试版1.2.43 - 熔断生效时不再发新一轮 — 本次调用中连续多批全部 revert 触发熔断后,不再开新一轮。
__stopCircuitBroken为真时红字日志后 break(熔断在确认阶段连续CIRCUIT_BREAK_STREAK=2 批全 revert 时置位)。触发:每轮开始前|参数CIRCUIT_BREAK_STREAK=2|版本1.1.12 - 本次已判定卡链的 kami 不再发 tx — 本次调用内已经判定为疑似卡链的 kami,从后续任何一批里剔除,交给黑名单机制处理。remaining 过滤掉
kamiId在__stopStuckThisInvocation里的;过滤后为空就 break。触发:每轮开始前|版本1.1.12 - 第 2 轮起发送前过
estimateGas门禁 — 从第 2 轮开始,发送前先用estimateGas过一遍,通过的才真正发送;不通过的按「已停 / 冷却 / 卡链」三类记账,不盲发。第 1 轮数据刚预检过,够新鲜,跳过门禁省一轮 RPC。_emergencyGateByEstimate(remaining)分出passItems和blockedItems,blocked 的交_emergencyCreditBlocked;全部被门禁判定完则 break。触发:round>1 时|来历旧代码直接拿 remaining 重新组批盲发,等于对其实已停、在冷却、真卡链的 kami 再发一次 tx|版本1.1.14 - 发送阶段:本轮各批连续发出,不等确认 — 本轮所有批一批接一批只广播不等确认,批与批之间隔 800~1500 毫秒随机时间。
chunkRandom(remaining,6,10);逐批_emergencySendOnly(batch)收集到sentBatches;最后一批之后不等。触发:每轮|参数SEND_STAGGER_MIN_MS=800,SEND_STAGGER_MAX_MS=1500;每批 6~10 只|来历发送和确认解耦,不再发一批等一批;随机间隔防 RPC 限流;小批加随机批量能降低 gas 飙升时整批被拒的连带损失|版本1.1.12 - 发送途中熔断则跳过后续批 — 发送过程中一旦熔断被触发,剩下的批不再发出。每批发送前检查
__stopCircuitBroken,为真就打「熔断中,跳过批 i」后 break。触发:发送循环中|版本1.1.12 - 每批按危险程度加图标仅观测 — 发送日志按该批最低 delta 标上 🔴/🟠/🟡/⚪,并列出批次序号、数量和编号。
minDelta依次与 CRITICAL /HIGH_RISK/ DANGER 阈值比较。触发:每批发送时 - 确认阶段:所有批并行确认 — 已发送的各批同时等待上链确认、按 gas 判级和
estimateGas裁决;某批确认抛异常时按失败处理,不影响其他批。Promise.allSettled(sentBatches.map(_emergencyConfirmBatch));rejected 的转成 {success:false, error, items}。触发:发送阶段之后|版本1.1.12 - 收集各批因冷却没停成的 kami — 确认结果里因操作冷却没停成的 kami,按
dbIndex去重放进冷却推迟队列,轮次结束后统一重试。result.cooldownList逐只cooldownDeferred.set(dbIndex, cd)。触发:逐批处理确认结果时 - 首批强化:部分失败先批量重试再逐只单发 — 第一批装的是最危险的 kami;这批上链了但有失败的,立刻过门禁批量重试,仍没停的再逐只单发。i===0 且
result.success且failCount>0 时,调_emergencyRetryBatchGated(batch,'首批强化'),retryItems不为空再调_emergencySingleRetryGated。其他批的失败留给下一轮。触发:逐批处理结果(只针对第一批)|来历第一批装的是最危险的 kami,有失败要立刻重试|版本1.1.12 - 首批整批失败只重试高危 — 第一批整批没上链时,只挑高危(delta ≤ -10)的做门禁批量重试,仍失败再逐只单发;低危的留给下一轮合批。
highRiskBatch= batch 过滤 delta ≤HIGH_RISK_DELTA;retryResult不成功或有失败时单发。触发:第一批result.success为假时|参数HIGH_RISK_DELTA=-10|来历整批没上链多半是 gas 问题,低危的留给下一轮合批更省|版本1.1.12 - 动态等待(自适应 p90 + 每批 1.5 秒) — 每轮复读状态前的等待时间 = 最近停采 tx 确认耗时的滚动 p90 + 批数 × 1.5 秒;日志显示 p90 秒数和样本数。
_adaptiveStopWaitInfo():取最近 30 笔确认耗时的 p90 加 3 秒索引余量,限制在 8~30 秒;样本不足 5 笔或计算异常时退回BASE_WAIT_MS=9000。只调整等待时长,不改任何判定或重发逻辑。触发:每轮确认处理之后|参数PER_BATCH_WAIT_MS=1500;BASE_WAIT_MS=9000(兜底);限制在 8~30s|来历用自适应值替代静态基础等待;9 秒兜底值来自 0712 实测 tx 确认 p50≈5.8s + 索引≈3.1s|版本1.2.4 - 轮末复读状态并给已停的记账 — 等待后查链上真实状态;确认已停的记入操作账,并清掉失败计数、加入成功集。
_emergencyQueryStatus(remaining)返回stillHarvesting和alreadyStopped;已停的调recordAction(imgNumber,'stopHarvest');_resolvedState存在且不是 unknown 时才_stopCreditSuccess,查询抛错(unknown)绝不据此清计数。触发:每轮等待之后|来历避免真已停的 kami 因残留失败计数被误拉黑|版本1.1.21 - 用成功集剔除索引器滞后项 — 索引器读数说「仍在采」,但本次调用中已被 gas 判级或
estimateGas实锤停成的 kami,不进下一轮。_stopFilterConfirmed(stillHarvesting)剔除__stopConfirmedThisInvocation里的kamiId,结果作为新的 remaining。触发:每轮复读之后|来历本地索引器读数可能落后于已实锤停成的结果|版本1.1.14 - 每轮结果日志和仍在采名单仅观测 — 每轮打印已停数和仍在采数(索引器读数);剔除成功集后仍在采的不超过 20 只时逐个列出编号。超过 20 只不列。触发:每轮复读之后|参数列出上限 20|来历方便排查
发送段
- 停采发送与确认拆成两段(背靠背发送、并行确认) — 原来的一条龙流程是:发交易、等回执、
delay(2000)、再用索引器复核。现在拆成两段:_emergencySendOnly只管把交易广播出去,_emergencyConfirmBatch负责等回执并裁决。主流程先把本轮所有批次一口气发完,再一起确认。首批强化、cooldown 补停重试这类零散调用点继续用厚包装_emergencySendBatch(内部就是发送加确认各跑一次),返回值形状和 v1.1.11 一样,调用方不用改。触发:被emergencyStopHarvest主流程及各重试路径调用|来历0709 夜 42-agent 审计:交易其实成功了,但旧码tx.wait后只等 2 秒就去问滞后 17~99 秒的索引器,整批被误判为真失败,随后触发重试风暴,每只约 261k gas 白白烧掉|版本1.1.12 - 失败计数每次调用每只最多记一次 — 停采的成功和失败都统一走
_stopCreditSuccess/_stopCreditFail记账,同一次紧急停采调用里,每只 kami 最多记 1 次失败。要连续好几次独立调用都判失败才会被拉黑。拉黑阈值是连续 3 次独立调用都判失败。触发:各裁决路径记账时|参数STOP_BLOCK_THRESHOLD=3|来历旧版失败计数只增不清,一次调用里被多条路径重复计数,直接穿透拉黑阈值(Change D)|版本1.1.12 - 发送前尽力读 nonce(诊断用)仅观测 — 原始签名器通道发交易前读一次当前 nonce,只写进发送日志,方便排查 nonce 冲突。先试
signer.getNonce(ethers v6),再试getTransactionCount(v5);读不到或抛错就静默返回 null,不影响发送。触发:raw 通道每批发送前 api.stop回退通道_apiStopBatchFallback— 底层合约句柄或签名器不可用时,改走游戏自带的api.player.pet.harvest.stop批量停采。整批同成同败,没有「坏的自动跳过」能力;api 不存在返回失败「API不可用」;返回空视为失败「API返回空」;成功打印耗时和 tx hash 前 10 位;异常打日志(消息截 50 字)后返回失败。触发:发送阶段判useFallback时,由确认阶段调用|来历只在AllowFailure依赖缺失时才用- 发送阶段:无
harvestId拦截 — 这一批过滤后一个harvestId都没有时,不发送,直接返回错误「无harvestId」。确认阶段看到后原样返回失败。触发:_emergencySendOnly入口 - 发送守卫补 .target 检查(防 to=undefined 发交易)测试线独有 — 停采合约句柄缺 interface、缺 target、或者没有 signer,任一情况都不发,改走
api.stop回退。触发:_emergencySendOnly发送前|来历0911 审计:旧守卫只查 interface。句柄有 interface 却没有 target 时守卫会放行,发出一笔 to=undefined 的交易,等于部署合约|版本测试版1.2.35 - MUD 队列只入队不 await(保住背靠背发送) — mud 通道调
executeBatchedAllowFailure拿到 promise 就立即返回,不等上链。真正的错误处理放到确认阶段 await 时再做。拿到 promise 后立刻挂一个空 catch,防止悬空期间出现 unhandled rejection;返回 {tx:null,txPromise}。触发:mud 通道发送时|来历MUD 队列 promise 可能要等上链才 resolve(链上 RPC 是eth_sendRawTransactionSync)。如果 await,多批背靠背广播会退化成串行等上链,每批多 2~6 秒|版本1.1.19 停采通道统一·背靠背修正 - 发送段耗时日志(D1)仅观测 — mud 通道入队后打印「已入队(nonce统一) 耗时Xms」,用来证明发送段很快、背靠背成立。触发:mud 通道入队后|版本1.1.19 停采通道统一 D1
- gas 真值账本记账 hook(紧急停采)仅观测 — 两个通道发出停采交易后,都按动作 stop 把这批
harvestId和 tx(或txPromise)记进 gas 账本,后台再按 hash 补回执统计真实 gas。mud 分支传的是没 await 的 promise(fire-and-forget 抓 hash),raw 分支传 tx 对象。触发:每批紧急停采发送后|来历余额差统计不准(混入买道具和充值),改为发交易时按动作打标签记账|版本1.2.7 gas真值账本 - 原始签名器发送路径(raw) — 默认通道。把
harvestId转成BigInt,用合约 interface 编码executeBatchedAllowFailure的 calldata,经signer.sendTransaction发往system.target。发送前读 nonce 打日志;返回空 tx 记错误「返回空Tx」;抛错时把消息放进 error,交确认阶段分类(发送函数本身不抛)。触发:通道为 raw 或 mud 跌落时|来历AllowFailure容错批量:坏的那只自动跳过,其余照停 - 发送失败分类日志(交易未上链) — 发送阶段就出错时,按错误文本分四类打日志:timeout/not mined(交易可能已发出)、nonce/sequence mismatch(Nonce 冲突,未发出)、RPC/network(网络错误)、其他(截 80 字)。返回里带
isTimeout标记。只有 tx 和txPromise都没有时才走这里;mud 成功入队(tx=null 但有txPromise)不走。触发:确认阶段入口 - 单批厚包装
_emergencySendBatch(带熔断短路) — 对一批 kami 连着做一次发送加确认,返回值形状和 v1.1.11 一致。熔断生效时不发送,直接返回 {error:'熔断',circuitBroken:true},并打「跳过本批发送,交下一轮对账」。触发:紧急停采主流程里的冷却等待危险批、cooldown 补停重试等零散调用点|版本1.1.12
确认段与 gas 判级
- 停采回执形状归一
_awaitStopReceipt— 队列 resolve 出来的东西可能是带wait()的交易对象,也可能直接就是回执。这个函数把两种统一成 {receipt, hash, shape}。有 wait 函数就调wait(),shape=tx;有gasUsed、status 或transactionHash字段就直接当回执,shape=receipt;空值记 shape=null;都认不出记 unknown。wait()真失败(revert 等)原样抛给调用方。raw 分支的行为与旧版逐字节等价。触发:_emergencyConfirmBatch确认每批时|来历0710 停采事故:MUD 队列等上链约 9528ms 才 resolve,返回的对象没有 .wait,旧码写死tx.wait(),结果报了假失败|版本1.1.21 停采回执适配 - 未知回执形状只打印一次字段名仅观测 — 第一次遇到认不出的回执形状时,把它的 keys 打印到日志(截 200 字符),方便以后适配。之后不再重复打印。用模块级标记
__stopShapeDumped控制只打一次。触发:_awaitStopReceipt遇到未知形状时|版本1.1.21 停采回执适配 txPromise解析耗时计时(D2)仅观测 — 确认阶段 await 队列 promise 时计时并打日志。参考线:≤300ms 大约是拿到 hash 就返回,≥2000ms 大约是等上链后才返回。用首夜实盘数据裁决 resolve 语义这个悬案。await 本身不变,只在前后计时。触发:mud 通道确认时|版本1.1.19 停采通道统一 D2- 回执缺 gas / 形状未知 →
pendingVerify(I2 最高红线) — 回执形状 unknown、没有回执、或gasUsed缺失时,本批不计成功也不计失败、不重发、绝不当成全 revert。成功与否交给之后复读 state≠HARVESTING 来判定。连续 revert 批计数清零;pendingVerify批数+1;全员入退避复读队列(gasLikely=false);后台起索引滞后诊断;返回okCount:0failCount:0pendingVerify:true。触发:确认阶段拿到回执后|来历旧码gasUsed缺失时按 0 处理,0 小于阈值,于是被判成全 revert,这是事故式假失败的根因之一|版本1.1.21 停采回执适配 - tx 执行失败判定(status 缺失不算失败) — 只有回执的 status 明确读到、而且不等于 1,才判整批「上链但执行失败」(全部未停采)。status 缺失不当失败。raw 回执的 status 恒为 0 或 1,行为和旧版等价。触发:确认阶段 gas 判级前
- 每批确认耗时与 gas 均摊日志仅观测 — 每批打印一行:tx 确认耗时、hash 前 10 位、
gasUsed总量、每只均摊 gas。触发:每批回执确认后 - gas 三级判级(
full_exec双条件) — 按每只均摊 gas 把一批分三级。每只 ≥1.2M 且估算执行只数 ≥ 全员 → 像全执行(full_exec);每只 ≤300k → 全 revert;其余 → 混合(mixed)。估算执行只数 = max(0, round((gasUsed−100000)/1500000))。这个估算原来只在 mixed 分支算来给日志看,现在提前到判级之前当第二个条件。阈值本身不变,靠第二维堵漏。触发:确认阶段拿到有效 gas 后|参数GAS_FULL_EXEC_PER_KAMI=1200000;GAS_FULL_REVERT_PER_KAMI=300000;GAS_PER_KAMI_ESTIMATE=1500000;GAS_REVERT_BASE=100000|来历只看均摊会被「少败多成」的混合批骗过(比如 9 成执行、1 只 revert,均摊约 1.41M,仍大于 1.2M),误判全执行,真失败就漏掉了。阈值 v1.1.14 从 800k 收紧:之前一批 0.9005M 的混合批曾被判成全执行。300k 是为了盖住单只 revert 固定约 261k 的情况|版本1.1.14 停采闭环边角 修复1 full_exec不作停成凭据 →pendingVerify+ 打gasLikely— 判「像全执行」时,gas 只作观测:本批不计成功不计失败,成功交下轮复读 state≠HARVESTING 判定。每只打上_gasLikelyExecuted标记,入退避复读队列(gasLikely=true)。以后这只如果因为索引滞后导致estimateGasrevert,分类器会 defer,不当场记失败。连续 revert 批计数清零、pendingVerify批数+1、后台起索引滞后诊断,返回okCount:0failCount:0。注释头写「≥GAS_FULL_EXEC_PER_KAMI即像全执行」,代码实际还要求估算执行只数≥全员。触发:gas 判级为full_exec时|参数GAS_FULL_EXEC_PER_KAMI=1200000|来历Bug B 修复前,full_exec直接给整批记成功,名单剔除完全由 gas 驱动。gas 不可作为停成凭据,所以改了|版本1.1.22 退避复读 C1+C2pendingVerify批数统计(C4)仅观测 — 统计本次调用里产生pendingVerify的批数(full_exec批和缺 gas 批),供完成小结显示。__pendingVerifyBatchCount++,每次调用开头清零。触发:判pendingVerify时|版本1.1.17 可观测性批次 C4- mixed / 全 revert 批逐只
estimateGas裁决 — 混合批先打日志(估算执行≈N 只 < 全员),全 revert 批打红色日志。两种都逐只过estimateGas门禁:通过的算「仍可停但没停成」;未通过的交分类器记账。触发:gas 判级为 mixed 或full_revert时|版本1.1.12 - 连续空转熔断 — 一批判为全 revert,或者
estimateGas裁决后全员都「仍可停但没停成」(这批 tx 对它们完全没生效),连续空转批数+1。连续 2 批就置位熔断:本次调用不再发任何新交易,交给下一轮对账。熔断时打红色粗体日志;其他情况把计数清零(full_exec批和缺 gas 批也清零)。触发:确认阶段逐只裁决后|参数CIRCUIT_BREAK_STREAK=2|来历防止空转批一批接一批白烧 gas|版本1.1.12 Change D - 没停成的成员分流:冷却推迟 vs 真失败 —
estimateGas仍能通过却没停成的 kami 分两类:冷却中的推迟处理、不计失败;真失败的调_stopCreditFail记失败计数。判冷却:time.last公式剩余秒数>0,或 DOM 主按钮 disabled(两个条件是或的关系)。注释说明 DOM 判据实盘恒为 false,只作兜底。触发:确认阶段 mixed / 全revert 路径|参数KAMI_ACTION_COOLDOWN_SEC=180|来历0708 实测定案:胶水和自身操作都会刷新time.last,DOM 判据在实盘已经失效|版本v1.1.10 起 - 批次战果汇总日志仅观测 — 每批打印一行:本批 N 个,成功几只、cooldown 推迟几只(附编号)、真失败几只(附编号)。触发:确认阶段每批末尾
- 确认阶段异常:中性文案、不记失败 —
txPromise解析或回执归一时抛错,日志写「tx 已入队但确认阶段出错,成员状态交 state 复读裁决」。本批不记失败,成员留给轮末 state 复读定夺。返回 success:false,带isTimeout。触发:确认阶段 try 块抛错时|来历走到这里的交易其实已经入队,不能再写「发送失败/未发出/未停采」误导人(codex 审查 Q5#5)|版本1.1.21 确认异常文案区分
estimateGas 门禁与失败分类
estimateGas门禁(发前预演) — 重试或单发之前,先对每只 kami 用estimateGas预演一次停采。能通过的才放行发交易;不通过的交给分类器,判断是已经停了还是卡链。没有harvestId的直接丢弃,不进任何一组;逐只串行调_preCheckStop([harvestId]);返回 {passItems,blockedItems}。触发:_emergencyConfirmBatch(mixed/全revert 时)、批量重试、单只重试调用|来历不放任对停不了的 kami 发 tx 白烧 gas|版本1.1.12- 已知卡链短路(本次调用不再碰它) — 本次调用里已经判过「疑似卡链」的 kami,门禁和分类器都直接把它归到未通过或卡链,不再调
estimateGas,更不会再发交易。查__stopStuckThisInvocation集合,日志标注「已知卡链」或「已知」。触发:门禁与分类器逐只处理时|来历省一次 RPC,也避免同一次调用里反复白烧|版本1.1.12 estimateGasrevert 原因观测(C6)仅观测 —estimateGas没通过时,把 revert 原因串打印一次,不改变判定。同一次调用里相同的原因串只打一次,用__stopRevertReasonSeen去重(每次调用开头清空);提取过程出错静默。触发:门禁判未通过时|来历想确认「因为已经停了而 revert」有没有可辨认的明文原因(部署 revert 就带 kami not RESTING)。如果有,下版可以升级成绕开索引器滞后的链上实时判据|版本1.1.22 revert原因观测- 裁决名单日志超长截断仅观测 — 门禁、分类器和索引滞后超时的日志里,名单超过 8 只时只显示总数加前 5 只样例,防止刷屏。fmt 辅助函数:长度>8 输出「N只(样例: 前5个...)」。触发:打印裁决日志时
- 分类器:已停实锤(不拉黑) —
estimateGasrevert 后直接读链上 state。不是 HARVESTING 就判「已停实锤」(只是索引器还没追上),记成功、清失败计数、绝不拉黑。调explorer.kamis.getByIndex(dbIndex,{harvest:true}),state 转大写后比较。拉黑机制本身保留:真卡链的 kami 可能卡好几天,每次停采都必然 revert,不能无限重试。触发:_emergencyCreditBlocked调用|来历0709 事故的问题不是「拉黑该取消」,而是「抓错人」:索引器滞后、其实已经停成的健康 kami 被误判成卡链,拉黑了 140 次|版本1.1.12 - 分类器:冷却分支(不计失败、转补停) — 链上仍在 HARVESTING,但 180 秒操作冷却还没到期,就归为 cooldown。不计失败、不拉黑,转进本次调用的冷却待补停队列。冷却按
time.last公式计算:优先用本次getByIndex刚读到的harvest.time.last,读不到才用 item 上缓存的旧值,不多发一次查询。触发:分类器逐只判定时|参数KAMI_ACTION_COOLDOWN_SEC=180|来历旧版只分两类,会把冷却期内的estimateGasrevert 连带判成疑似卡链,记失败甚至拉黑|版本1.1.14 停采闭环ABCD 改动D - 分类器:前端冻结门闩 — 冷却已过期但
estimateGas仍然 revert 时,如果此刻判定前端冻结,就归为 unknown:不判卡链、不计数、不拉黑。调_isFrontendFrozen(),为真就 push 进 unknown,日志标注frontendFrozen。触发:分类器冷却已过期分支|来历前端冻结时读到的是冻住的旧值。Bug B 修复前,这种情况仍会判疑似卡链,后续可能计数或拉黑 - 分类器:退避门禁(疑似卡链不当场计数) — 「revert + 仍在 HARVESTING + 冷却已过期」算疑似卡链,但不当场记失败。要先过
_stopBackoffGate:只有同时满足退避复读表走完、过了证据窗、没有群体滞后冻结、最后一次复读仍是 HARVESTING,才真判 stuck(记一次可疑计数,并加入本次调用的卡链集合)。否则 defer,进退避复读队列,本次不计失败。_stopBackoffGate返回 'count' 才判 stuck。退避表见STOP_BACKOFF_TABLE_MS。触发:分类器冷却已过期且未冻结时|参数证据窗 普通300s /gasLikely30min;STOP_BACKOFF_TABLE_MS=[7000,20000,45000,90000,180000,300000]|来历防止把索引滞后、其实已停的 kami 冤枉计数。真卡链最早约 15 分钟后仍会经拉黑通路落网(I2 保留)|版本1.1.22 退避复读 C1+C3 - defer 日志措辞按
gasLikely反转仅观测 — 进了退避队列的 kami,如果之前那批 gas 曾达到真执行水平,日志写「疑似已停/索引滞后」;否则写「退避表未走完 t+Xs/第N档」。读__stopPendingVerify条目里的gasLikely、sentAt、attempts。触发:分类器 defer 时|版本1.1.22 退避复读 C1 - 分类器:状态查询失败保守保留(unknown) —
estimateGas已经失败,紧接着链上状态查询又抛错(两次抖动叠在一起),就归为 unknown,只打日志不记账:不写成功集、不拉黑、不转冷却队列,留在待停名单里交下轮重试。在 catch 分支 push 进 unknown。触发:分类器getByIndex抛错时|来历旧版在 catch 里把它当「已停」写进成功集,于是被剔出重试名单,而它可能还在采。紧急停采时漏停的代价远大于多查一次 RPC|版本1.1.14 停采闭环边角 修复2 - 分类器五类汇总日志仅观测 — 分类结果按类各打一行日志:✅已停实锤、🧊冷却待补停、⚠️疑似卡链记一次、已停用defer 交退避复读、⚠️双失败/前端失真保守保留。每类非空才打印。触发:分类器结束时
- 未通过项分类记账
_emergencyCreditBlocked— 按分类结果记账。stopped 记成功并清计数;stuck 记失败,原因写「疑似卡链(estimateGasrevert+state仍HARVESTING)」;cooldown 汇入模块级冷却待补停队列,交本轮末统一补停;unknown 和deferBackoff不记账。名单为空直接返回。cooldown 队列用 Map,以dbIndex为键去重,和emergencyStopHarvest里的局部cooldownDeferred引用同一个对象;转入后打一行「N只转入cooldown待补停」。触发:确认阶段、批量重试、单只重试调用|版本1.1.14 改动D / 1.1.22 退避复读 - 后台索引器滞后诊断
_emergencyDiagIndexLag仅观测 — 某批判成pendingVerify后,在后台(不阻塞主流程)自适应轮询本地索引器,记录每只 kami 从发出到索引器显示非 HARVESTING 用了多少秒。轮询间隔依次 3/6/12/24/45 秒(超出后一直用最后一档),总时长上限 90 秒;查询抛错当作仍在滞后;超时仍是 HARVESTING 只打警告(「未被 state 复读确认停成,保留到下轮重试」),不改判;异步异常会被 catch 打日志。gas 不再作为停成凭据。触发:确认阶段判full_exec或回执缺 gas 时|参数INDEX_LAG_POLL_MS=[3000,6000,12000,24000,45000];INDEX_LAG_POLL_CAP_MS=90000|版本1.1.12 - 停采时延样本收集(供完成小结)仅观测 — 把每只 kami 的索引器滞后毫秒数,以及每批交易的确认耗时收集起来,供紧急停采完成小结算中位数和最慢值。推入
__stopIndexLagMsList和__stopTxConfirmMsList,写入失败静默。触发:索引滞后诊断翻状态时、每批回执确认后|版本1.1.22 退避复读 C4 - 确认耗时写入滚动 p90 自适应窗口 — 每批停采的确认耗时同时写进一个滚动 p90 窗口(跨会话持久化),部署侧据此自适应决定停采后要等多久。调
_recordTxConfirmMs(confirmMs),异常静默。触发:每批停采回执确认后|存储(由_recordTxConfirmMs持久化,键不在本段)|来历用滚动 p90 替代静态等待 5 秒,随网络状况自动调整|版本1.2.4 部署防重发门禁 D4v2 - 重试候选链上刷新
_emergencyRefreshCandidates— 重试前逐只直读链上:已经不是 HARVESTING 的直接记成功(清计数),不再重试;仍在采的换上链上最新的harvestId和time.last,再进入候选。查询抛错时保守保留旧 item(前提是它有harvestId)。代码这里用 res?.state !== 'HARVESTING' 严格比较,没有转大写,和分类器不同。触发:批量重试、单只重试调用 - 批量重试闸门
_emergencyRetryBatchGated(显式熔断检查) — 没停成的批次重试流程:先刷新候选,再过estimateGas门禁,未通过的分类记账,通过的一次性批量重发并确认。入口先查熔断,生效时打日志、返回空结果,不发送。触发:紧急停采主流程「首批强化」「整批失败重试」|来历这个函数直接调发送函数,不经过带熔断的厚包装。必须在这里补一道,否则熔断刚在本轮确认阶段触发时,首批强化或整批失败重试仍会漏网发送|版本1.1.12 - 单只降级重试
_emergencySingleRetryGated— 批量重试后仍失败的逐只单发:同样先刷新候选、过estimateGas门禁,再逐只调_allowFailureStop。返回 true 记成功(state 复读确认),false 记失败,null(pendingVerify)入退避复读队列(gasLikely=true)。入口先查熔断;日志带 Δ 值(保留两位小数);每只之间隔 300ms。触发:「首批强化单发」「整批失败单发」|来历熔断期间不能逐只漏网发送。Bug B 修复前,_allowFailureStop只要 gas 判full_exec就直接记成功|版本1.1.22 退避复读 C1/C2
冷却处理
- 操作冷却公式
_cooldownRemainSec— kami 任意操作后有约 3 分钟操作冷却,冷却期内停采/喂食/部署 TX 必败;按harvest.time.last+ 180 秒算冷却剩余秒数。time.last经本地 ECSgetByIndex(idx,{harvest:true})零 gas 读取,RESTING 状态同样有效;读不到(null/0/非正数)按无冷却返回 0,保持旧行为交下游失败分类兜底。触发:被部署/停采/喂食的冷却预筛调用|参数KAMI_ACTION_COOLDOWN_SEC=180|来历2026-07-08 冷却判定探针实测:T 夹逼区间 (157,189],取游戏 3 分钟常量,269 只样本混淆矩阵零错配(含「胶水」攻击导致的冷却)|版本v1.1.10 - 组批前冷却预筛,归入冷却推迟队列 — 还在 180 秒冷却里的 kami 不进发送批,直接放进冷却推迟队列,等轮次结束后统一处理。用预检时顺带取到的
timeLast算_cooldownRemainSec;remain>0 逐只打日志并按dbIndex放入cooldownDeferred(即模块级__stopCooldownDeferredThisInvocation,classify 阶段发现的冷却 kami 也汇入同一个队列);timeLast读不到时 remain=0,回到原有行为。触发:组批前|参数冷却 180s|来历发了也必败,省一次注定失败的 tx|版本v1.1.10 / 1.1.14 - 冷却收尾同样受 180 秒硬上限约束测试线独有 — 轮次结束后如果停采阶段已超过 180 秒,放弃冷却收尾,冷却队列交给下一轮(测试版 1.2.59 起其中危险的交残留补停,冷却完就补),直接去 finally 释放锁。
cooldownDeferred.size>0 且已超时时打红字(同时显示停采阶段耗时和调用总耗时)。触发:所有轮次结束后|参数INVOCATION_HARD_CAP_MS=180000|来历冷却定时补停最长可等 93 秒,叠加前面多轮耗时可能整体超时|版本1.1.12 / 测试版1.2.43 - 冷却收尾与 remaining 解耦 — 冷却队列里的 kami 总是全量处理(只要求有
harvestId),不再要求它们出现在 remaining 里、或 remaining 已清空。是否还需要处理,由后面逐只实时复读链上状态来判断(state 不是 HARVESTING 就跳过)。触发:冷却收尾|来历冷却预筛出的成员从来没进过 remaining,旧条件永远命中不了;只要还有别的 kami 没停,危险冷却 kami 的保命补停就会被整批跳过|版本1.1.14 - 冷却收尾前先剔除成功集 — 冷却队列里本次已实锤停成的 kami 先剔掉,不再为它们查链上状态。
_stopFilterConfirmed(cdListRaw,'cooldown收尾扫描前'),只是过滤,不改判定语义。触发:冷却收尾开始|来历省掉对已确认成功的 kami 的无谓 RPC|版本1.1.14 - 冷却队列按实时状态分三路 — 逐只重新查链上状态:已不在采集的跳过;冷却已结束的立即重试;危险且冷却剩余不超过 90 秒的定时等待后补停;其余留到下一轮。
getByIndex同时刷新harvestId和time.last重算 remain;「危险」=isStarving或 delta ≤ -1;查询失败按无冷却处理,放进立即重试。触发:冷却收尾|参数危险冷却等待线 90s;EMERGENCY_DANGER_DELTA=-1|来历不在本次紧急停采里白等非紧急的冷却 kami|版本v1.1.10 - 非紧急冷却留到下一轮的日志仅观测 — 列出仍在冷却(剩 >90 秒或非危险)、留到下一轮处理的 kami 及剩余秒数;测试版 1.2.59 起其中危险的交残留补停(冷却完 +5 秒补),日志写明。
cdDeferNext非空时打印。触发:冷却分流之后|版本v1.1.10 - 危险 kami 冷却定时补停(加随机缓冲,封顶 93 秒) — 危险且冷却剩余不超过 90 秒的 kami,取其中最长剩余时间,加 1~3 秒随机缓冲统一等待,到点后一次性批量补停。
waitMs= min(最大 remain×1000 + 1000~3000ms 随机, 93000);然后_emergencySendBatch。触发:cdWaitDanger非空|参数抖动 1~3s;封顶 93000ms(随缓冲上限从 92000 同步调整)|来历缓冲防止卡在 180 秒冷却结束的边界上又失败一次,也错开多只同时解除;封顶是防呆,避免计算异常导致长时间占锁|版本v1.1.10 - 定时补停后等 2.5 秒复核战果 — 批量补停上链后等 2.5 秒逐只复核,仍在采集或查询失败的进入单发降级;批量本身失败则全部转单发。复核前先用成功集剔除,已实锤停成的跳过复核 RPC。触发:定时补停批发送后|参数
delay(2500)|来历省掉对已确认成功的 kami 的无谓getByIndex|版本1.1.14 - 熔断时跳过冷却补停的单发 — 熔断已生效时,定时补停和冷却重试后剩下的都不再逐只单发。
waitStillFail或stillFail非空且__stopCircuitBroken时打「跳过单发」。触发:冷却收尾单发前|版本1.1.12 - 冷却单发前:卡链跳过 +
estimateGas门禁 — 冷却收尾里每次单发前,先跳过本次已判定卡链的,再用estimateGas预检,不通过的按「已停(索引滞后)/ 冷却 / 疑似卡链」分类记账,不盲发。__stopStuckThisInvocation命中则 continue;_preCheckStop([harvestId])结果 ok 为假时调_emergencyCreditBlocked([item], 场景名) 后 continue。定时补停和冷却重试两条单发路径都有这一步。触发:冷却定时补停 / 冷却重试的逐只单发|来历不盲发 tx|版本1.1.12 - 单发结果三态记账 — 单发返回 true 记成功,false 记失败,null(已上链待确认)放进退避复读队列,由调度器之后复读定夺;单发异常只打日志。每次单发间隔 0.8 秒。true →
_stopCreditSuccess;false →_stopCreditFail;null →_stopBackoffEnroll(item,true);每只之后delay(800)。触发:冷却收尾的逐只单发|参数单发间隔 800ms|来历单发间留 0.8 秒,避免连续 tx 造成 nonce 冲突和节点压力|版本1.1.22 - 补停后回写 remaining 并记账 — 定时补停或冷却重试做完后,重新查一遍 remaining 的状态,剔除成功集后回写;确认已停的记操作账并清失败计数(unknown 不算确认)。
_emergencyQueryStatus(remaining)→_stopFilterConfirmed;alreadyStopped调recordAction和_stopCreditSuccess。注意复读的是主循环剩下的 remaining,不是冷却队列本身。触发:定时补停后 / 冷却重试后|来历避免下面推迟判断提前 return 时漏更新;避免最终汇总把已实锤停成的算成未停|版本1.1.14 / 1.1.21 - 冷却重试少量且低危时推迟合批 — 冷却结束、待立即重试的 kami 只有 1~2 只,且没有饿死、没有高危时,不在本次发,推迟到下一轮合批。
canDefer= 数量 >0 且 <3、无isStarving、全部 delta > -10;满足时打青色日志后直接 return。代码实际上这个 return 会跳过后面的最终汇总、收敛小结和时延小结日志(finally 照常执行)。触发:冷却收尾,定时补停之后|参数COOLDOWN_DEFER_MIN=3;HIGH_RISK_DELTA=-10|来历单只重试约 2.43M gas 很浪费;高危的即使只有 1 只也立即发,避免 30 分钟后变成 STARVING 被杀 - 冷却重试含高危时保命不推迟仅观测 — 冷却重试不到 3 只,但其中有高危或饿死的,用橙色日志说明保命优先、不推迟。列出编号、Δ 和是否 STARVING。触发:冷却收尾|来历保命优先原则:高危绝不推迟
- 冷却重试:先批量,2.5 秒后复核 — 冷却结束的 kami 先一次批量停采;上链后等 2.5 秒逐只复核,仍在采集的记下最新
time.last,进入单发降级;批量整体失败则全部转单发。_emergencySendBatch(cdToRetry);复核前用成功集剔除;复核查询失败的也进stillFail。触发:冷却收尾,cdToRetry非空|参数delay(2500)|来历冷却最长 3 分钟,前面多轮等待中可能已自然恢复;顺手取time.last不增加查询|版本v1.1.10 / 1.1.14 - 单停降级前再做一次冷却复检 — 冷却重试的单发前,用复核时拿到的
time.last再算一次冷却剩余,仍在冷却就跳过这只。_cooldownRemainSec(item.timeLast)>0 时 continue;读不到timeLast(没走复核分支)时 remain=0,直接尝试。触发:冷却重试逐只单发前|来历批量 tx 和单发之间隔了 2.5 秒以上,理论上可能被别的操作重新打入冷却|版本v1.1.10 - DOM 主按钮 disabled 判冷却(降级兜底) — 判断 kami 是否在被胶水打中后的 3 分钟冷却期:卡片是采集中,且主操作按钮(harvest/stop 图标所在的 button)是 disabled,就判为冷却中。优先按
imgNumber重新定位卡片,找不到再用缓存的kamiDiv;不是采集中返回 false。v1.1.10 起主判据改为time.last公式,这里降级为或条件兜底。注释称实盘恒为 false,已失效。触发:确认阶段没停成的成员分流时(|| 兜底)|来历冷却期内停采和喂食必然失败,要和真失败分开,冷却的推迟处理、不计失败|版本v1.1.10 起 - DOM 缺失一律判非冷却(保守取向) — 卡片、状态图或按钮任一读不到时都返回 false(不在冷却)。全程可选链。触发:
_isKamiInCooldown内|来历宁可把冷却误判成真失败多计一次,也不能把真失败误判成冷却,否则那只 kami 永远进不了黑名单,反复白烧 gas
退避复读与自适应等待
- 疑似卡链退避复读(不当场记失败) —
estimateGasrevert + 仍 HARVESTING 的疑似卡链 kami 不再当场记失败,而是进__stopPendingVerify队列,按相对发送时刻 7/20/45/90/180/300 秒零 gas 复读 state;走完全表(≥6档且跨度≥300s)仍 HARVESTING 才真记失败。条目 {item,sentAt, attempts,nextAt,gasLikely,enrolledAt} 跨调用存续,终态(确认停成/拉黑/死亡/兜底超时)才删除;I3 红线:成功唯一出口是 state≠HARVESTING,gas 永不作凭据。等效拉黑需≥15分钟持续证据。触发:停采失败判定时入队,由 15 秒调度器复读|参数STOP_BACKOFF_TABLE_MS=[7000,20000,45000,90000,180000,300000];STOP_BACKOFF_FULL_MS=300000|来历0710 下午账户A实测 tx 确认 p50=5.3s/p95=16s/max=376s,疑似卡链 3 笔几分钟内就凑满,快于长尾确认,41 只已停成的 kami 被误判卡链拉黑|版本1.1.22 - 退避入队
_stopBackoffEnroll— 把一只 kami 纳入退避复读队列。已在队只刷新 item(取最新harvestId/timeLast),gasLikely只升不降,不覆盖sentAt(退避时钟不重置);新条目nextAt=now+7s;全 try/catch 异常静默跳过。触发:卡链计数门禁首次遇见时等|版本1.1.22 - 退避出队
_stopBackoffRemove— 终态时把 kami 移出退避复读队列。全 try/catch;供成功/拉黑/兜底调用。触发:_stopCreditSuccess、_stopCreditFail拉黑时|来历防 Map 泄漏|版本1.1.22 - 群体组件滞后冻结计数 — 退避队列里≥3 只卡在 90 秒+档(attempts≥4)仍未确认 → 判群体组件同步滞后,本轮冻结全部卡链计数,全体只 defer。
_componentMassLagSuspect判定;冻结时每次调用只打一条「🧊 群体组件滞后→本轮冻结全部卡链计数」。触发:_stopBackoffGate判定时|参数STOP_BACKOFF_MASSLAG_MIN=3|来历用规模区分病灶:账户A 滞后是群体现象(0710 实测 41 只),真卡链是孤例(1~2只)不会触发冻结|版本1.1.22 - 卡链计数门禁
_stopBackoffGate— 判定一只疑似卡链 kami 现在是否够格真记失败:返回 'count' 或 'defer'。首次遇见入队并 defer;需 attempts≥6(读数证据)且跨度≥证据窗(时间证据)两条件同时满足;任一不足(如页面被节流复读不足)都保守 defer;任何异常 defer。触发:停采失败记账前调用|来历绝不凭单一维度提前拉黑;出错保守 defer,因为漏停代价大于少拉黑一次|版本1.1.22 gasLikely30 分钟长证据窗 — 本批 gas 曾达真执行水平的 kami,count 需跨度≥30 分钟;其余维持 300 秒。gasLikely只延长证据窗、不豁免 count:走完仍 HARVESTING 说明 gas 谎报,必须允许最终记失败,否则拉黑通路被永久架空。触发:_stopBackoffGate判定时|参数STOP_BACKOFF_GASLIKELY_FULL_MS=30分钟|来历grok 审查关键洞:账户A 组件滞后达数十分钟,300 秒表按索引 21 秒滞后标定差一个数量级|版本1.1.22- count 前终审新鲜复读 — 真记失败前再读一次链上 state:已非 HARVESTING 则移出队列,非 DEAD 记「终审复读确认已停」成功,并返回 defer;读到空 state 或查询异常也 defer。
window.network.explorer.kamis.getByIndex(dbIndex,{harvest:true})。触发:_stopBackoffGate即将返回 count 时|来历关闭 count/success 的 TOCTOU 竞态,也是最后一道滞后保险|版本1.1.22 - 退避复读调度器(15 秒 tick) — 独立
setInterval每 15 秒扫描退避队列,对到期条目做零 gas state 复读。启动时由window.__stopBackoffSchedulerStarted防重复挂载并打印「退避复读调度器已启动(表 7s/20s/…)」;__stopBackoffTickRunning重入保护(上轮getByIndex慢于周期则跳过);队列空直接返回;整体异常静默吞掉不影响主循环。触发:自动(首轮runAutomation后启动,每 15 秒)|参数STOP_BACKOFF_SCAN_INTERVAL_MS=15000|版本1.1.22 - 调度公平:按
nextAt升序限量复读 — 先收集全部到期条目,按nextAt升序只取最早 20 只处理,其余下轮。处理过的条目nextAt后移、自然轮转。触发:每次调度 tick|参数STOP_BACKOFF_SCAN_MAX=20|来历grok 审查 B2:290 只风暴下旧写法队头垄断,队尾被饿死|版本1.1.22 - 退避 30 分钟兜底移除 — 入队满 30 分钟仍未确认的条目直接移除,并打「退避复读N分钟仍未确认已停,兜底移除,交下轮常规流程重新对账」。扫描时先于到期判断执行。触发:每次调度 tick|参数
STOP_BACKOFF_STALE_MS=30分钟|来历防 Map 泄漏|版本1.1.22 - 复读结果分类处理 — 读到空 state → 不推进 attempts,15 秒后重试;读到 DEAD → 移出队列不记成功并打「⚰️ 复读到已死亡」;读到其他非 HARVESTING → 记「退避复读确认已停」成功并打「📈 第N档(t+Xs)确认已停」。📈 日志用于画出确认时间分布。触发:调度 tick 对到期条目|版本1.1.22
- 90 秒后
estimateGas单向裁决释放重发 — 仍 HARVESTING 且距发送≥90 秒、有harvestId时,做一次停采estimateGas:成功(ok===true)= 首停 tx 真没生效,移出队列释放回候选待下轮重发(测试版 1.2.59 起:来自紧急停采的条目当场登记残留补停,5 秒后只补这一只);revert/异常不作任何判定,继续等退避档位。只信一个方向:estimateGas成功无歧义说明还能停且不在冷却;打「🔁estimateGas可停 + 仍HARVESTING(≥90s) ⇒ 首停未生效」。触发:调度 tick|参数90000ms 门槛|来历异源审查铁律:estimateGasrevert 有已停/冷却/卡链/网络毛刺四种成因,不能反推「已停」,那半边已砍,不拿保命赌 30 分钟优化|版本1.2.1 - 退避档位推进 — 仍 HARVESTING 时 attempts+1,
nextAt设为表中下一个未过点的档位;全表过点后按 15 秒周期持续复读直到确认或兜底移除。单只查询抛错不推进 attempts(保守),15 秒后重试,不影响其余条目。触发:调度 tick|版本1.1.22 - 残留补停:点名要停却没停成的那几只,1 分钟左右补一次测试线独有 — 紧急停采(主循环修剪、杀手/死亡监控全撤、手动)点名要停、却没停成的 kami,不再等下一轮主循环(约 11 分钟;贪婪模式下要候选到 106 才会再修剪),按 kamiId 登记后单独补停。只补被点名的那几只,不重新整池修剪或全撤;到时已停/已回血到停采线之上/不在采就跳过。两个入口:①轮末残留(
emergencyStopHarvest的 finally):没停成、且不在退避队列/没判卡链/没确认停成的,加上冷却收尾里没处理完的危险 kami(被 180 秒硬上限或全撤抢占跳过、冷却剩 >90 秒、单发失败或熔断跳过;补停轮自己点名的目标不论危险与否都会再登记)→ 最早 60 秒后补,还在冷却的等冷却完 +5 秒;②退避释放(C2 判「首停未生效」且条目来自紧急停采)→ 当场登记,5 秒后补。补停这一轮(opts.residual):扫描后只留点名的,不修剪、不等凑批,第 1 轮也先过estimateGas门禁(只发「还能停」的,revert 转分类不发 tx)。让路:紧急停采正在跑或有全撤排队(__pendingFullEvac/ 补撤定时器未落地__fullEvacQueuedAt)→ 15 秒后再看,最多 40 次后放弃(❌ 日志);反过来,修剪或补停正在跑时来了全撤请求,会在下一轮发送前提前收尾(__preemptForFullEvac),让全撤先跑。补停撞上正在跑的一轮 → 重新登记 15 秒后再补,不会被当成全撤请求。限流:30 分钟最多发起 6 次,一笔停采 tx 都没发的轮次不计数(按window.__stopTxSentCount差值);超了不丢,等窗口空出。单只从第一次登记起 40 分钟未补成放弃(❌ 日志;首次时刻记在window.__residualRestopFirstSeen,补停后重新登记不重置这个时钟)。补停轮的完成行带「(残留补停轮)」,统计撤离耗时可据此剔除。已知限制:补停门禁遇到 RPC 超时(不是真 revert)会把目标推回退避队列,等下次确认再补。按「后台默认会被节流」设计:主时钟setTimeout,退避调度器每拍(重入保护之前)再查一次当看门狗,节流时可能晚到约 1 分钟。登记后 5 分钟内会挡一下定时刷新(刷新会清空待补名单和退避队列)。日志前缀:「⏱️ [紧急停采/残留补停] 轮末残留/退避释放/补停仍残留:… 约 Ns 后只补这几只」「🔁 … 开始」「🎯 … 只补点名的」「✅ … 没有需要停采的kami」「已停用 … 达上限」「❌ … 放弃」。触发:紧急停采结束 / 退避调度 tick|参数RESIDUAL_RESTOP_DELAY_MS=60000、RESIDUAL_RESTOP_RELEASE_DELAY_MS=5000、RESIDUAL_RESTOP_MAX_IN_WINDOW=6、RESIDUAL_RESTOP_WINDOW_MS=30 分钟、RESIDUAL_RESTOP_TARGET_TTL_MS=40 分钟、RESIDUAL_RESTOP_BUSY_RETRY_MS=15000×RESIDUAL_RESTOP_BUSY_MAX=40、RESIDUAL_RESTOP_RELOAD_HOLD_MS=5 分钟|存储window.__residualRestop(待补名单)、window.__residualRestopHist(限流时间戳),刷新即清空|来历0916 20:55 服务端 RPC 故障,一个账户的修剪最深 2 只首发失败,C2 一分钟后判首停未生效,却要等 6 分钟后的下一轮主循环才重发,共晚停约 11 分钟;历史日志同类真实晚停 6 起(4.6~13.8 分钟)。用户 0917「按你想的改」|版本测试版1.2.59 - 紧急停采防卡死:请求超时 + 心跳看门狗 + 全撤可打断单发测试线独有 — 防止一轮紧急停采卡在某个永不返回的网络请求上、把之后所有停采(含杀手全撤)都挡在「已在运行中,跳过」外面。①超时:停采 tx 的发送与等回执各套 120 秒(
__raceTimeout,覆盖单发_allowFailureStop的 mud/raw 发送与回执、批量 raw 发送、批量确认的 txPromise 与回执);超时=结果未知,单发返回 null → 入退避复读、不记失败(记失败 3 次会拉黑 30 分钟),真没停的约 90 秒后被 C2 释放、由残留补停补上;一只单发卡住最多耗 240 秒,然后轮到下一只。②逐只单发循环每只之前看一眼:有全撤请求排队(修剪/补停轮)或本轮已被接管就收手,打「…不再逐只单发(交残留补停/下一轮)」;不按 180 秒截断。③看门狗:每轮开头、每发完一批、每只单发之前刷新心跳window.__emergencyStopStartedAt;连续 20 分钟没有心跳(__emergencyRunIsStale())→ 下一个请求不再走「已在运行中」,打红底「🚨 [紧急停采/看门狗] 上一轮…已 N 分钟没有任何进展,判定卡死,强制接管」并开始新一轮;慢但活着的一轮(上百只全撤、逐只单发)因为一直有心跳不会被误接管。每轮有代号window.__emergencyStopGen:被接管的旧一轮日后醒来,while 顶部/单发循环不再发送,它的 finally 只打「被接管的旧一轮…忽略它的收尾」,不碰锁、运行标志、补撤、残留登记。④定时刷新不给卡死的一轮让路,且让路有 10 分钟总上限(见「紧急停采让路」条目)。已知限制(罕见路径,只会多发不会漏停):冷却收尾里的两个逐只单发循环不看抢占/代号;旧一轮若卡在设紧急锁之前,醒来后可能再发一批、紧急锁空挂到 600 秒自愈;残留补停见到卡死的运行标志仍让路(由主循环硬触发/杀手监控触发接管);一键停采命令自己置的运行标志没有心跳,不受看门狗管。触发:每次emergencyStopHarvest调用|参数超时 120000ms;卡死阈值 20 分钟(写在函数里,无模块级常量)|存储window.__emergencyStopStartedAt、window.__emergencyStopGen,刷新即清空|来历0918 18:28~22:50 一台 RPC/钱包签名很慢的机器上,修剪轮降级成逐只单发,第 10 只的请求永不返回 → 运行标志挂 4 小时 → 20 多次修剪与 20 次杀手/死亡全撤全部被跳过,线下候选 90→171,杀手进房后死 24 只。回放同一时间线:旧版 4 小时 20 分后仍卡死;新版首轮约 14 分钟正常结束,最深 10 只 17 分钟内全部停成|版本测试版1.2.60 - 停采确认耗时滚动历史持久化 — 把最近 30 笔停采 tx 确认耗时存到
localStorage,跨刷新保留。加载时读 JSON,过滤非正数/非有限值,截尾 30;读失败(无localStorage/坏 JSON)空数组冷启动;_recordTxConfirmMs入窗并持久化,任何异常静默吞掉,持久化失败内存窗口仍生效。触发:停采确认耗时收集点(约 5314 行)调用_recordTxConfirmMs|参数__TX_CONFIRM_HIST_MAX=30|存储kami_tx_confirm_hist|版本1.2.4 - 自适应停采基础等待(滚动 p90) — 样本<5 笔用 9 秒;否则取 p90+3 秒索引余量,限制在 8~30 秒;计算异常回退 9 秒。纯等待时长调整,不改任何判定/重发逻辑。触发:紧急停采计算确认等待时|参数下限 8000 / 上限 30000 / 余量 3000|存储
kami_tx_confirm_hist|来历用真实确认时延替代静态等待,避免「确认慢→误判失败→重发白烧gas」;30 秒封顶防 376 秒极端长尾拖垮,长尾交多轮重试+退避复读|版本1.2.4 - 自适应等待读数观测仅观测 —
_adaptiveStopWaitInfo返回 {ms, n, secs} 供日志验证自适应在工作。异常返回默认 9 秒/0 样本。触发:被日志调用|版本1.2.4
熔断、去重记账与停采黑名单
- 停采失败黑名单 — 连续 3 次
AllowFailure模式都停不下来才拉黑,30 分钟后自动解除。__stopBlockedKamis/__stopBlockedTime/__stopFailCount;纯内存;window.__stopBlockedKamis只读引用供看板读规模。触发:停采失败记账_stopCreditFail写入|参数STOP_BLOCK_THRESHOLD=3;STOP_BLOCK_COOLDOWN_MS=30分钟|命令showStopBlockedKamis()/clearStopBlockedKamis()|来历卡在链上的 kami 继续发停采 tx 只会白烧 gas - 每次调用同一 kami 最多记一次失败 — 每次
emergencyStopHarvest调用自增__stopInvocationId;同一调用内同一 kami 重复失败只记 1 次,并打「本次调用内已记过失败,跳过重复计数」。__stopFailCreditedInvocation记kamiId→已记账的invocationId;天然要求连续 3 次独立调用都判卡链才拉黑。触发:_stopCreditFail时|来历0709 审计:同一 kami 在一次调用内被主批→强化批量重试→强化单独重试多路径重复判失败,计数 3→4→5 连加,配合误判穿透阈值误拉黑|版本1.1.12 - 失败处置两分类(索引滞后 vs 疑似卡链) —
estimateGasrevert 但链上 state 已非 HARVESTING → 判「已停实锤、索引器滞后」清计数绝不拉黑;仍 HARVESTING → 判「疑似卡链」进计数/退避流程。实现在 classify 函数(本段外);拉黑机制必须保留,只是要抓对人。触发:停采estimateGas裁决时|来历部分 kami 会卡链数天、任何停采必 revert,不能无限重试;0709 问题是把已停成只是索引读数滞后的健康 kami 误拉黑 140 次|版本1.1.12 - 本次调用已判卡链集合 —
__stopStuckThisInvocation:本次调用内已判疑似卡链的 kami,除estimateGas探测外不再发起任何操作,尤其不再发真实 tx。模块级 Set,每次调用清空。触发:紧急停采调用内|版本1.1.12 - 本次调用已确认停成集合 —
__stopConfirmedThisInvocation:本次调用内用强证据(链上直接读到非 HARVESTING,或estimateGasblocked 后复读 state≠HARVESTING)确认停成的 kami。唯一写入点是_stopCreditSuccess;主循环每轮查完状态后用它剔除 remaining。触发:紧急停采调用内|来历0709 事故直接根因:索引器滞后 17~99s,已停 kami 仍报 HARVESTING 被再发一次 tx|版本1.1.14 - 冷却延后队列跨函数共享 —
__stopCooldownDeferredThisInvocation:classify 发现「estimateGasrevert 但仍在 180 秒操作冷却期内」的 kami 归入此 Map,主循环局部变量cooldownDeferred直接引用同一个 Map。模块级+每次调用清空,解决 classify 顶层函数够不到主循环局部变量的问题。触发:停采 classify 时|来历冷却中的 revert 不是卡链,不应计失败|版本1.1.14 - 停采调用内统计计数器仅观测 —
__pendingVerifyBatchCount统计本次调用产生pendingVerify的批数;__stopChanFallbackCount统计本次调用 mud→raw 通道跌落次数。纯统计,调用开头清零,供小结日志。触发:紧急停采调用内|版本1.1.17 / 1.1.19 - 时延与 revert 原因观测容器仅观测 —
__stopTxConfirmMsList记本调用各批 tx 确认耗时、__stopIndexLagMsList记各 kami 索引器滞后,完成小结算中位/最慢;__stopRevertReasonSeen记已打过的 revert 原因串去重防刷屏。均在调用开头清空(群体滞后日志节流标记也在此时重置)。触发:紧急停采调用内|版本1.1.22 - 前端冻结时停采失败不计数 — 前端疑似失真(
window.__frontendFrozen)时,停采失败不计数、不拉黑,只打「🧊 前端疑似失真,本次不计数、不拉黑,记 defer」。_stopCreditFail第一关读_isFrontendFrozen()。触发:_stopCreditFail时|来历MUD 前端同步流卡死时读数冻在旧值,冻结期间累计失败会误拉黑(Bug B)|版本v1.1.25 - 停采失败达阈值拉黑 — 连续失败计数 +1,达到 3 次加入停采黑名单并移出退避队列,打「🚫 连续N次…加入停采黑名单」;未达阈值打「(n/3),暂不拉黑」。
reasonTag只影响日志措辞;拉黑后由黑名单自己的 30 分钟到期复查。触发:_stopCreditFail时|参数STOP_BLOCK_THRESHOLD=3|版本1.1.12 - 停采成功统一记账
_stopCreditSuccess— 确认停成时:移出退避队列、清零失败计数与去重记录、若在黑名单主动移出(打「停采成功,移出停采黑名单」)、写入本次调用已确认集合(首次写入打「写入已确认停成,下轮remaining剔除,不再重发tx」)。是全部「确认停成」路径的唯一记账出口,比在多个调用点分别插入更不易漏改。触发:classify 已停实锤、链上读到非 HARVESTING、退避复读确认等|来历30 分钟自动解除只是兜底,主动移出避免已恢复的 kami 白等|版本1.1.14 - 剔除已确认停成的 remaining — 从
stillHarvesting/remaining 里剔除本次调用已强证据确认停成的 kami,并打「索引器仍报N只HARVESTING,其中M只已实锤停成→剔除」。剔除超过 8 只时只显示前 5 个样例。触发:主循环每轮查完状态后|来历索引器滞后(0709 实测 17~99s)会让已停 kami 被重新组批空烧 gas;改动 A 收紧阈值后必须配合本函数,否则真失败不再被滞后兜住重试而真正漏停(顺序陷阱)|版本1.1.14 clearBlockedKamis清空全部黑名单 — 同时清空部署黑名单三件套、停采黑名单三件套和退避复读队列,打印并返回 {deploy, stop} 数量。不清__stopFailCreditedInvocation。触发:命令|命令clearBlockedKamis()|来历手动处理完卡住的 kami 后立即解封;清退避队列给这些 kami 干净重来|版本1.1.22clearStopBlockedKamis只清停采黑名单 — 清停采黑名单、拉黑时间、失败计数和退避复读队列,返回数量。触发:命令|命令clearStopBlockedKamis()|版本1.1.22showStopBlockedKamis查看停采黑名单 — 逐只打印「#编号 已拉黑N分钟,剩余M分钟」及总数,返回列表。kamiId反查kami_core_db编号,查不到显示 '?';kamiId截断前 10 位;剩余分钟用硬编码 30 计算(未引用STOP_BLOCK_COOLDOWN_MS常量);为空提示并返回 []。触发:命令|命令showStopBlockedKamis()
收尾小结
- 最终汇总仅观测 — 打印未能停采的编号,以及「停采 X/Y、耗时 N 分 N 秒」;全部停成用绿色,否则橙色。stopped = 首次预检后的
validList数 − 最终 remaining 数。代码实际上凡是不在 remaining 里的都算停成,包括只喂不停的 STARVING、冷却留到下一轮的、卡链跳过的和门禁拦下的,所以这个数字偏乐观。触发:全部结束后 - 停采收敛小结仅观测 — 打印本次
pendingVerify批数、已确认停成只数、当前停采通道,以及 mud 跌落到 raw 的次数,并注明 gas 不作为停成凭据。读__pendingVerifyBatchCount、__stopConfirmedThisInvocation.size、_getStopTxChannel()、__stopChanFallbackCount。触发:汇总之后|来历成功以下一轮 state 不是 HARVESTING 的复读为准;保守起见不为凑指标改流程|版本1.1.17 / 1.1.19 - 停采时延小结仅观测 — 打印本次 tx 确认耗时的中位数和最慢值、索引器反映耗时的中位数,以及还留在退避队列里的只数。对
__stopTxConfirmMsList和__stopIndexLagMsList取中位数,无样本显示 NA;整段 try/catch 包住。触发:收敛小结之后|来历用来日后画确认时间分布、标定退避表|版本1.1.22
1.5 部署
休息中、血量够的 kami 凑够一批才部署。部署前核对主地块、做 estimateGas 预检、查黑名单;失败的回队重试并二分拆包隔离坏个体。
部署候选与组池
- RESTING 动作冷却跳过 — 休息中的 kami 若刚处理过部署,本周期内不再重复收集。
alreadyActed(imgNumber,'startHarvest')为真则restingAlreadyActed+1 跳过;restingTotal每只+1。触发:每只 RESTING kami|参数冷却窗口=checkInterval - 缺
kamiId链上补查并回填 — 本地库缺kamiId时按编号从链上补查,顺手把harvestId也补进本地库。_fetchKamiAndHarvestIds(dbIndex);拿到 kid 回填record.kamiId;拿到 hid 且本地harvestId不是合法 64 位哈希时回填。回填的是内存中的kami_core_db记录。触发:RESTING 且缺kamiId且编号有效 - 清算线过高跳过部署 — 清算线超过 95% 的 kami 不自动部署,提示先升级。
record.LT>95 时restingLTSkip+1,打「部署跳过 #N 清算线过高(LT=x%),需先升级后再部署」(每轮都会打)。触发:每只 RESTING kami|参数LT 跳过线 95|来历未升级的低级 kami 清算线接近 100%,部署后几乎立刻要停采,纯浪费 gas - 部署候选按 HP 分层 — 血量≥98% 进主力池,95%~98% 进随队池,更低的继续休息。HP≥98→
__startStrong;HP≥95→__startWarm;否则restingHPLow+1;dlog 输出 [START?] STRONG/WARM/skip。触发:每只 RESTING kami|参数strong 门槛 98;warm 门槛 95|来历warm 不单独成行:为血没回满的 kami 单独发 TX 不划算,部署后也更早触线停采 - 部署池组池规则(strong 带队) — 决定哪些休息中的 kami 进本轮部署池:主力≥2 只时主力加随队全上,1 只主力可带随队,没有主力就不部署。strong≥2→strong+warm;strong=1 且 warm≥1→1 只 strong+全部 warm;strong=1 且无 warm→单只候选(能否真部署由凑批闸门决定);strong=0→空池。触发:停采流程结束后、仍持普通锁|来历warm 不单独成行:为血没回满的 kami 单独发 TX 不划算
- 自家杀手 kami 不自动部署 — 注册为自家杀手的 kami 从部署池里剔除,由用户手动部署到攻击位置。
window.MY_KILLER_KAMIS为 Set 且非空时,按 Number(dbIndex) 剔除,打橙色「部署 跳过 N 只杀手 kami: #编号(不参与自动部署…)」。该 Set 由杀手监控脚本自检后自动注册。触发:组池后|来历杀手站位是战术选择,脚本不代劳 - 无可部署 kami 提示仅观测 — 本轮没有血量够的休息 kami 时打一条提示;被主地块校验清空的不重复提示。池为空且 strong/warm 都为 0 且不在暂停窗口→打「没有HP足够的RESTING Kami可以部署(需HP≥98%)」。触发:池为空时
- 小账户逃生通道 — 账户 kami 总数不超过凑批门槛时,把门槛降为 1,按实际数量部署。
getByOperator(addr).kamis.length≤MIN_DEPLOY_BATCH时effectiveMin=1,打灰色提示;读不到账户按默认门槛走。触发:池非空时|参数MIN_DEPLOY_BATCH=6|来历否则小账户永远凑不齐一批,陷入反复跳过部署的死循环 - 部署凑批闸门 — 候选不够一批时跳过本轮,等下一轮凑齐再批量部署省 gas。
pool.length<effectiveMin时打青色「部署/凑批 候选仅 N 只 (#…) < 门槛,跳过本轮等下一轮凑批省 gas」和「单只部署 1.35M gas vs N=6 时 0.80M/只,凑批可省 ~40%」。触发:小账户判定后|参数MIN_DEPLOY_BATCH=6|来历单只部署约 1.35M gas,6 只一批约 0.80M/只 - 部署凑批门槛
MIN_DEPLOY_BATCH— 部署候选不足 6 只时本轮跳过部署,等下一轮凑够再一起发(常量在此定义,由部署板块使用)。调小部署更及时但单只 gas 上升;调大更省 gas 但 Kami 闲置更久。触发:部署板块组批时读取|参数MIN_DEPLOY_BATCH=6|来历单只部署约 1.35M gas,6 只批量约 0.80M/只,散着发要多花约 70% - 查询账户当前地块编号
_getTileNumber— 通过getByOperator取当前账户所在地块(房间)编号roomIndex,部署流程用它确认要部署到哪个采集点。roomIndex不是有限数就抛错;任何失败都打「获取地块编号失败」并返回NaN,调用方用Number.isFinite判断,不中断流程。触发:部署流程按需调用
主地块校验
- 采集中不足 5 只不校验 — 账户里采集中的 kami 少于 5 只时跳过主地块校验,样本太小容易误判。
getByOperator(addr).kamis 中 state 为 HARVESTING 的≥5 才校验;当前地块读不到也不校验。触发:部署池非空时|参数启用校验最少采集中数量 5 - 等距抽样求主地块 — 从采集中的 kami 里等距抽最多 15 只查所在地块,出现最多的就是主地块;每轮实时抽,不缓存,用户真搬家后自动认新家。
samplePrimaryRoom:step=floor(总数/样本数),逐只getByIndex读harvest.node.index计数,单只查询失败跳过,取众数。触发:校验启用时|参数抽样上限 15 - 不匹配时错开样本二次确认 — 第一次抽样发现当前地块不是主地块时,错开样本再抽一次,两次都不匹配才认定。第二次 offset=floor(样本数/2);dlog 记第 1 次结果;任一次抽样结果为空或与当前一致都不暂停。触发:第一次抽样不匹配|来历排除偶然误判
- 确认不在主地块则暂停部署 — 两次确认都不在主地块时,清空本轮部署池并打大号红色提示,回到主地块后自动恢复。打「部署暂停 主地块: 名称(#x),当前: 名称(#y)。回到主地块后自动恢复部署」,pool=[]。触发:二次确认仍不匹配|来历用户临时跑图办事时照常部署会把 kami 撒到错误地块
- 部署暂停定时提醒 — 暂停期间每 3 分钟提醒一次;检测到已回主地块时打恢复提示并停掉提醒。注册前先
clearInterval旧的window.__deployPauseReminder防堆积;setInterval每 3 分钟读当前地块,等于主地块打绿色「部署恢复」并清除定时器,否则打红色「自动部署仍暂停中」。触发:确认暂停时注册|参数提醒间隔 3*60*1000ms - 校验出错放行部署 — 主地块校验本身出错时照常部署,不因查询故障卡死自动化。catch 中 dlog「主地块检查出错,继续部署」。触发:校验抛异常
全量停采后的暂停窗口
- 全量停采后自动暂停部署 10 分钟 — 一键停采(
stopCurrentRoom)/转移停采(stopMinorityForTransfer) 完成后,自动暂停自动部署 10 分钟,并橙色打印到期时刻(配置时区 HH:MM)和恢复命令。写window.__deployPausedUntil=now+10分钟;自动部署入口每轮检查未到期则跳过部署;用绝对到期时间戳+主循环轮询实现,不挂定时器无需清理。触发:两个停采命令收尾时调用_pauseDeployAfterStopAll(tag)|参数DEPLOY_PAUSE_AFTER_STOPALL_MS=10分钟|命令resumeDeploy()|来历用户全量停采通常是为转移地块或转给其他账户,不暂停的话下一轮会把刚 RESTING 的 kami 重新部署回原地块,白停一次浪费两笔 gas - 部署暂停标志幂等初始化 —
window.__deployPausedUntil= 已有值 || 0。挂 window 让部署入口跨作用域共享。触发:脚本加载时|来历脚本重复加载时不清零已生效的暂停 resumeDeploy手动解除暂停 — 立即把暂停标志清零,打印「暂停已手动解除,下一轮恢复自动部署」。触发:命令|命令resumeDeploy()|来历误触停采命令后的逃生口- 转移暂停窗口 — 全量停采后的一段时间内不自动部署,避免用户要转移地块时刚停完又被部署回原地块。
window.__deployPausedUntil未到期且池非空→打橙色「转移暂停窗口生效中,剩余 N 分钟,本轮 x 只候选不部署(立即恢复 →resumeDeploy())」并清空池。触发:主地块校验后|命令resumeDeploy()立即解除暂停窗口(定义在别处)
发送批量部署
- 部署准备日志 + tile 获取失败放弃 — 打出准备部署的数量和目标地块;拿不到地块 tile 编号时放弃本轮部署。打「部署 准备部署 N 个Kami到 地块名(HP≥98%: x个, HP≥95%: y个)」;await
_getTileNumber()非有限数则打「tile 获取失败,跳过本轮部署」。触发:通过凑批闸门后|来历缺 tile 参数无法发部署 TX - 部署随机切批(含尾批合并) — 部署池按门槛到 10 只随机切批,与停采同一套策略,避免零碎尾批浪费 gas。
chunkRandom(pool,effectiveMin,10);dlog 输出各批大小。小账户门槛为 1 时可退化为单只部署。触发:tile 获取成功后|参数批量上限 10 - 每批部署前紧急锁中断 — 每批部署发出前检查紧急锁,有就中断剩余部署。
hasEmergencyLock()为真打「检测到紧急锁,中断部署」break。触发:每批前 - 发送前逐只 ready 过滤 — 发 TX 前把已经在采集或没有
kamiId的从本批剔除,剔完为空就跳过这批。batch.filter(!_isCardHarvestingByImg && kamiId),为空 continue;打「批量部署/第 i/N 笔(API) 计划 x 个」。触发:每批 - API 批量部署 + 成功件记冷却 + 整批汇总 — 调用带退避的批量部署,确认成功的记部署冷却防重复,整批只打一行汇总。const {pending:still,succeeded:
doneList}=awaitdeployWithBackoff(ready,tile,5);doneList逐只recordAction('startHarvest');打「批量部署/第 i 笔(API) 成功 N 个:#编号(img:…)」。触发:每批|参数退避尝试 5 次|来历0709 审计:旧版用 ready 减 still 的差集反推成功,被拉黑/冷却跳过的项被误算成功。同一笔原子成功,逐只打印是冗余刷屏|版本1.1.12
交易预检与发送
estimateGas通用交易预检 — 发真实交易前先模拟执行整笔批量 TX,会失败的就不发,识别坏 kami 完全不花 gas。_preCheckTx(type,params):用system.interface.encodeFunctionData('executeBatched',…)构造与真实 TX 一致的 calldata,再signer.estimateGas({to:system.target,data});deploy 参数 [kamiIds,tile,0(taxerID),0(taxAmt)],stop 参数 [harvestIds];成功返回 {ok:true,gasEstimate},失败返回 {ok:false,reason,detail},reason 取e.code||e.reason。触发:被_apiDeployOnce、D3 复核、DOM 兜底部署门禁、停采流程调用|来历以 0 gas 成本拦截注定失败的部署/停采- 预检不可用时放行 — 签名者、合约接口不可用或类型未知时,预检直接放行,不卡住主流程。signer 为空→dlog 后返回 {ok:true}(无
gasEstimate);system.interface为空→{ok:true};未知 type→dlog 后 {ok:true}。没有 feed 分支。触发:每次预检 - revert 原因多字段提取 — 预检失败时尽量抓出完整的 revert 原因串,写进调试日志并随结果返回。依次取
e.reason/e.shortMessage/e.info.error.message/e.data/e.message的第一个非空值,截 120 字作为 detail。注释写「判定逻辑不变,仍以 reason 为准」,代码实际:1.2.4 的 D3 复核会用 reason+detail 做 revert 正则判断,detail 已参与部署成功判定。触发:预检失败时|参数detail 截断 120 字|来历供停采estimateGas裁决处打观测日志|版本1.1.22 - 部署/停采预检便捷封装 — 给部署和停采各包一个简单的预检入口,停采流程复用同一个预检通道。
_preCheckDeploy(kamiIds,tile)/_preCheckStop(harvestIds)。触发:被部署与停采代码调用 - 交易回执轮询兜底 — TX 对象没有 wait 方法时,自己去链上轮询回执;拿不到就如实返回空,绝不当作成功。
_pollTransactionReceipt(signer,hash,capMs):按 3s→6s→12s→24s 退避查provider.getTransactionReceipt,累计不超过capMs;查询异常继续退避不中断;循环结束再查一次;provider 或 hash 缺失直接返回 null。触发:部署 TX 无 wait 方法时调用|参数退避序列 3000/6000/12000/24000ms|来历绝不能因为拿不到回执就假设已经成功|版本1.1.12 - 单次批量部署入口检查与准备日志 — 部署 API 不可用时直接报错;可用时先打出本批准备部署的列表。
api.player.pet.harvest.start不存在则 throw 'api.start不可用';打「批量部署/准备(API) N 个 → 列表」,dlog 输出 ids 与 tile。触发:_apiDeployOnce开头 - 整批一次预检 — 先用一次
estimateGas覆盖整批,通过就直接发,并打出预估 gas。_preCheckDeploy(ids,tile),通过打「批量部署/预检通过 预估gas: …」。触发:每次单批部署前 - 整批预检失败→逐只预检定位坏 kami — 整批模拟失败时,一只只单独预检,找出是哪几只有问题,剩下的健康 kami 照常部署。逐只
_preCheckDeploy([id]),通过的进okIds,失败的打「逐个预检 #N ❌ 失败: 原因」并只给失败的累加失败次数;打排查结果统计;全失败返回 {ok:false,isCallException:true,isPreCheckFail:true} 跳过本批;否则只用okIds继续。触发:整批预检失败时|来历预检不上链不花 gas,避免一只坏 kami 连累整批 - 预检连续失败拉黑(未花 gas) — 同一只 kami 部署预检连续失败达到次数就进部署黑名单,暂时不再尝试部署它。
__kamiDeployFailCount达DEPLOY_BLOCK_CONFIG.FAIL_THRESHOLD时加入__blockedKamiIds,记__kamiBlockedTime,打「连续失败N次,已加入黑名单(未消耗gas)」。三者均为内存态,重载清空。触发:逐只预检失败时|参数DEPLOY_BLOCK_CONFIG.FAIL_THRESHOLD=2 - 部署发 TX 前等紧急锁(最多 300 秒) — 发部署 TX 前如果紧急停采正持锁,先原地等它结束;等太久就放弃这次部署。
hasEmergencyLock()为真时waitForEmergencyRelease('批量部署',300000),超时打「等待紧急锁释放超时,放弃本次部署」并返回失败(非CALL_EXCEPTION)。触发:预检之后、api.start之前|参数紧急锁等待上限 300000ms|来历发 TX 前最后一道防线,紧急停采优先 - 发送批量部署 TX(空 TX 视为失败) — 用一笔 TX 把整批 kami 部署到采集点;API 返回空就当失败。
api.start(idsToUse,tile);返回空打「API返回空Tx」并返回 {ok:false}。触发:通过预检与紧急锁检查后|来历N 只合并 1 笔摊薄固定 gas - gas 真值账本记账(部署)仅观测 — 部署 TX 发出后记一笔账,按 hash 事后补真实 gas,归类到部署动作。
_gasLedgerRecord('deploy',idsToUse,tx),非侵入仅此一行。触发:部署 TX 非空后|命令showGasReport()(定义在别处)|版本1.2.7 - 部署 TX 无 wait 改轮询回执(不假设成功) — TX 没有 wait 方法时轮询回执最多 60 秒,拿到 status=1 才算成功,拿不到按「已发送待确认」交上层去链上复核。有 wait 就 await
tx.wait();否则_pollTransactionReceipt(cap 60s):无回执返回 {isTimeout:true,attemptedIds};status≠1 返回 {isCallException:true};status=1 打「轮询到回执确认成功」继续。板块头注释仍写「Tx 无 wait 方法时的回退延时 15000ms」,代码实际已改为轮询回执、上限 60 秒。触发:部署 TX 发出后|参数回执轮询上限 60000ms|来历0709 审计:v1.1.11 及更早delay(15000)就当已确认,TX 到底成没成完全未知,是「部署假成功」的根因之一|版本1.1.12 - 部署成功清失败计数 + 完成日志 — 部署确认成功后清掉这些 kami 的失败计数,并打出 tx 前缀和编号列表。打「批量部署/完成(API) tx:前10位… → #编号 [消耗Gas]」,逐只
__kamiDeployFailCount.delete,返回 {ok:true,deployedIds}。触发:TX 确认成功后 - 部署失败错误分类 — 把部署失败分成合约回滚、超时、nonce 被拒几类,上层按类别决定怎么重试。
CALL_EXCEPTION:e.code或消息含CALL_EXCEPTION/revert;超时:消息含 timeout/not mined;nonce:消息含 nonce 或 sequence mismatch。返回 {isCallException,isTimeout,attemptedIds}。触发:api.start或tx.wait抛异常时 - 失败 gas 消耗提示仅观测 — 部署失败日志里标出这笔大概有没有花掉 gas,方便对账。nonce 被拒→[未消耗Gas - Nonce被拒];超时→[可能消耗Gas - 超时];其余→[消耗Gas];日志截 80 字。触发:部署失败时
CALL_EXCEPTION整批计失败并拉黑 — 真实 TX 合约回滚时,本批每只都记一次失败,达到次数就拉黑。打「检测到CALL_EXCEPTION,可能有kami卡在链上」,idsToUse逐只失败计数+1,≥阈值进黑名单并记时间、打日志。触发:部署 TX 回滚时|参数DEPLOY_BLOCK_CONFIG.FAIL_THRESHOLD=2
部署队列、复核与拆包
- 部署进场静置 5 秒 — 进入部署队列前先等 5 秒,让上一轮 TX 的 nonce 和链上状态稳定下来。
delay(STATE_CHECK_DELAY_MS);代码实际:这 5 秒在空输入检查之前,传入空列表也会先等 5 秒再返回。触发:deployWithBackoff开头|参数STATE_CHECK_DELAY_MS=5000|来历避免 Nonce 冲突 - 包队列调度 + 尝试次数上限 — 把待部署的 kami 放进队列按包发 TX,失败的回队重试,发 TX 总次数有上限。queue=[{items,
failStreak}],while 队列非空且 attempts<maxAttempts;真正调用_apiDeployOnce才 attempts++(本包无目标直接 continue 不计次)。触发:被主循环批量部署调用|参数maxAttempts=5(形参默认,上层也传 5)|来历防止无限重试烧 gas - 显式成功清单(不再差集反推) — 部署结果明确分成「确认成功」和「仍待处理」两份返回,被跳过的 kami 绝不会被误算成成功。succeeded 只收 DOM 已采集中/链上确认/最终确认成功/D3 复核确认的;被拉黑或冷却预筛跳过、没真正尝试过的进
droppedPending,收尾并入 pending;返回 {pending,succeeded}。触发:deployWithBackoff全程|来历0709 审计:旧版上层用「输入减 pending」反推成功,中途被悄悄丢弃的项被误算成功,是部署假成功根因之一|版本1.1.12 - 部署队列每轮紧急锁检查 — 每处理一包之前检查紧急锁,有紧急停采就中断整个部署队列。
hasEmergencyLock()为真打「检测到紧急锁,中断deployWithBackoff」并 break,剩余队列在收尾时进 pending。触发:每包循环开始 - 卡片已显示采集中计为成功 — 部署前过滤时发现卡片已经在采集,直接算作部署成功,不重复发 TX。
_isCardHarvestingByImg(imgNumber)为真则 push 进 succeeded 并从本包剔除。触发:每包过滤阶段|来历可能是本次更早一批已成功或本来就在采,不再悄悄丢弃|版本1.1.12 - 缺
kamiId项直接剔除 — 没有kamiId的条目在过滤阶段直接去掉。filter 中 !x.kamiId直接 return false。代码实际:这类项既不进 succeeded 也不进droppedPending(上层 ready 已预先过滤掉缺kamiId的,实际不会走到)。触发:每包过滤阶段 - 部署黑名单过滤 + 30 分钟自动解除 — 跳过黑名单里的 kami;拉黑满 30 分钟的自动解除并清零失败计数,给它自愈后重新上岗的机会。在黑名单且拉黑时长>
AUTO_CLEAR_MS→删除黑名单/时间/失败计数,打「已超时自动解除黑名单」并保留;否则打「跳过被blocked的 Kami #N」,push 进droppedPending。触发:每包过滤阶段|参数DEPLOY_BLOCK_CONFIG.AUTO_CLEAR_MS=30分钟|版本1.1.12 - 本包无可部署目标跳过 — 过滤后一只都不剩就跳过这包,不发 TX。打「批量部署/跳过 本包无可部署目标」或「冷却预筛后本包无可部署目标」并 continue。触发:过滤后/冷却预筛后
- 部署冷却公式预筛 — 部署前逐只查一次冷却,还在 3 分钟操作冷却里的本批不发,避免整批模拟失败连累健康 kami。本包≤20 只时逐只
getByIndex({harvest:true})用_cooldownRemainSec(time.last);剩余>0 进冷却列表,打「冷却预筛 N 个冷却中,本批不发(tx必败省gas): #编号(剩Xs)」并并入droppedPending;读取失败按无冷却处理(回落旧行为)。触发:每包过滤后|参数补读阈值≤20 只;KAMI_ACTION_COOLDOWN_SEC=180|来历整批estimateGas失败会触发二分拆包,冷却中的 kami 让同批健康 kami 被误伤|版本v1.1.10 - 部署 TX 发出后立即让路紧急锁 — 部署 TX 发出后马上再看紧急锁,紧急停采来了就跳过确认环节直接让路(TX 已广播不受影响)。
hasEmergencyLock()为真打「部署Tx已发送,检测到紧急锁,跳过确认让路紧急停采」并 break。代码实际:这一包已从队列取出,既不进 succeeded 也不进 pending,交下轮扫描重新判断。触发:每次_apiDeployOnce返回后 - 部署成功后等 UI 收敛逐只链上复核 — TX 确认成功后等前端刷新 25 秒,再逐只查链上是否真的进入采集状态。
delay(UI_SETTLE_MS)后逐只getByIndex:state 非 HARVESTING 且卡片也不显示采集中→进stillNotHarvesting;查询异常console.warn并保守算未收敛。触发:result.ok后|参数UI_SETTLE_MS=25000 - D3
estimateGas复核纠偏(索引滞后假失败) — 25 秒后索引器仍说「没起采」的 kami,再单独模拟部署一次;链上拒绝再次部署说明其实已经部署了,直接计成功,不回队重发。只对stillNotHarvesting中属于本批已发送子集的逐只_preCheckDeploy;ok===false 且 reason 为CALL_EXCEPTION或 reason+detail 匹配 /call_exception|revert/i 才算已部署→清失败计数、进 succeeded,打「D3复核纠偏 N只estimateGas确认已部署…」;ok===true(真没部署)、网络错、预检异常→保守回队。双重证据:只有原子批 status=1 加 revert 两条都指向已部署才计成功,revert 单独在别处不能当「已部署」用。触发:部署成功后的复核发现未收敛时|来历掐断 25 秒索引复核的假失败源头;若这 25 秒内恰被紧急停采,start 因冷却 revert 也计成功是对的(本批确实部署过),回队重发只会撞冷却白烧 gas|版本1.2.4 - D3 成功判定限定本批已发送子集(B1 修) — 用 revert 反推已部署只对本批真正发出去的 kami 有效,预检阶段就被剔掉、没发送的 kami 一律回队。
_deployedSet=new Set(result.deployedIds),不在集合里的直接进幸存者回队。触发:D3 复核时|来历grok 审出:整批预检可能只发子集,没发送的 kami 若因地块满等其他原因 revert 会被误计成功导致漏部署|版本1.2.4 - 未收敛回队 + 连败二分拆包 — 复核后仍没起采的回队重试;同一包连续失败 2 次且还有多只时对半拆开,把整包连坐收敛成隔离个别坏 kami。
failStreak+1,打「UI未收敛 仍未起采 x/y」;failStreak≥2 且>1 只→前后两半各自failStreak=0 入队,打「二分拆包 N → a + b」;否则整体回队打「重试排队」。代码实际:部分收敛时,本包已确认起采的那部分不会进 succeeded(只影响上层记账和日志,下轮扫描已是采集中不会重发)。成功分支回队后没有额外 5 秒重试间隔(已等过 25 秒)。触发:复核后仍有未收敛项时|参数二分拆包阈值failStreak≥2 - 最终确认成功(去重 D3 已计) — 全部收敛时整包计入成功,D3 已经计过的不重复计。打「批量部署/最终确认成功 共 N 个」,
succeeded.push(live 中不在 D3 已计集合的)。触发:复核后无未收敛项|来历唯一的「最终确认成功」分支|版本1.1.12 / 1.2.4 - 超时后查真实状态只重试真正失败的 — 部署超时后等 5 秒逐只查卡片和链上状态,已在采集的不再重发,只把真没部署的回队。打「超时 等待5秒后查询链上状态」;逐只:卡片显示采集中→确认成功;
getByIndex为 HARVESTING→确认成功;否则进重试;查询失败保守进重试。确认的进 succeeded;无需重试打「超时但成功」;否则failStreak+1,同样二分拆包/回队。触发:_apiDeployOnce返回isTimeout且有attemptedIds|参数超时后核对前延时 5000ms|来历避免对已成功的 kami 重复发 TX 烧双倍 gas|版本1.1.12 - 非超时失败按 DOM 过滤后回队 — 合约回滚或 nonce 被拒等失败后,卡片已显示采集中的算成功,其余回队或二分拆包。已采集中的 push 进 succeeded;剩余为空打「重试放弃 本包已无可部署目标」;否则
failStreak+1、≥2 且>1 只拆包、否则回队。全部预检失败(isPreCheckFail)也走这条并消耗一次尝试次数,已拉黑的下一轮过滤会跳过。触发:部署失败且非超时|版本1.1.12 - 失败后重试间隔 5 秒 — 部署失败(超时或非超时)处理完后歇 5 秒再进下一包。
delay(RETRY_DELAY_MS);超时但全成功、重试放弃这两种 continue 不走这个间隔。触发:每次部署失败后|参数RETRY_DELAY_MS=5000|来历避免 Nonce 冲突 - 部署队列收尾汇总 — 队列结束时把没成功的整理成待处理清单交给上层,收尾时已显示采集中的归为成功。pending=
droppedPending+ 队列里卡片仍不显示采集中的;队列里卡片已显示采集中的进 succeeded。触发:队列清空或尝试次数用完|来历上层直接用 succeeded 判成功,不再差集反推|版本1.1.12
DOM 兜底部署
- DOM 兜底部署去重 + 紧急锁跳过 — API 部署后仍未成功的条目去重后走 DOM 兜底;紧急锁存在时整个兜底跳过。按
imgNumber|dbIndex去重;有紧急锁打「紧急锁存在,跳过DOM兜底部署(N个)」,否则打「批量部署/第 i 笔(DOM) 准备 N 个」。触发:每批 API 部署后|来历避免 nonce 冲突 - D2 DOM 兜底遵守部署黑名单 — 被 API 层拉黑的 kami 不再在 DOM 兜底里点击部署。
__blockedKamiIds.has(kamiId)时打「DOM兜底 #N 在API黑名单内(30min自动解除),跳过点击省gas」并 continue。触发:每只 DOM 兜底部署前|参数黑名单 30 分钟自动解除|来历被拉黑者经 pending 流回兜底,旧码不查黑名单会逐只重发→revert 白烧 gas|版本1.2.4 - DOM 兜底部署前确认未在采 — 点击部署前查链上和卡片,已经在采集的跳过。
getByIndex读 state,为 HARVESTING 或卡片显示采集中则 continue。触发:每只 DOM 兜底部署前 - D1v2 第一道:冷却公式预筛 — DOM 兜底部署前先看操作冷却,冷却中必败,连模拟都不跑直接跳过。复用同一次
getByIndex的harvest.time.last,_cooldownRemainSec>0 打「DOM兜底 #N 冷却中剩Xs,跳过(tx必败省gas)」;读不到按 0 落到第二道。零额外 RPC。触发:通过未在采确认后|参数KAMI_ACTION_COOLDOWN_SEC=180|版本1.2.4 - D1v2 第二道:
estimateGas门禁(只信成功方向) — 冷却已过才单只模拟部署;只有模拟成功且拿到预估 gas 才点击,其余一律保守跳过。_preCheckDeploy([kamiId],tile),仅 ok===true 且有gasEstimate才进入第三道;revert、signer 不可用(ok 但无gasEstimate)、异常都打「estimateGas未通过(冷却已排除⇒疑似已部署/读不到结果),跳过省gas」。漏点一轮下轮主循环会重扫。触发:通过冷却预筛后|来历DOM 兜底曾把 API 层已实锤「已部署」的 kami 逐只重发,某账户实测 18 笔 revert 白烧 gas;部署侧宁跳勿发|版本1.2.4 - D1v2 第三道:模拟点击 Start/Onyx Harvest — 通过门禁后点开卡片面板,点击可见的 Start Harvest 或 Onyx Harvest,并记部署冷却。主按钮缺失打「DOM 部署找不到主按钮」;
simulateClick(主按钮,100);1200ms 后在面板找 harvest/start/onyx 图标所在、文本匹配 Start Harvest|Onyx Harvest 且可见的元素simulateClick(…,800),recordAction('startHarvest'),打「单个部署成功(DOM)」(点击即打,非链上确认);每只后delay(800);单只异常打「DOM 部署异常」。触发:通过estimateGas门禁后|参数面板等待 1200ms;只间 800ms|版本1.2.4 - 部署段整体异常隔离 — 整个部署段出错只记日志,不影响后面的喂食和复活。外层 try/catch 打「批量开始 失败:错误」。触发:部署段
部署黑名单
- 部署失败黑名单 — 同一 kami 连续部署失败 2 次拉黑、暂停对其部署,30 分钟后自动解除。三件套:
__blockedKamiIds(名单 Set)、__kamiDeployFailCount(连续失败次数)、__kamiBlockedTime(拉黑时刻);纯内存,刷新即清空不跨会话;window.__blockedKamiIds只读引用供健康看板/控制台直读规模。触发:部署流程失败时写入|参数FAIL_THRESHOLD=2;AUTO_CLEAR_MS=30分钟|命令showBlockedKamis()/clearBlockedKamis()|来历防止对卡在链上的 kami 反复发无效部署 tx 白烧 gas showBlockedKamis查看部署黑名单 — 用表格展示部署黑名单:编号、截断kamiId、拉黑多少分钟前,返回列表。不显示剩余解封时间;为空打印「没有blocked的kami」返回 []。触发:命令|命令showBlockedKamis()
1.6 喂食
两条喂食线:日常喂食给低血的休息 kami 喂 3 种食物;饿死救援给血量 0 的采集中 kami 喂 11 种回血食物,喂活后再决定停不停。
日常低血喂食
- 紧急锁存在跳过喂食,否则调度低血喂食 — 紧急停采进行中时整轮不喂;否则调低血喂食函数,由它自己确认有活才拿锁。
hasEmergencyLock()为真打「紧急锁存在,跳过本轮喂食」;否则 awaitautoFeedLowHpRestingKamis(kamiList)。触发:每轮普通锁释放后|来历候选为 0 的常见轮次不占锁,不挡辅助脚本的合成等模块 - 低血 RESTING 候选收集 — 逐张扫描 kami 卡片,挑出正在休息且掉血够多、值得喂一口食物的 kami。状态看卡片 img[src*='/assets/
kami_'] 的 src 含 resting/harvesting;血量取该图标下一个兄弟元素文本里的 (xx%);用图号在kami_core_db反查 index/kamiId/maxhp(maxhp 缺省按 100),当前 HP=floor(百分比×maxhp),缺口=maxhp-当前HP;只收 RESTING、血量可解析且缺口≥50 的。触发:被runAutomation末尾的低血喂食调度调用(每轮一次)|参数MIN_GAP_BURGER=50|来历最小食物汉堡恢复 50HP,缺口不到 50 喂了就溢出浪费 - 单只喂食失败冷却 — 同一只 kami 喂食连续失败达到次数后,冷却期内静默跳过,不再反复发失败 TX。内存 Map
__feedFailedKamis记 {count,lastFailTime};count≥FEED_MAX_FAILS且未满冷却时长则跳过;冷却期满自动删除记录重新尝试;喂食成功也删记录;失败时 count+1。只对有kamiId的记录生效,脚本重载即清空。触发:候选收集时检查;每次喂食成功/失败时更新|参数FEED_MAX_FAILS=2;FEED_FAIL_COOLDOWN_MS=5分钟|来历kami 卡在 STARVING 等链上异常状态时,避免反复发必败 TX 烧 gas - 单卡解析异常静默跳过 — 某张卡片解析出错时不打断整轮,直接跳过这一张。每张卡片的解析包在 try/catch 里,catch 为空(不打日志)。触发:候选收集循环内
- 无候选早退不拿锁 — 没有需要喂的 kami 时直接结束,不查库存、不拿锁、不发 TX。候选数为 0 时打「✅ 喂食完成,无需喂食」后 return。触发:候选收集结束后|来历多数轮次没有喂食需求,不白占锁挡住辅助脚本的合成等模块
- 钱包地址缺失早退 — 拿不到当前钱包地址时放弃本轮喂食。读
window.network.network.connectedAddress.value_,为空则打「无法获取钱包地址」并 return。触发:查询库存前 - 一次性查询三种食物库存 — 一次读出汉堡、蜜露鳞、金苹果三种食物的库存数量,后面整轮都用这份本地计数。
accounts.getByOperator(addr).inventories 里按item.index找 11302/11312/11313 的 balance;读取抛异常则打日志并 return。代码实际:inventories 不是数组时按空数组处理,余额全算 0,随后落到「库存不足」直接放弃,没有区分「没读到」和「真没货」。触发:有候选时|参数ITEM_CHEESEBURGER=11302(+50);ITEM_HONEYDEW_SCALE=11312(+75);ITEM_GOLDEN_APPLE=11313(+150) - 库存全空早退 — 三种食物加起来为 0 时不拿锁,直接放弃并报出库存数。总数≤0 时打「库存不足:金苹果=…蜜露鳞=…汉堡=…」后 return。触发:查询库存后
- 库存档位预筛(不匹配不取锁) — 先算出手上食物最小能喂多大的缺口,把喂不了的候选筛掉;一只都喂不了就不拿锁,并建议补低档食物。有汉堡时最小档=50,否则有蜜露鳞=75,否则=150;只保留缺口≥最小档的候选;全不匹配时打日志(含库存最小档与候选最大缺口,建议补汉堡/蜜露鳞)后 return。触发:库存非空后、拿锁前|参数
MIN_GAP_BURGER/HONEY/APPLE=50/75/150|来历曾出现只有金苹果的账户 30+ 候选逐只链上复查约 12 分钟,普通锁超时被强制释放 - 先查后锁 + 拿不到普通锁跳过本轮 — 确认真有活干后才申请普通锁;锁被别的模块占着就跳过本轮喂食,不排队等。
tryAcquireNormalLock('feed','core')失败则打「普通锁被占用,跳过本轮喂食」并 return;拿到后打候选数、可匹配数与三种库存。触发:档位预筛通过后|来历避免空转占锁挡住其他 TX 板块 - 分批喂食与批间隔 — 把可匹配的候选按每批 3 只分组依次喂,批与批之间歇 2 秒。按
BATCH_SIZE切片循环;最后一批之后不再等待。触发:拿到普通锁后|参数BATCH_SIZE=3;批次间隔 2000ms - 喂食中紧急锁让路(批前 + 每只前) — 每批开始前、每只喂食前都看一眼紧急锁,发现紧急停采来了就立刻中断喂食,把 TX 通道让出去。
hasEmergencyLock()为真时打「检测到紧急锁,中断喂食」并 break。触发:每批前、每只前|来历紧急停采优先于一切普通操作 - 按缺口匹配食物(不浪费原则) — 缺口大的优先用大食物,恢复量绝不溢出;实在对不上宁可不喂。缺口≥150 且有金苹果→金苹果;否则缺口≥75 且有蜜露鳞→蜜露鳞;否则缺口≥50 且有汉堡→汉堡;都不满足打「无匹配食物,跳过」并 continue(此时还没发任何链上调用,也不走 1.5 秒间隔)。候选本身没有按缺口排序。触发:每只喂食时|参数
MIN_GAP_APPLE=150;MIN_GAP_HONEY=75;MIN_GAP_BURGER=50|来历链上复查挪到匹配之后,避免为注定喂不了的候选浪费一次约 20 秒的getByIndex;大缺口用大食物还能少发 TX - 发 TX 前链上状态复核 — DOM 可能滞后,发喂食 TX 前再用链上数据确认这只还在休息,不在就跳过。
kamis.getByIndex(index,{harvest:true})读 state,非 RESTING 打日志跳过且不计失败。代码实际:这次查询本身抛异常会落进 catch,被计为一次喂食失败(计入熔断和失败冷却)。触发:食物匹配通过后 - 喂食冷却公式预筛 — kami 还在 3 分钟操作冷却里时不发喂食 TX,跳过且不算失败。复用同一次
getByIndex的harvest.time.last,_cooldownRemainSec算剩余秒数,>0 打「冷却中(剩余Ns),跳过」并 continue,不写__feedFailedKamis。time.last读不到按无冷却处理。触发:链上复核为 RESTING 后|参数KAMI_ACTION_COOLDOWN_SEC=180|来历冷却中发 TX 必败,还会误触发喂食熔断,熔断只该记真失败|版本v1.1.10 - 喂食不做
estimateGas预检(有意设计) — 日常喂食刻意不走 gas 预检,直接发 TX。_preCheckTx里也没有 feed 分支;实际喂食调用api.player.pet.item.use(kamiId,itemId)。触发:每只喂食发 TX 前|来历能用的预检路径走system.kami.use.item,签名与封装层 api 不一致,encodeFunctionData必抛UNEXPECTED_ARGUMENT,预检 100% 误报;偶尔失败浪费约 100-500K gas,远好于永远不喂 - 发送喂食 TX — 通过游戏 API 给这只 kami 用上匹配到的食物。打「#N 缺口
xHP→ 食物名」后调用window.network.api.player.pet.item.use(kamiInfo.id, itemId),kamiId取自链上复核结果。触发:通过全部前置检查后 - gas 真值账本记账(日常喂食)仅观测 — 每笔日常喂食 TX 发出后记一笔账,事后按 hash 补真实 gas,归类到喂食动作。
_gasLedgerRecord('feed',[kamiId],tx),只挂钩不改流程。触发:喂食 TX 发出后|命令showGasReport()(定义在别处)|版本1.2.7 - 等待上链 + 无 wait 回退 8 秒 — 等喂食 TX 上链确认;TX 对象没有 wait 方法时固定等 8 秒。typeof
tx.wait==='function' 则 awaittx.wait(),否则delay(8000)。触发:喂食 TX 发出后|参数无 wait 回退延时 8000ms - 本地扣减库存计数 — 喂成功后在本地把对应食物数量减 1,后面的候选直接按新数量选食物,不再重复查链。按所用食物
balApple/balHoney/balBurger--。触发:每只喂食成功后|来历省去重复查链 - 喂食失败只记日志继续 — 单只喂食报错时记下原因和失败次数,接着喂下一只。catch 打「喂食/失败 #N: 错误前60字」,批失败数与总失败数+1,写失败冷却记录。触发:喂食 TX 或链上复核抛异常时
- 相邻喂食 TX 间隔 1.5 秒 — 每喂完一只(成功或失败)歇 1.5 秒再喂下一只。
delay(1500)放在 try/catch 之后;匹配不到食物、链上非 RESTING、冷却中的 continue 不走这个间隔。触发:每只喂食后|参数喂食间隔 1500ms|来历防 nonce 冲突和 RPC 限流 - 单批失败熔断 — 一批里失败达到 2 只,判断为系统性问题,停止后续所有批次。
batchFail≥FAIL_THRESHOLD时打「喂食/熔断 批次N失败x个,疑似系统问题,停止喂食」并 break。触发:每批结束时|参数FAIL_THRESHOLD=2|来历RPC 故障、余额异常等系统性问题下防止连环烧 gas - 批次与整轮喂食小结日志仅观测 — 每批打成功数,整轮结束打总成功/失败数。「喂食/批次N 成功x/y」与「喂食完成,成功x个,失败y个」。触发:每批结束、整轮结束
- finally 必释放喂食普通锁 — 无论喂食中途是熔断、让路还是报错,最后一定释放普通锁。try…finally 里
releaseNormalLock('feed','core')。触发:喂食流程结束 - 喂食失败冷却记录 —
__feedFailedKamis记kamiId→{count,lastFailTime},连续失败 2 次进入 5 分钟冷却,冷却期内不再尝试喂同一只。纯内存,由喂食模块(约 9353 行)读写;调大冷却更省 gas 但恢复更慢。触发:喂食模块失败时写入|参数FEED_FAIL_COOLDOWN_MS=5分钟;FEED_MAX_FAILS=2|命令clearFeedFails()|来历避免反复失败白烧 gas clearFeedFails清空喂食失败记录 — 清空__feedFailedKamis并返回数量。触发:命令|命令clearFeedFails()
每轮库存盘点
- 每轮食物库存盘点日志测试线独有仅观测 — 每轮把账户里 11 种回血食物中数量>0 的列出来(名称+回血量×数量),并单独标出日常喂食能用的是哪几种。
getByOperator(addr).inventories 同步本地读、零成本;食物行格式「名称+HP×数量」,全 0 时写(全部为 0);后缀「日常喂食可用: …(日常只认汉堡/蜜露鳞/金苹果三种;饿死救援认全部 11 种,差异是设计)」。只读只打日志,不改任何判断、不发交易。触发:runAutomation每轮,在喂食的紧急锁判断之前调用(紧急轮次也照打)|参数INV_SNAPSHOT_ITEMS.食物 11 种(Blue Pansy/Ghost Gum/Gingerbread 25,Fetid Egg/Resin 35,Cheeseburger/Pom-Pom 50,Honeydew 75,Gakki/Paeon法术卡 100,Golden Apple 150)|来历0912 查日志想知道账户有什么食物,13 份日志一条都没有(盘点原本只在饿死救援里,那晚救援没跑),导致给出「补汉堡/蜜露鳞」的错误建议,其实账户里有 Pom-Pom 和 Gakki|版本测试版1.2.40 - 日常/救援食物清单差异标注测试线独有仅观测 — 在盘点日志里把「日常喂食只认 3 种、饿死救援认 11 种」直接标出来,一眼分清没喂是因为没货还是这档食物不在日常清单里。
DAILY_FEED_ITEMS= Set(11302,11312,11313),与autoFeedLowHpRestingKamis保持一致,用于过滤出「日常喂食可用」。触发:随每轮库存盘点|参数DAILY_FEED_ITEMS={11302,11312,11313}|来历当时没标清楚,把有意设计当成 bug 改了两版|版本测试版1.2.40 - 库存盘点其他道具行(复活丝带 + 步长道具)测试线独有仅观测 — 同一轮再打一行:复活丝带数量,以及 5 种步长道具的库存。「复活丝带×数量 |步长道具: 名称+值×数量」,步长道具为 Ice Cream 20、Better 40、Best 80、Candyfloss 80、Neith卡 80,全 0 写「无」。触发:随每轮库存盘点|参数复活丝带 item 11001;步长道具 21201~21205|版本测试版1.2.40
- 盘点读不到时区分「没读到」和「空」测试线独有仅观测 — 库存数组读不到时明确写「不是空,是没读到」,避免把读取失败误读成没货;任何异常都不影响主流程。无钱包地址静默 return;inventories 不是数组打「读不到
acc.inventories(不是空,是没读到)」;整体 try/catch,异常打「读取异常(不影响主流程)」+前 80 字。触发:随每轮库存盘点|版本测试版1.2.40 - 库存盘点放在紧急锁判断之前测试线独有仅观测 — 每轮在喂食锁判断之前先打库存盘点,紧急轮次也照打。
_logInventorySnapshot()。触发:每轮停采/部署段之后|来历紧急轮次正是最想知道库存的时候|版本测试版1.2.40
饿死救援 · 主循环入口
- 饿死 kami 地块核对(跨地块只提醒) — 逐只查饿死 kami 在哪个地块采集,不在当前地块的无法喂,打醒目日志请用户手动处理。
getByIndex({harvest:true})读harvest.node.index,与__getMyRoomIndex()不同则进其他地块列表;打橙色「饿死救援/地块不匹配 当前地块: 名称,N 个STARVING kami在其他地块,请手动处理: #编号(地块x)」。触发:停采池中有isStarving候选|来历跨地块发喂食注定失败,只提醒不操作 - 地块读不到时保守保留 — 查地块失败或当前地块未知时,仍把这只饿死 kami 留在救援队列,宁可多试不漏救。当前地块或采集地块为 null 时不判不匹配;
getByIndex抛异常直接进救援队列。触发:地块核对时 - 同地块饿死 kami 批量救援喂食测试线独有 — 对同地块的饿死 kami 先批量喂一轮食物(支持 11 种回血食物,按库存自动选),喂活且确认上链、仍在停采线下的随本轮停采批带走。打橙色「发现 N 个STARVING的kami,需先喂食」,await
_starvingFeedKamis(list,'普通/饿死救援',{yieldToEmergency:true}),开紧急让路、不设 deadline;fedCount>0 打「已喂食 N 个kami,直接进入停采流程」。救援函数喂后复查会原地翻isStarving并更新 delta。触发:救援队列非空|来历0 血 kami 游戏规则要求先喂食回血才能 Stop|版本测试版1.2.45
饿死救援 · 预查与食物分配
- 救援食物表 — 11 种非复活类 HP 恢复食物:Blue Pansy/Ghost Gum/Gingerbread Cookie(+25)、Fetid Egg/Resin(+35)、Cheeseburger/Pom-Pom Candy(+50)、Honeydew Scale(+75)、Gakki Cookie/Paeon Spell Card(+100)、Golden Apple(+150)。表内按 HP 升序便于阅读,选食时降序;复活类道具留给复活模块;增删条目即可调整范围。触发:救援选食时|参数
STARVING_FOOD_LIST|来历救援喂大食物优先,一次到位省 tx|版本1.2.2 - 救援函数整函数互斥 — 普通停采与紧急停采两条路径同时调救援时,先到先做,后到者直接返回 0 并打「另一条救援路径正在喂食同一批,本轮跳过」。
window.__starvingFeedRunning标志,finally 中复位。触发:_starvingFeedKamis入口|来历0825 实盘两路并发,3 只 kami 各被重复喂食一次白烧食物+gas,并加剧 mempool 拥堵;根因是两路富化都在任何喂食落地前完成(TOCTOU)|版本1.2.23 - 救援起始时刻全局标记测试线独有 —
window.__starvingFeedSince记录救援开始时刻,结束清零。供紧急停采的waitForStarvingFeedDrain判断救援进行了多久。触发:_starvingFeedKamis入口/出口|版本测试版1.2.45 - 紧急路径喂食截止时刻测试线独有 — 紧急停采调用时传
deadlineMs(now+90秒),每只发送前检查,到点 break,剩余交下一轮并打「喂食阶段已到时间上限」。日常救援不传 → Infinity,行为逐字节不变;未发送的没记账,下轮会被重新捞起。触发:紧急停采 Step 4.5 调用|参数FEED_PHASE_CAP_MS=90000|来历不让喂食把紧急停采的时间预算吃光|版本测试版1.2.43 - 日常救援遇紧急锁立即让路测试线独有 — 普通路径传
yieldToEmergency:true;每只发送前若出现别人设的紧急锁就 break,打「紧急停采已接管,立即让路,剩余交给紧急停采的饿死救援接手」。只让路不设 deadline;互斥此刻已释放,由紧急停采 Step 4.5 接着喂同一批。触发:普通停采调用_starvingFeedKamis时|来历这是核心里唯一漏做让路的发 tx 循环:停采走 raw、救援走 queue 同时在飞就是双账本 nonce 分叉(0710 定案 46 起撞号);0815 那次 1105 只死亡雪崩根因正是只在循环前查一次锁|版本测试版1.2.45 - 单次库存查询与本地余额表 — 一次
getByOperator取回账户全部 inventories,建立「食物 index→余额」表,只收余额>0 的。之后全程不再逐只查询链上库存。触发:救援 Step 1|来历少 RPC、快速批量救援 - 库存查询异常不放弃(defer) — 库存查询抛错时打「查询库存失败」和「库存读取疑似前端失真,本轮不放弃 starving,defer 到下轮重试」。注释写「直接返回 0,本轮放弃救援」,代码实际同样 return 0,但语义改为 defer(下轮重试)。触发:救援 Step 1 异常|来历实盘上前端失真被当成无库存放弃喂食导致饿死(Bug B)|版本v1.1.25
- 食物全 0:失真 defer / 真空红字告警 — 一种可用食物都没有时:前端冻结或读数可疑(inventories 非数组或空数组)→ defer 不告警;否则红色告警「所有HP恢复食物库存为0,请手动处理」并列出全部候选编号。
invReadSuspicious在读库存时判定。触发:救援 Step 1 后|来历冻结/空数组读数曾走红字放弃整批|版本v1.1.25 - 可用食物摘要日志仅观测 — 打印「🍔 可用食物: 名称(+HP)×余额, …」。纯日志。触发:救援 Step 1 后
- 8 路并发预查(worker 抢号) — 8 个 worker 共享
nextI指针抢号处理每只 kami 的预查,结果按原顺序写回。Promise.all等全部完成。调大更快但 RPC 压力大。触发:救援 Step 2|参数CONC=8|来历避免逐个串行等待 - 补
kamiId两级兜底 — 候选只带dbIndex/imgNumber时先查本地kami_core_db(按imgNumber或 index),查不到再走链上getByIndex(dbIndex,{})取 id,仍拿不到记「无法获取kamiId」跳过。触发:救援 Step 2 - 链上 24h 无 tx 疑似卡链先试喂一次 —
harvest.time.last距今超过 24 小时只算卡链嫌疑:首次照常喂食抢救(标stuck24hTrying),近期试过仍饿着才真跳过。板块注释仍写「超过24小时跳过」,代码实际是先试一次;查询失败不阻断后续。同时把time.last存到 out 上供冷却预筛复用。触发:救援 Step 2|参数STARVING_STUCK_TIME_MS=24小时|来历用户 0825 定案:旧码一次扫描 238 只 STARVING 里 232 只被无差别跳过只喂 6 只;24h 无 tx 也可能是长挂机刚醒、索引滞后,一次喂食成本仅 1 笔 tx+1 个食物。1.2.24 撤销了 1.2.23 的单轮限流,因为 STARVING 是活着的 0 血,不能等下一轮|版本1.2.22 - 卡链试喂闸两档(6h / 30min)测试线独有 —
window.__stuck24hTried记每只 kami 上次试喂:有链上实锤(confirmed)→ 6 小时内不再试;无实锤 → 30 分钟短闸;旧版遗留的裸时间戳一律当未实锤走短闸。window 级 Map,会话内有效,刷新后重来一次可接受。触发:救援 Step 2 判卡链时|参数STUCK_RETRY_COOLDOWN_MS=6小时;STUCK_RETRY_SHORT_MS=30分钟|来历用户 0912 复现:1.2.43 用 Number(tx.status)===0 判 revert,raw 返回无 status、queue 形状未实测,判据在两通道都是死代码,100% 登记 6h;改后判错代价是多试一次而非 0 血 kami 锁死 6 小时|版本测试版1.2.45 - S2 链上检查点 HP 纯观测仅观测 — 每次救援调用至多 1 次查
stats.health,打印 sync/total/keys/time.last(标定用非判据);不可读也打一条statsKeys。尝试即置位防每只每轮空发 RPC;独立 try,异常不拖累主链路。触发:救援 Step 2 首只|来历原链上 HP 闸门发布前被异源审 REJECT 砍掉:sync 是上次 tx 检查点非实时,真饿死的 sync 系统性偏高,闸门会精准误拦最需要喂的;勿复活|版本1.2.2 - 重复喂食熔断(保护2) —
__starvingFedRecord记喂食次数;累计≥2 次且在 30 分钟内的跳过(skipReasonalreadyFedNStuckMm),冷却过期则清记录允许重试。按代码推演:该记录只有本函数写入,且 Step 3 发送前复查会跳过 30 分钟内喂过 1 次的,count≥2 分支在本函数自身写入路径下实际难以触达。喂食路径不读停采黑名单(保持解耦)。触发:救援 Step 2|参数STARVING_STUCK_THRESHOLD=2;STARVING_STUCK_COOLDOWN_MS=30分钟|命令clearStarvingStuck()|来历防止对救不回来的 kami 反复浪费食物和 gas - 逐只停采预检(能停就不喂)已停用 — 旧逻辑:对每只先
_preCheckStop(estimateGas),能停说明实时 HP>0 无需喂食。已砍除。板块注释仍写有这一步,代码已无;stopOk恒 false,「停采预检通过无需喂食」日志与汇总里「API验证HP>0跳过N个」分支成为死代码。来历:用户 0825 定案:196 只×RPC÷8 ≈ 3 分钟,这批 0 血 kami 一口没吃,杀手来就全灭;及时性远大于省几个食物(金苹果库存 1500+)|版本1.2.24 - 单只预查异常隔离 — 单只 kami 预查中任何异常只记
skipReason=preQueryError跳过它自己,不影响其他只。外层 try/catch 包每只。触发:救援 Step 2 - 全量食物预分配 — 发第一笔 tx 前用本地
kami_core_db.maxhp当缺口给全部待喂分配食物:选 HP≤缺口的最大有库存食物(不浪费前提下加血最多);都超缺口取最小有库存的;读不到 maxhp 直接取最大;边分边扣工作库存。零链上调用;板块注释写「按HP降序取第一个有库存」,代码实际是上述不浪费规则。触发:救援 Step 2.5|来历1.2.24 编辑事故此块未落盘,致plannedFood全空、151 只被静默跳过、发送 0,本版补齐|版本1.2.25 - 食物分配报告与不足告警 — 蓝色打印「食物分配完成:N只可喂(食物×数量)」;有没分到的红字「食物不足,另有N只没分到,请尽快补货(Mina 商店)」;否则提示「食物充足,接下来连续发送」。触发:救援 Step 2.5 后|来历0 血 kami 停不了采也躲不了杀手,喂食是唯一救法|版本1.2.25
- 0 分配防复发 guard仅观测 — 有候选却 0 只分到食物时打红色大字🚨「若食物有库存仍见此条,请把日志发给维护者」。决不静默。触发:救援 Step 2.5 后|来历防 1.2.24 那种逻辑坏掉静默不发的事故复发|版本1.2.25
- 无分配食物静默跳过 — 预分配阶段没分到食物的 kami 在发送循环里直接跳过。Step 2.5 已打过食物不足红字。触发:救援 Step 3|版本1.2.24
clearStarvingStuck清空重复喂食熔断 — 清空__starvingFedRecord,立即允许重新喂食,返回数量。不清window.__stuck24hTried(卡链试喂 6h/30min 闸)。触发:命令|命令clearStarvingStuck()|来历手动处理完卡住的 kami 后调用
饿死救援 · 发送通道与 nonce
- 救援喂食通道开关 —
setStarvingFeedChannel('raw'|'queue')切换救援喂食通道,下轮救援生效;默认 queue(MUD 队列 api,稳)。非法参数打印用法与当前值;注释曾写默认 raw,1.2.29 起以localStorage读取默认 'queue' 为准;读取异常也回 queue。触发:命令;救援时读取|命令setStarvingFeedChannel('raw')/setStarvingFeedChannel('queue')|存储kami_starving_feed_channel|来历用户 0825 定案:raw 当晚两翻车(旧地址/nonce 空洞),默认回归 api,raw 留作测试开关,单机实测喂后复查≈发送数后再考虑转正|版本1.2.29 - raw 直发并行喂食 — raw 模式下手拼 calldata(选择器 0xe60f3a76 +
kamiId+ 食物index 各 32 字节)、显式递增 nonce 直接sendTransaction,提交即返回,真正连发。默认不启用,需setStarvingFeedChannel('raw');要求signer.sendTransaction与provider.getTransactionCount可用;不走 ABI 编码。queue 模式下本函数不自取任何锁。触发:救援 Step 3(raw 通道)|参数FEED_RAW_SELECTOR='0xe60f3a76'|命令setStarvingFeedChannel('raw')|存储kami_starving_feed_channel|来历MUD 队列串行,0825 实测 60~75 秒/笔,133 只≈2 小时;raw 同停采批量打法约 1~2 分钟|版本1.2.27 - raw 先加锁→排空→再读 nonce测试线独有 — raw 模式先设紧急锁(若还没有)关死普通通道,再等在飞普通操作让出(最多 30 秒,打「等待普通操作让出通道后再读 nonce」),最后读 pending nonce 作起点。
waitForNormalLockRelease返回值未检查:30 秒后仍被占也会继续读 nonce。救援全程持紧急锁独占 tx 通道。触发:raw 模式初始化|参数等待 30000ms|来历旧顺序先读 nonce 再加锁留有裸窗口,普通操作塞进一笔即抢号整串错位(0710 同型 46 次冲突)|版本测试版1.2.43 - 运行时解析
use.item合约地址 — 从txQueue.systems['system.kami.use.item'].target取当前合约地址;解析不到则本轮回退 MUD 队列并打「绝不打硬编码地址」。FEED_RAW_SELECTOR_ONLY常量定义但未被使用。触发:raw 模式初始化|来历0825 血训:游戏 patch 重新部署系统合约,旧地址成僵尸(写权限注销)157 笔全灭;铁律严禁硬编码系统合约地址|版本1.2.28 - 按真实通道打印发送模式测试线独有仅观测 — 真在 raw 时打「raw直发模式:合约(运行时解析),nonce起点,回退命令」;否则打「走 MUD 队列(api)通道逐笔发送」。纯日志。触发:raw 初始化完成|来历0911 审计:地址解析失败已回退却仍打「raw直发 合约=null」,误导读日志的人|版本测试版1.2.35
- raw 初始化失败回退队列 — raw 初始化任何异常 → 本轮回退 MUD 队列逐笔发送并打日志。触发:raw 模式初始化异常|版本1.2.27
- raw 批
gasLimit实测估算测试线独有 — 每批第一笔estimateGas一次,×1.5 余量并压 4,000,000 地板,全批复用;估不出来直接给 4,000,000,并打估算日志。不逐只估;链上按gasUsed计费,gasLimit给大不浪费。触发:raw 批首笔发送前|参数余量×1.5;地板 4000000|来历0911 实盘:写死 2M 而实际需 2.85M,gasUsed1,998,856 out of gas;同一 kami Ghost Gum 估 2.85M、Gakki Cookie 1.87M 差 52%,只估一次可能恰好估到最便宜的|版本测试版1.2.35 - raw nonce 成功才自增 + 失败重新对账 — 只有
sendTransaction成功后 nonce 才 +1;发送失败时重新读链上 pending nonce 并等 2 秒再继续。触发:raw 发送|参数失败后等待 2000ms|来历0825 实锤:nonce 空洞卡死后续 101 笔;失败不烧号、防空洞防重号|版本1.2.28 - raw 批里法术卡挪到批尾 api 段测试线独有 — raw 批中选中 Paeon Spell Card(11305) 时不当场走 api,登记进待补发列表,整批 raw 发完后由 api 补发段统一发。打「选中法术卡(只有api入口),挪到批尾api段统一发,避免与raw混用nonce」。触发:raw 发送循环|来历在 raw 连发中间插一笔 MUD 队列 tx 会造成两套 nonce 交错分叉|版本测试版1.2.43
- 法术卡 cast / 食物 use 入口分流测试线独有 — 11305 Paeon Spell Card 走
api.player.pet.item.cast,其余食物走 .use;只有确实要走 api 时才检查入口是否可用,不可用打日志跳过该只。触发:救援发送|来历0911 审计:旧守卫无条件挡在 raw 前,raw 根本不用apiFn却因其缺失白白放弃救援|版本测试版1.2.35 - queue 通道 await 实为等上链测试线独有 —
api.player.pet.item.use()实测 722~2148ms 才 resolve,返回 {hash, receipt},没有wait()方法——即等到上链才返回,链上成败可当场判;raw 提交即返回无回执只能事后对账。函数 docstring 仍写「awaitapiFn()仅等 nonce 分配,fire-and-forget」,与 1.2.46 实测不符,以实测为准;每笔另加 300ms 间隔。触发:救援 queue 通道发送|来历藏在共享前提里的错,异源交叉验证纠不出来,只有实盘能纠|版本测试版1.2.46 - 喂食发送节奏 — raw:每笔间隔 150ms,每 10 笔歇 1.2 秒(批量分批);queue:每笔 300ms。调小更快但 nonce 排队冲突风险升高。触发:喂食发送成功后|参数raw 150ms/1200ms;queue 300ms|来历让 nonce 排队稳定|版本1.2.27
饿死救援 · 发送、补发与喂后复查
- 喂食冷却预筛 — 发喂食 tx 前算操作冷却剩余,冷却中跳过(tx 必败),不计失败、不进喂食记录,单独计数,下轮自动重试;打「⏳ 冷却剩余Ns(
time.lastage=Xs),跳过本次喂食」。复用预查已读到的time.last,零额外链上查询(注释写复用 Step 1,实际来自 Step 2 预查)。触发:救援 Step 3 每只|参数KAMI_ACTION_COOLDOWN_SEC=180|版本1.1.11 - 发 tx 前实时复查已喂记录 — 用最新
__starvingFedRecord再查一次,30 分钟内已被喂过就跳过并打「刚刚已被喂过(N秒前),跳过重复喂食」。Step 2 富化那次只是快照,可能已过去几分钟。触发:救援 Step 3 每只|参数STARVING_STUCK_COOLDOWN_MS=30分钟|来历期间可能已被别的路径或上一轮喂过|版本1.2.23 - 卡链抢救性试喂提示仅观测 — 对
stuck24hTrying的 kami 橙色提示「链上已N小时无tx(疑似卡链),先尝试喂食一次抢救;若无效6小时内不再重试,请手动处理」。纯日志。触发:救援 Step 3|来历明确告知用户这是抢救性尝试|版本1.2.22 - 卡链试喂登记:发送成功后按回执三态测试线独有 — 只有 tx 确实交出去才登记试喂:回执 status=0 → 不登记(打「链上revert,不登记冷却,下一轮继续救」);status=1 → 登记 confirmed 走 6h 长闸;拿不到回执(raw)→ 登记未确认走 30min 短闸。读
__feedTx.receipt.status(Number('0x1')===1)。触发:救援发送成功后|来历旧码先登记再发,发送抛错也被锁 6 小时,断网夜整批饿着锁死;0913 控制台实测 api 返回 {hash, receipt}、receipt 已是完整回执、顶层无 status,此前 Number(tx.status)===0 在两通道都是死代码|版本测试版1.2.46 - 喂食 gas 真值账本记账仅观测 — 每笔喂食(主循环与补发)调用
_gasLedgerRecord('feed', [kamiId], tx)。记账不影响发送。触发:喂食发送成功后|版本1.2.7 - 记录喂食 tx hash测试线独有 — 发送成功后把 hash 存到
info.__feedTxHash,补发成功的也存。供喂后复查按 hash 查回执。触发:喂食发送成功后|来历补发的不留 hash 会被复查一律判未落地,转停覆盖率变差|版本测试版1.2.43 - 喂食次数记账与疑似卡链提示 — 发送成功后
__starvingFedRecord次数+1、记时刻;累计≥2 次打红字「已累计喂食N次仍是STARVING,疑似卡链,后续将跳过(冷却30分钟),请手动处理」。触发:喂食发送成功后|参数STARVING_STUCK_THRESHOLD=2 - 本地扣减库存余额 — 每喂一只在
balMap里扣 1,扣到 0 删除,不再重复查链上库存。预分配上线后选食改用plannedFood,balMap扣减之后不再被读取,属残留记账。触发:喂食发送成功后 - 单只发送失败不中断整批测试线独有 — 某只发送抛错时打「喂食tx发送失败」(截断 160 字),登记待 api 补发,继续下一只。失败未写喂食记录,不会被永久丢弃。触发:喂食发送异常|版本测试版1.2.35
- 失败补救段:api 逐只补发一次测试线独有 — 主循环跑完后,对发送失败的与 raw 批挪来的法术卡,用 api 通道逐只再发一次;补发再失败交下一轮,不无限重试;结束打红/绿汇总「补发完成:成功N只,仍失败M只」。补发成功同样记 gas 账本、hash、试喂登记(按回执三态)、喂食次数,每笔间隔 300ms。触发:救援 Step 3 结束且有待补发|来历0911 审计:catch 只打日志就滑走,本轮不再尝试;用户 0825 规矩「饿死的不能等下一轮」、0911「至少把 api 作为备选」;raw 失败多为 nonce 错位/池满,同路重试大概率同样死,api 由队列统一管 nonce|版本测试版1.2.35
- 补发段让路与宽限上限测试线独有 — 补发每只前:日常路径遇别人设的紧急锁则让路 break;超过喂食截止+30 秒宽限则 break 并报剩余只数;api 入口不可用计补发失败。触发:补发循环每只|参数
FEED_TAIL_GRACE_MS=30000|来历补发段也要有上限,不能吃掉紧急停采预算|版本测试版1.2.43 / 测试版1.2.45 - 补发前复查防重复花钱测试线独有 — 补发前再查喂食记录,30 分钟内已被喂过就跳过补发。触发:补发循环每只|来历万一刚才那笔其实发出去了、或别的路径已喂过|版本测试版1.2.35
- 喂后复查·当场转停测试线独有 — 等 20 秒后逐只按 hash 查回执,status===1 才信链上 sync HP:喂活则更新
hpPercent/delta 并把isStarving翻 false,delta≤1 的由紧随其后的停采批带走;没 hash/无回执/status≠1 → 保持isStarving只喂不停,下轮再救。只读 provider(_gasLedgerProvider)绝不发 tx;代码仅在 raw 模式且发送数>0 时执行,默认 queue 通道不做喂后复查;异常打日志不影响已发喂食。触发:救援发送与补发结束后(raw 模式)|参数__postWaitMs=20000|来历sync 是上次 tx 检查点:tx 还堵在池里时读到 sync=100 会误判喂活,把 0 血 kami 塞进停采批必 revert(0825 实测 10/10CALL_EXCEPTION);宁可多饿一轮也不塞进停采批|版本测试版1.2.43 - 喂后复查实锤升 6h 长闸测试线独有 — 喂后复查拿到 status=1 回执的卡链试喂 kami,登记 confirmed:true,之后走 6 小时长闸。触发:喂后复查|参数
STUCK_RETRY_COOLDOWN_MS=6小时|来历确实喂进去却还饿着才算真卡链,防对真卡死的反复烧 gas|版本测试版1.2.45 - 喂后复查结果日志测试线独有仅观测 — 打印未见回执实锤只数(保持只喂不停),以及「N只已确认喂活(M只在停采线下转入本轮停采),K只确认未到账」。纯日志。触发:喂后复查结束|版本测试版1.2.43
- 紧急锁归属令牌释放测试线独有 — 救援只释放自己设的那把紧急锁:比对取锁时保存的对象引用,不是同一把就不动并打「紧急锁已被他人重设,本轮不释放」。注释提到 720s 强制释放兜底;代码里紧急锁的超时自愈实际是
TX_EMERGENCY_TIMEOUT=600000(10 分钟),720s 是过时注释。触发:救援收尾(raw 模式设过锁)|来历持锁期间锁被强制释放后紧急停采重新取锁,旧逻辑只看布尔会掐掉别人正在用的锁|版本测试版1.2.43 - 救援汇总日志与返回值仅观测 — 打印「汇总:N个候选 → 发送M个,冷却预筛跳过K个(下轮重试)」,返回成功交出去的笔数
fedCount。冷却跳过单独展示,便于和其他跳过区分复盘。触发:救援结束|版本1.1.11
1.7 复活
用复活丝带批量复活死亡 kami。和辅助脚本的启动窗口复活(2.6)共用一张 15 分钟防重发表,不会重复花丝带。
- 本轮死亡 kami 批量复活 — 每轮最后把扫描中收集到的死亡 kami 交给批量复活函数一次处理。await
_reviveDeadBatch(__deadToRevive),锁、分批、重试都在函数内部处理;放在停采和喂食之后。触发:每轮runAutomation末尾|来历停采/喂食是保命操作优先;死亡 kami 不会再有进一步损失,垫后不吃亏;逐只复活在慢链下会被自己上一笔长时间占锁挡住 - 复活统一走链上 API 的约定 — 注释约定:复活操作统一通过链上 API 完成,不走 DOM 点击。本段仅为约定注释,实现在复活模块。来历:DOM 复活路径不可靠
- 批量复活主流程(复活丝带) — 发现死亡 kami 后,用复活丝带 Red Ribbon Gummy(#11001) 在一次普通锁内逐只串行发复活 tx,复活成功回 10 HP。
_reviveDeadBatch(deadList):合并名单 → 15 分钟防重发过滤 → 查丝带库存 → 按丝带数截断 → 单轮上限 30 → 查锁 → 逐只发pet.item.use(kamiId,11001)→ finally 释放锁。触发:自动:死亡监控发现死亡时不等主循环,立即 fire-and-forget 调_reviveDeadBatch([]);主循环扫描收尾 await_reviveDeadBatch(__deadToRevive);辅助脚本启动窗口复活是第三路,三路靠锁和防重登记互不重发|参数REVIVE_RIBBON_ID=11001;REVIVE_RETRY_COOLDOWN_MS=15分钟;REVIVE_MAX_PER_ROUND=30|来历复活放在停采/喂食之后:已死的 kami 不会再有损失,保命操作优先 - API 全量查死亡名单补 DOM 漏扫 — 复活名单以链上 API 查到的 state=DEAD 为准,DOM 扫描名单只当底本,把 DOM 漏掉的死亡 kami 补进来。
getByOperator(当前地址) 取acc.kamis过滤 DEAD;按dbIndex去重后追加;补进了几只就打橙色日志「API 全查补充 N 只」。触发:每次调用_reviveDeadBatch时|来历网络慢时 party 列表渲染不全,只信 DOM 会让没渲染出来的死亡 kami 永远进不了复活流程 kamiId三级反查 — 给 API 补进来的死亡 kami 找kamiId:先查本地映射表,再用 API 返回的 id,最后单独调 API 反查。先查window.kami_core_db按 index 找kamiId,其次用k.id,都没有再调explorer.kamis.getByIndex(index);这一步出错静默吞掉,最终还是没有kamiId的就不加入名单。触发:API 全查补充名单时|来历优先走本地映射表,省一次网络请求- API 全查失败降级只按 DOM 名单 — API 查死亡名单出错时不中断,改为只处理 DOM 扫描出来的名单。整段 try/catch,报错时打「API 死亡全查失败…仅按 DOM 扫描名单处理」;合并后名单为空就直接 return。触发:
getByOperator等调用抛异常时 - 复活 15 分钟防重发冷却 — 同一只 kami 发出复活 tx 后 15 分钟内不再重发,防止慢链确认期间重复消耗丝带。
window.__reviveSentAt是 Map<kamiId,发送时间>;过滤掉距上次发送不足 15 分钟的,并打「N 只在 15 分钟内已发过复活tx(慢链确认中),本轮不重发」;全被过滤就 return。登记只在内存,刷新页面清空;已存在的 Map 不覆盖(跨调用共享)。触发:每次复活前|参数REVIVE_RETRY_COOLDOWN_MS=15*60*1000|命令window.__reviveSentAt.clear()|来历慢链下tx.wait会卡几十秒,不防重就会对同一只重复发 tx、重复烧丝带 - 复活防重登记清空命令 — 手动清空复活防重登记,让刚发过复活 tx 的 kami 可以立刻重发。控制台执行
window.__reviveSentAt.clear();适用于确认丝带没被消耗、需要马上重发的情况。触发:命令|命令window.__reviveSentAt.clear() - 丝带为 0 时红字提醒购买且不发 tx — 背包里一个复活丝带都没有时,大号红字提醒去 Mina 商店购买,本轮一笔 tx 都不发。从
acc.inventories找item.index=11001 的 balance;≤0 时打 14px 红字(GDA 浮动价,基价约 100 musu)后 return。触发:每次复活流程查库存时|参数REVIVE_RIBBON_ID=11001|来历防止每轮白发注定失败的 tx 烧 gas - 丝带库存查询失败按有货处理 — 查不到丝带库存时,保守地假设丝带够用,照样尝试复活。catch 里把 ribbons 设成待复活数量并打日志「保守按有货尝试」。触发:库存查询抛异常时|来历宁可多发一笔 tx,也不漏掉复活
- 丝带不足时截断名单并提醒补货 — 丝带比死亡 kami 少时,本轮只复活名单前 N 只(N=丝带余额),并提示补货。
fresh.slice(0, ribbons);名单顺序没有排序,就是 DOM 名单在前、API 补充的在后;截断时打橙色「丝带仅 X 个 < 死亡 Y 只…请补货」。触发:丝带余额小于待复活数时 - 单轮复活上限 30 只(限流分轮) — 每轮最多复活 30 只,剩下的交给下一轮死亡扫描(约 3 分钟后)接着救,避免一次性长时间独占 TX 锁。
byRibbon.slice(0, 30);超出时打三行说明:本轮救几只、为什么限流、剩几只预计几轮救完(丝带不浪费、不会重复发 tx、无需人工干预)。触发:待复活数超过 30 时|参数REVIVE_MAX_PER_ROUND=30|来历0815 实盘 1105 只死亡雪崩:一次复活 215 只≈215 笔 tx、10~18 分钟独占普通锁,喂食/部署/停采/拾荒全被卡住,反而害死更多 kami;分轮后每轮锁占用降到约 1.5 分钟|版本1.2.21 - 复活前检查紧急锁与普通锁 — 准备发复活 tx 之前,有紧急锁或普通锁被占,就整轮跳过,下轮再试。
hasEmergencyLock()为真时打「紧急锁存在,本轮跳过复活」;tryAcquireNormalLock('revive','core')抢不到时打「普通锁被占用(可能上一轮复活tx仍在确认)」。触发:每轮进入发 tx 阶段前 - 复活循环逐笔让路紧急停采 — 每发一笔复活前都查一次紧急锁,一旦杀手来袭触发紧急停采,立刻中断剩余复活让出 TX 通道。循环内
hasEmergencyLock()为真就打橙色让路日志(已复活几只、剩余几只延后)后 break。注释写「已复活」,代码实际__revDone在发 tx 前就自增,统计的是已尝试发送数,包含失败的。触发:复活循环每只 kami 发送前|来历0815 雪崩:旧码只在循环开始前查一次紧急锁,实测紧急停采干等复活跑完卡死 353 秒,该停的没停继续被杀;紧急停采救的是活着的,复活的已经死了,多等一轮零损失|版本1.2.20 - 复活冷却观察日志(180s 窗)仅观测 — 只打日志不改逻辑:记录每只死亡 kami 的
harvest.time.last距今多久、有没有落在 180 秒操作冷却窗内,以及复活成败,用来统计复活受不受冷却影响。发 tx 前每只额外调一次getByIndex(index,{harvest:true});有值打「age=Xs(⚠️在180s冷却窗内/已过冷却窗)」,查不到打「查不到time.last」;发完再打「age=Xs 时复活结果=成功/失败(原因)」,age 沿用发送前查到的值、不再重查(省一次 RPC)。查询失败不影响复活。触发:复活循环每只 kami|参数冷却窗 180s(写死在日志判断里)|来历复活对象是 DEAD kami,它的time.last是否受同一冷却计时器约束没有实盘验证,所以只观察、不预筛,留待 grep 统计后再决定要不要加预筛|版本1.1.11 - 发送前登记、发送异常撤销登记 — 发复活 tx 之前先登记防重;如果发送本身抛异常(tx 没发出去),就删掉登记,允许下轮立刻重试。
__reviveSentAt.set在use()之前;catch 里__reviveSentAt.delete并打红字「发送失败」。触发:每只 kami 发复活 tx 时|来历防止同一轮或下一轮对同一只重发 - 复活确认 45 秒超时(三态结果) — 等复活 tx 上链确认最多 45 秒,超时只记日志不卡整批,下轮死亡监控会自动核查。
Promise.race(tx.wait→'ok'/'revert', 45s→'timeout')。ok 打绿字成功;timeout 打「tx已发出但确认超时(慢链),15分钟内不重发」且不删登记;revert 打红字执行失败。触发:tx 对象带 wait 函数时|参数确认超时 45000ms|来历慢链下tx.wait可能卡几十秒,不加超时会拖慢整批复活 - 无 wait 函数时按已发出视为成功 — API 返回的 tx 对象没有 wait 方法时,直接当作复活 tx 已发出,算成功。typeof tx?.wait !== 'function' 时打「复活tx已发出」,结果标注「(已发出未confirm)」。触发:
use()返回值不带 wait 时 - 复活 gas 账本记账 — 每笔复活 tx 记进 gas 真值账本,分类为 revive。
_gasLedgerRecord('revive', [kamiId], tx),拿到 hash 后由账本后台补回执算 gas。触发:每笔复活 tx 发出后|命令showGasReport()(账本报告,定义在别处)|版本1.2.7 - 复活间隔 1.2 秒 — 相邻两只复活 tx 之间固定间隔 1.2 秒。每只处理完 await
delay(1200)。触发:复活循环每只之后|参数delay(1200)|来历配合锁,防止 nonce 冲突 - 复活锁在 finally 中必定释放 — 无论复活成功、失败还是中途异常,普通锁都会被释放。try/finally 里执行
releaseNormalLock('revive','core')。触发:复活流程结束时|来历避免锁被带走,卡死其他模块
1.8 拾荒
采集点的拾荒次数(rolls)攒到 1000 次自动领取。
- 自动拾荒领取主流程 — 打开当前采集点 Node 面板读累计拾荒次数 rolls,攒到 1000 次以上就点 Scavenge 按钮,一笔 tx 领取全部奖励。等 5 分钟 → 查紧急锁 → 点 Harvest 按钮开面板 → 解析 rolls → 未达门槛关面板返回 → 抢普通锁点 Scavenge → 释放锁 → 关面板。注释写「由主循环按周期调用」,代码实际只在页面加载的启动序列里调用一次(页面约每 45 分钟+随机刷新)。触发:自动:启动序列里
autoScavenge(),每次页面加载跑一轮|参数rolls 门槛=1000(写死)|来历攒一大批再一次领取 = 减少 TX 次数、省 gas - 拾荒模块健康心跳仅观测 — 模块启动时写一个存在性时间戳,供辅助脚本的健康看板判断模块是否在运行。
window.__kamiHealthBeats['拾荒']=Date.now()(跨脚本全局变量)。触发:autoScavenge入口(每次页面加载一次) - 拾荒入口固定等 5 分钟错峰 — 拾荒流程启动后先等 5 分钟再动作,和其他模块的启动时序错开。await
delay(5*60*1000)。触发:autoScavenge入口|参数delay(5*60*1000) - 拾荒遇紧急锁整轮跳过 — 有紧急锁时本轮不拾荒。
hasEmergencyLock()为真时打「紧急锁存在,跳过拾荒」后 return。触发:等待 5 分钟结束后 - 拾荒先查后锁 — 开面板、读 rolls 都是纯 DOM 读取,不占锁;只有真正点 Scavenge 发 tx 时才进普通锁。锁在 rolls≥1000 之后才申请。触发:每轮拾荒|来历rolls 不够时不白占一趟锁,免得挡住合成、喂食等模块
- 按图标路径识别 Harvest 按钮 — Harvest 按钮没有稳定的 id/class,靠按钮里图片地址匹配 assets/harvest-*.png 找到它并点击,打开 Node 面板。#
node_button下的 button,img.src匹配正则;找不到打「未找到 Harvest 按钮」后 return;点完等 500ms 渲染。触发:每轮拾荒|参数delay(500) - 面板未打开直接放弃 — #node 面板不存在或处于隐藏状态时,本轮直接放弃,不报错也不重试。!node ||
node.style.display==='none' 时 return。触发:点击 Harvest 后 - rolls 解析与失败按 0 处理 — 从 Scavenge 按钮同容器里的文本「N rolls」解析累计次数,解析不出来就按 0 算,自然走未达门槛分支。找文本正好是 Scavenge 的按钮 → 其父容器里第一个 div → 正则 ^(\d+)\s*rolls;按钮不存在时 rolls 也是 0,所以不会对空按钮发 tx。触发:每轮拾荒
- rolls 未满 1000 跳过并关面板 — rolls 小于 1000 时本轮不领取,关掉面板。打「rolls=N < 1000 未到领取门槛,本次跳过(未占锁)」→ 关面板 → 等 500ms → return。触发:rolls<1000|参数门槛 1000|来历门槛调小领取更频繁、更费 gas;调大更省 tx,但奖励积压更久
- 拾荒普通锁被占时跳过 — 要发 tx 时普通锁被别的模块占着,本轮不拾荒,关面板等下次。
tryAcquireNormalLock('scavenge','core')失败时打「普通锁被占用,跳过拾荒」→ 关面板 → return。触发:rolls≥1000 时 - 点击 Scavenge 发 tx 并记账(只计次数) — 点击 Scavenge 按钮领取奖励,并在 gas 账本里记一次拾荒动作。
scavengeBtn.click();_gasLedgerRecord('scavenge', [], null)因为是 UI 点击拿不到 tx/hash,只计次数,gas 无法回填;日志「本次自动拾荒完成,rolls: N」;等 500ms 后在 finally 释放锁。锁只覆盖点击后约 500ms,不覆盖 tx 上链确认。触发:拿到普通锁后|版本1.2.7 - 关闭 Node 面板还原界面 — 领取完或跳过时,点面板里文字为 X 的按钮关掉 Node 面板。
closeNodePanel()找textContent==='X' 的按钮点击,并打「关闭弹窗」日志;找不到按钮不报错。触发:跳过或领取结束时|参数delay(500)
1.9 合成与 XP 药水
XP 药水总控在一次锁里做完三件事:合成 Greater XP Potion → 给高清算线 kami 喂 XP 药水 → 凑批合成松花粉。步长不够就等它随时间恢复,不吃步长道具(步长道具要花钱;测试线 1.2.36~1.2.55 曾临时自动吃道具,1.2.56 已移除)。
XP 药水总控流程
- XP 药水总控流程(合成→喂食→凑批) — 一次普通锁占用内串行完成三件事:合成 Greater XP Potion、喂 XP 药水、凑批合成松花粉,最后关掉 Crafting 窗口。查紧急锁 → 无锁预判 → 拿锁 → 锁内补步长 → Greater 合成 →
autoFeedXPPotion→ 重读背包凑批 Pine Pollen → 关窗 → finally 释放锁。注释写「主循环约每 30 分钟一轮、可控制台手动执行」,代码实际只在页面加载的启动序列里调用一次,且没有挂到 window。触发:自动:启动序列autoXPPotionFlow(),每次页面加载跑一轮|来历三件事共用一次锁,减少锁争抢 - XP 流程健康心跳仅观测 — 模块启动时写存在性时间戳,供辅助脚本健康看板判活,并打启动日志。
window.__kamiHealthBeats['XP流程']=Date.now();打「启动 XP 药水总控流程」。触发:autoXPPotionFlow入口 - XP 流程遇紧急锁整轮让路 — 紧急停采等高优先级流程占着 TX 通道时,整轮 XP 流程直接跳过。
hasEmergencyLock()为真时打「紧急锁存在,跳过XP Potion流程」后 return。触发:入口 - 先查后锁预判与诊断早退 — 先无锁读步长和背包,预判合成、喂食、补步长是否至少有一件可能要做;全都不满足就不占锁,直接打两张诊断卡返回。
_greaterOk=步长≥50且松花粉≥2500且罐≥1且炉≥1;_pollenOk=步长≥100且松果≥10且研磨器≥1;_feedMaybe见单独条目;再加补步长两项。五项全假时打「合成/喂食条件均不满足,本轮不占锁」+ Greater 诊断 + 凑批不足提示 + Pine Pollen 诊断。触发:每轮总控|来历条件全不满足是最常见的轮次,先拿锁再检查会白占普通锁、挡住其他模块的 TX - 喂食可能性粗筛(阈值引用常量) — 背包有可喂药水(Fortified>0 或 Greater 超出保留量),且数据库存在 LT>70 的记录,就认为可能要喂、值得拿锁。阈值直接引用
XP_POTION_LT_THRESHOLD和GREATER_RESERVE;这里不做持有过滤。触发:无锁预判区|参数XP_POTION_LT_THRESHOLD=70;GREATER_RESERVE=3|来历粗筛阈值若比正式喂食更严,落在两个阈值之间的 kami 会被判「无事可做」不拿锁,永远轮不到喂食 - XP 流程普通锁被占时跳过 — 拿不到普通锁说明别的模块正在发 TX,本轮跳过。
tryAcquireNormalLock('xp_potion','core')失败时打「普通锁被占用,跳过XP Potion流程」。触发:预判有活干之后|来历与其他发 TX 的模块互斥,防 nonce 冲突 - Greater XP Potion 合成门槛与不满足诊断 — 步长≥50、松花粉≥2500、玻璃罐≥1、便携炉≥1 时合成 1 瓶 Greater;否则打红字跳过并打诊断卡。材料判断用的是拿锁前的背包快照 items(锁内复查的库存只用于补步长判断),步长用可能已补过的本地值。触发:锁内|参数门槛写死:步长50/松花粉2500/罐1/炉1
- 合成确认成功才本地扣 50 步长(A09)测试线独有 — Greater 合成确认成功时,本流程内的步长才减 50,失败不扣。
_craftOk为 true 时 stamina=max(0, stamina-50)。触发:Greater 合成后|来历官方队列sendTx等成功回执才 resolve、失败抛错,没抛即成功|版本测试版1.2.37 - Greater 合成后等 30 秒 — 执行过 Greater 合成(无论成败)后固定等 30 秒,让链上库存和状态刷新,再进喂食环节。await
delay(30000)。触发:执行过 Greater 合成时|参数delay(30000) - 凑批前重读背包、步长不重读 DOM(A09)测试线独有 — 合成和喂食后重新读一次背包,但步长不重读 DOM,沿用本流程内已确认的增减(Greater 合成成功 -50)。合成后 DOM 步长滞后在偏高一侧,重读或取较大值会高估步长,让松花粉凑批合成白发一笔必失败的 tx。触发:Greater 合成与喂食之后|版本测试版1.2.37
- Pine Pollen 固定 10 次一笔凑批合成 — 步长≥100、松果≥10、研磨器≥1 时,单笔 tx 执行 10 次合成(10 松果 + 100 步长 → 5000 松花粉);不够就等下轮,不拆小批。
account.item.craft(6,10);成功打红色粗体日志并等 3 秒;失败只记日志;不足时打「凑批条件不足,跳过等下轮(步长 x/100,松果 y)」+ Pine Pollen 诊断卡。诊断卡按单次配方判定(松果≥1、步长≥10),可能卡片全 ✅ 但凑批仍被跳过。触发:锁内,喂食之后|参数POLLEN_BATCH=10;POLLEN_BATCH_STAM=100;成功后delay(3000)|来历拆小批 = 同样步长变多笔 tx、多付固定 gas;松花粉是囤积材料,不赶时间,减少 TX 优先 - Pine Pollen 合成 gas 补记账 — 松花粉凑批合成 tx 记进 gas 账本,分类为 craft。
_gasLedgerRecord('craft', [6], __ptx)。触发:凑批合成 tx 返回后|来历原来这笔从不入账本(ChatGPT审计 A14)|版本1.2.37(A14) - 合成后关闭 Crafting 窗口 — 合成走 API 后页面可能残留打开的 Crafting 面板,找到其中的 X 按钮点掉。#crafting 下文本为 X 的按钮用
simulateClick点击,打「已关闭 Crafting 窗口」;找不到只打「未找到 Crafting 关闭按钮」,不报错。触发:锁内流程末尾 - XP 流程锁在 finally 中必定释放 — 无论正常结束还是中途异常,都释放
xp_potion普通锁。try/finally 里执行releaseNormalLock('xp_potion','core')。触发:流程结束|来历避免锁泄漏卡死其他模块
XP 药水自动喂食
- XP 药水按 LT 降序自动轮喂 — 给 LT>70% 的高价值 kami 喂 XP 药水加速升级,稀缺药水按 LT 从高到低优先投放,Fortified 优先、Greater 兜底。依次打印规则 → 幽灵过滤 → LT 筛选 → 剔除杀手 → 轮喂去重/重置 → 算可用库存 → 逐只查状态和冷却后发
pet.item.use→ 收尾统计。循环内不复查紧急锁。注释说可在控制台手动执行autoFeedXPPotion(),代码实际没有挂到 window,手动请用feedXPPotionNow()。触发:被autoXPPotionFlow(合成 Greater 之后)和feedXPPotionNow调用|参数XP_POTION_LT_THRESHOLD=70;GREATER_RESERVE=3|命令feedXPPotionNow()|存储kami_xp_potion_fed|来历LT 越高越扛饿、单周期采集越久、价值越高,所以按 LT 降序投放 - 喂食规则速览打印仅观测 — 每次喂食入口打印 7 条规则,解释背包有药水为什么有时不喂,并附强制重喂方法。
_printXPPotionRules():LT 门槛、持有过滤、仅 RESTING、Fortified>Greater、Greater 保留数、轮喂去重、杀手保护(带当前只数),全部合并为一次 log。触发:autoFeedXPPotion入口|命令clearXPPotionFed() - 幽灵 kami 过滤 — 只喂当前账户链上实际持有的 kami,跳过数据库里已转走或卖掉的记录。
getByOperator取acc.kamis的 index 集合,打「当前账户实时持有 N 只 kami,数据库 M 条」;查询失败打「退化为不过滤」并继续(宁可多试一只也不中断整轮)。触发:每次自动喂食|来历对幽灵 kami 发喂食 tx 必然失败,白烧 gas - LT 门槛筛选与无目标早退 — 从精简数据库筛出 LT 是数字且大于 70 的 kami,一只都没有就跳过喂食。typeof LT!=='number' 或 LT≤70 的排除;为空时打「没有LT>70%的Kami,跳过XP Potion喂食」。触发:每次自动喂食|参数
XP_POTION_LT_THRESHOLD=70 - 全部喂过自动开新一轮 — 候选按 LT 降序,只取本轮没喂过的;达标 kami 都喂过一轮后,自动清空去重记录开始新一轮,杀手依然排除在外。候选为空时打青色「已全部喂过一轮,自动重置开始新一轮」,
removeItem去重 key,候选改用全部非杀手 kami。触发:候选为 0 时|存储kami_xp_potion_fed - 可用药水库存计算与无货早退 — 算出本轮能喂的药水数:Fortified 全部可用,Greater 只算超出保留 3 瓶的部分;两者合计为 0 就跳过。
fetchInventoryItems取库存;为 0 时打「背包中无可用XP Potion(Fortified: x, Greater: y, 保留3个用于合成)」;有货时打开始日志(Fortified×、Greater 可用×、待喂几只)。触发:每次自动喂食|参数GREATER_RESERVE=3 - 药水优先级 Fortified > Greater,用完即停 — 每喂一只前选药水:先用 Fortified,没了再用 Greater,Greater 降到保留线就停止喂食。
balFortified>0 用 11411;否则balGreater>3 用 11402;都不满足打「Potion已用完…停止喂食」后 break。触发:喂食循环每只之前|参数ITEM_FORTIFIED_XP=11411;ITEM_GREATER_XP=11402 - 只喂 RESTING 状态的 kami — 喂之前查 kami 实时状态,只有 RESTING 才喂;采集中、死亡、升级中一律跳过并计数。
getByIndex(index,{harvest:true})的 state 不是 RESTING 就skippedNotResting++ 并 continue(跳过时不走 3 秒间隔)。触发:喂食循环每只|来历HARVESTING 中喂食会影响采集参数,DEAD/升级中喂不进去 - 180 秒操作冷却预筛 — 复用同一次状态查询带回的
harvest.time.last,判断 kami 是否还在 180 秒操作冷却内,冷却中不发 tx 直接跳过。_cooldownRemainSec(time.last)> 0 时打「#x 冷却中(剩余Ns),跳过」,skippedCooldown++ 后 continue;time.last读不到按无冷却处理。不新增查询。触发:喂食循环每只(状态为 RESTING 后)|参数KAMI_ACTION_COOLDOWN_SEC=180|来历冷却中发 tx 必定失败|版本v1.1.10 - 发喂食 tx、记账、立即写去重并扣本地库存 — 对选中的 kami 发喂食 tx,记进 gas 账本,立刻写入轮喂记录,并在本地扣减药水计数。先打「喂食 X 给 Kami #n (LT),剩余 F/G」,再发
pet.item.use(kamiId, 物品);_gasLedgerRecord('xp_potion', [kamiId], tx);_saveXPPotionFed在 tx 返回后立刻写;本地对应药水计数减 1,打绿色成功日志。tx 返回即算成功,不等上链回执。触发:喂食循环每只通过状态和冷却检查后|存储kami_xp_potion_fed|来历立即写记录:即使后续流程中断,这只也不会被重复喂|版本1.2.7 - 单只失败不连坐 — 某只 kami 状态查询或喂食 tx 出错时,只记日志,继续喂下一只。每只包在 try/catch,打「喂食失败:Kami #n」。若写去重记录的
setItem抛错,也会走这里:此时 tx 已发出,但本地库存没扣、fedCount没加。触发:喂食循环每只异常时 - 喂食间隔 3 秒节流 — 每喂一只(含失败)后固定等 3 秒。await
delay(3000);被 continue 跳过的非 RESTING 和冷却中的 kami 不等。触发:喂食循环每只之后|参数delay(3000)|来历降低 TX 过密引发 nonce 冲突的风险 - 喂食收尾统计与 0 喂食原因提示仅观测 — 喂完打印统计:因非 RESTING 跳过几只、因冷却跳过几只、共喂几只;有候选却一只没喂时给出原因提示。分别打「非RESTING=N」「冷却中=N(下轮自动重试)」「共喂 N 个kami」;
fedCount=0 且有候选时打黄字「候选全部不在 RESTING 状态…等下一轮(30 分钟后)」。代码实际只要 0 喂食就打这句(冷却跳过或发送失败也会触发);实际下一轮是下次页面加载,不是 30 分钟后。触发:喂食循环结束|版本v1.1.10(冷却计数) - 立即手动喂 XP 药水命令 — 控制台立即触发一次 XP 药水喂食,只喂不合成(不合成 Greater 也不合成松花粉),沿用自动喂食的全部规则。
feedXPPotionNow():打青色启动日志 → 紧急锁存在就让路返回 →tryAcquireNormalLock('xp_potion','manual')拿不到就让路返回(不排队不重试)→ awaitautoFeedXPPotion()→ finally 释放锁。已挂到 window;没有列入wrapManual手动命令标记列表。想重喂已喂过的 kami,先clearXPPotionFed()再调它。触发:命令|命令feedXPPotionNow();配合clearXPPotionFed()|存储kami_xp_potion_fed
喂食记录与参数
- XP 药水喂食去重记录(持久化) — 在
localStorage记下本轮已经喂过 XP 药水的kamiId(Fortified 和 Greater 共用一份),跨刷新保留。_getXPPotionFedSet()读取 JSON 解析成 Set,异常返回空 Set;_saveXPPotionFed把kamiId转字符串加入后整体写回。注释写「每只 kami 终生只喂一次」,代码实际全部喂完一轮会自动清空重来(按轮去重);注释写「读写全部包 try/catch」,代码实际写入的setItem没有包 try。触发:自动喂食挑候选时读,每喂一只写|命令clearXPPotionFed()|存储kami_xp_potion_fed|来历kamiId统一转字符串存,防数字/字符串混型导致查重失效 - 旧记录 key 自动迁移 — 脚本载入时,把旧 key
kami_fortified_fed的数据迁到新 keykami_xp_potion_fed,并删除旧 key。只有旧 key 有数据、新 key 为空时才迁移,避免覆盖已有新记录;异常静默。触发:脚本载入时执行一次|存储kami_fortified_fed→kami_xp_potion_fed - 清空 XP 药水喂食记录命令 — 清空全部 XP 药水喂食记录,所有 kami 重新变成可喂。控制台
clearXPPotionFed(),旧名clearFortifiedFed()指向同一个函数;删除localStoragekey 并打印清除了多少条。两个名字都在手动命令标记列表里。触发:命令|命令clearXPPotionFed();clearFortifiedFed()(旧名兼容)|存储kami_xp_potion_fed - XP 药水物品 ID 常量 — 定义 Fortified XP Potion 和 Greater XP Potion 的链上物品 ID。链上固定值,勿改。触发:喂食时选物品|参数
ITEM_FORTIFIED_XP=11411;ITEM_GREATER_XP=11402 - XP 药水喂食 LT 门槛 70 — 只有清算线 LT 高于 70% 的 kami 才参与 XP 药水轮喂。精确清算线刻度(由旧公式刻度的 70% 换算);调高喂得更少更保守,调低时药水摊给本就安全的低 LT kami,性价比更低。触发:自动喂食筛选和总控预判|参数
XP_POTION_LT_THRESHOLD=70|来历升级能降 LT,所以优先喂高清算线 kami - Greater XP Potion 保留 3 瓶 — Greater XP Potion 始终保留 3 瓶作为合成 Fortified 的原料,超出部分才拿去喂。可喂数 = max(0, 库存-3);循环中库存降到 ≤3 就停喂 Greater。触发:喂食和总控预判|参数
GREATER_RESERVE=3
杀手保护清单
- 杀手 kami 免喂 XP 药水保护清单 — 维护一份杀手 kami 编号集合,XP 药水喂食自动跳过其中的 kami,保住手调的技能 build。
window.MY_KILLER_KAMIS是全局 Set,默认为空;已被其他脚本或控制台定义时沿用、不覆盖。喂食时按 Number(index) 命中就跳过,并打「跳过我的杀手 N 只: #编号(LT=x%)」。代码只认 Set 类型,赋成数组等其他类型会被当作空集。需要和辅助脚本的RESET_WHITELIST手动保持一致,长期生效要改两个脚本的源码。触发:脚本载入初始化;每轮喂食前读取|参数window.MY_KILLER_KAMIS=new Set([])|命令window.MY_KILLER_KAMIS= new Set([编号...])|来历喂 XP 药水会升级,升级重新分配技能点,会冲掉专为清算/攻击精调的 build - 查看杀手清单命令 — 打印当前杀手保护清单和修改方法提示。控制台
showMyKillers();清单为空时提示「XP Potion 喂食对所有 kami 生效」;否则列出编号,并提示可 .add/.delete、长期生效需同时改核心和辅助两处。已列入手动命令标记列表。触发:命令|命令showMyKillers() - 临时增删杀手清单 — 在控制台临时增删保护清单成员,只在当前会话有效,刷新后恢复脚本里写的清单。
window.MY_KILLER_KAMIS.add(编号) / .delete(编号)。触发:命令|命令window.MY_KILLER_KAMIS.add(编号);window.MY_KILLER_KAMIS.delete(编号)
合成配方与材料诊断
- 步长只认 DOM 实时值(
getStamina) — 读取账户当前步长,只信页面 #MyPath步长条的实时值,读不到就返回 0,本轮跳过合成。调辅助脚本暴露的window.getStaminaFromDOM(),返回有限数值就用;否则打「DOM 步长读取失败,本轮按 0 处理(跳过合成,不用陈旧 API sync 值)」并返回 0。触发:被autoXPPotionFlow调用|来历API 的acc.stamina.sync是上次链上动作的检查点旧值,不随时间自然回复,直接用会严重偏离真实体力、导致合成超扣 - 背包合成链物品查询(
fetchInventoryItems) — 查询背包里合成链相关 7 种物品的数量:松果、松花粉、玻璃罐、香料磨、便携炉、Greater XP Potion、Fortified XP Potion。getByOperator取 inventories,物品名转小写后经nameMap映射成内部 key,返回 {key:数量};不在映射表里的物品忽略;查询失败打「获取背包物品失败」并返回 {}(上层当库存全 0,不会触发合成)。触发:被 XP 总控流程、自动喂食、诊断调用|参数nameMap(显示名→内部 key,新增物品在此登记) - 合成 Greater XP Potion 并返回成败测试线独有 — 调链上合成接口合成 1 瓶 Greater XP Potion,返回是否成功,供调用方决定本地扣不扣 50 步长。
account.item.craft(2,1)(配方 2=Greater,执行 1 次);没抛异常就算成功(官方队列等到成功回执才 resolve),打红色粗体成功日志,等 3 秒返回 true;失败打日志返回 false,不重试。触发:被autoXPPotionFlow调用|参数craft(2,1);成功后delay(3000)|来历原来没有返回值,调用方拿到 undefined 永远当失败,步长本地不扣、被低估,白白跳过后续合成(A09)|版本测试版1.2.37 - Greater 合成 gas 补记账 — Greater XP Potion 合成 tx 记进 gas 账本,分类为 craft。
_gasLedgerRecord('craft', [2], __tx)。触发:每次 Greater 合成 tx 返回后|来历ChatGPT审计 A14 指出核心两处合成从没记账,导致「账本攒够一天就当完整」的判据建立在残缺样本上|版本1.2.37(A14) - 合成配方真值表 — 定义 XP 药水合成链的链上实测配方:Greater XP Potion 每次产 1 瓶、耗 50 步长、消耗松花粉 2500 和玻璃罐 1、工具便携炉 1;Pine Pollen 每次产 500、耗 10 步长、消耗松果 1、工具香料磨 1;测试版 1.2.58 给 Pine Pollen 加
batch=10(主流程凑满 10 次才合成,诊断按一批核对)。RECIPE_DEFS:consumes 按合成次数倍数消耗,tools 只要持有量达到阈值、不随次数累加。链上craft(6,10)= 执行 10 次 = 10 松果 + 100 步长 → 5000 松花粉。触发:诊断函数读取|参数greater_xp_potion:{out=1,stam=50,pollen 2500,jar 1,burner 1};pine_pollen:{out=500,stam=10,cone 1,grinder 1} - 玻璃罐循环容器语义仅观测 — 玻璃罐合成时每瓶占 1 个(库存确实会减,所以算消耗品),但喂掉 Greater/Fortified 药水后会返还,不是净消耗。标签写「玻璃罐·喂食XP药水后返还」。注释说缺罐应先喂药水回收,但代码里罐没有配方,实际仍会被列进 🛒 采购清单。触发:诊断输出测试版 1.2.58 起诊断里缺玻璃罐提示「喂 Greater/Fortified XP 药水会返还空瓶;背包没药水就去市场买或拾荒」,不再写需采购。|来历缺罐时应优先喂掉库存药水回收空瓶,而不是买新瓶
- 合成诊断卡片仅观测 — 合成条件不足时打印一张完整诊断卡:体力缺口、每项消耗品/工具 ✅❌、短缺的递归追溯、最底层补货清单。
_diagnoseCraft(配方key, items, stamina, crafts=1);所有行合并成一次 log,避免每行重复时间戳和来源链接;未知配方直接 return。顶层短缺中,消耗品会调_traceDeficit下钻,工具只进采购清单不下钻;体力不足提示仍写「等恢复或喝体力药」。触发:XP 总控流程判定不合成时测试版 1.2.58:第 4 个参数按一批核对(Pine Pollen 两处调用传 10,体力与消耗品需求乘批数);步长不够提示「等它随时间恢复(不吃步长道具)」(此前写「等恢复或喝体力药」);清单每项附去哪补,末行提示辅助脚本会大字提醒补货(辅助 1.2.16 起大字提醒只管 Fortified XP Potion 材料链,Greater 有库存时不往下查松花粉/松果,这句待下次核心升级改)。 - 短缺递归追溯仅观测 — 对某个短缺物往下追:可合成的算出还要合成几次、每次产量和耗体力,并继续核对下层材料;不可合成的基础料标 🛒 缺 N 个并附去哪补(1.2.58 前写需采购)。
_traceDeficit:合成次数 max(Math.ceil(缺口/产量), 该配方一批次数batch),不低估;消耗品按次数倍数算需求,工具看是否 ≥ 阈值;按层级缩进输出树状行。触发:_diagnoseCraft内 - 补货清单折算最底层基础料仅观测 — 把所有短缺递归折算到最底层基础原料的数量,汇总成补货清单(每项附去哪补),并提示「补齐后才能合成」。
_accumulatePurchase:没有配方的物品累加进 summary;可合成物按 max(ceil, batch) 次数继续向下折算(消耗品按倍数,工具补到阈值)。触发:_diagnoseCraft内 - 去哪补提示
_supplyHintOf测试线独有 — 诊断卡片里每个缺货的基础料附一句去哪补:研磨器、便携炉「Mina 商店有售(工具,买一次一直用)」;玻璃罐「喂 Greater/Fortified XP 药水会返还空瓶;背包没药水就去市场买或拾荒」;其余「商店不卖:市场购买或拾荒获得」。依据游戏 listings 表(合成相关只有两样工具在商店卖)。触发:诊断卡片输出时|版本测试版1.2.58 - 物品中文显示标签仅观测 — 诊断输出里给物品显示中英文名,只影响日志可读性。
ITEM_LABEL映射,_labelOf查不到时显示原 key。触发:诊断输出|参数ITEM_LABEL
1.10 模式与停采线
两种停采模式:normal 用"精确清算线 +3%,封顶 80%",greedy(默认)用极限线(测试版 30%,公开版 5%)。
- normal / greedy 两种停采模式 — normal=安全模式,停采线=精确清算线 LT+3%(封顶80%),取不到 LT 用 65%/76%;greedy=贪婪模式,无杀手时用极限停采线(测试版 30%,公开版 5%),尽量采到接近极限才停。模式从
localStorage「kami_mode」读,未设置默认 'greedy';加载时固化,写入window.__kamiMode供停采线计算模块读。触发:脚本加载时|参数GREEDY_THRESHOLD=30(测试版 1.2.57 起;公开版 5)|命令setKamiMode('greedy'|'normal')|存储kami_mode|来历最大化单周期采集时长;旧名「饥饿模式 starving」改名为贪婪模式|版本1.1.13 - 默认停采线兜底值 — 取不到精确清算线 LT 时:normal body 默认 65%,非 normal body 默认 76%。挂到
window.DEFAULT_THRESHOLD_NORMAL/DEFAULT_THRESHOLD_OTHER。触发:停采线计算取不到 LT 时由其他模块读取|参数DEFAULT_THRESHOLD_NORMAL=65;DEFAULT_THRESHOLD_OTHER=76|来历非 normal body 受亲和度克制、更易被清算,所以默认线更高 - 停采线封顶 80% — LT 再高的 kami 也最多在 80% 停采,保证每周期至少采 20 个百分点血量区间。挂
window.MAX_THRESHOLD供计算模块读。触发:停采线计算时|参数MAX_THRESHOLD=80|来历有意设计,目标是单周期采集时长最大化;LT 过高的 kami 应先升级属性而不是抬高停采线 - 停采线安全垫 LT+3% — 停采线 = 精确清算线 LT + 3 个百分点。只在本脚本闭包内使用,未挂 window;配套凑批等待与单停危险线收紧至 -1。调大更保命但采集更短。触发:停采线计算时|参数
LT_STOP_MARGIN=3|来历精确清算线经官网/单测/实盘三重验证后收窄;此前 v1.1.1~1.1.7 过渡期为 15|版本v1.1.8 起 - 贪婪极限线常量与兼容别名 —
GREEDY_THRESHOLD挂到window.GREEDY_THRESHOLD,同时保留window.STARVING_THRESHOLD指向同一个值。供停采线计算模块与辅助脚本读取;调小采集更久但风险升高。触发:脚本加载时|参数GREEDY_THRESHOLD=30(测试版)/5(公开版)|来历兼容 v1.1.13 改名前的旧引用方式|版本1.1.13 - 贪婪线 5% → 30%测试线独有 — 测试版把极限停采线从 5% 抬到 30%。依据是 0914 全网探针:全网 22222 只里 97% 是白板(无攻击技能),我方 313 只全带防御加成,血量 ≥20% 时没有任何一只白板能杀我方任何一只;带技能的 3% 在 30% 时只有 0.5~1.6% 能杀。5% 线只剩 5 点垫子,夜里卡片血量停更会直接采成 STARVING(0 产出+必须喂+intensity 清零);30% 多留 10 点给读数滞后和深处掉血加速。每点血换的 MUSU 恒定 =(harmony+20)/6.5,深采收益在少停部 tx 与 intensity,5→30 损失很小。触发:脚本加载时|参数
GREEDY_THRESHOLD=30|来历用户 0914 定案|版本测试版1.2.57 - 旧值 starving 一次性存储迁移测试线独有 — 读到老机器
localStorage里残留的 'starving' 时自动改写为 'greedy' 并打一条迁移提示。只迁移一次,之后永不触发;注释特别警告这不是残留别名,别删。触发:脚本加载读取模式时|存储kami_mode|来历若不迁移,__kamiMode既非 normal 也非 greedy,下游 === 'greedy' 判假,停采线从 5% 静默跳回约 72%|版本测试版1.2.50 setKamiMode切换模式命令测试线独有 — 只接受 'normal' / 'greedy',写入localStorage后打印绿色提示,1.5 秒后自动刷新页面生效。非法值打印用法(normal: 清算线+3%上限80%;greedy: 无杀手5%有杀手切安全线)并返回。触发:命令|参数刷新延迟 1500ms|命令setKamiMode('greedy')/setKamiMode('normal')|存储kami_mode|来历模式常量在加载时固化,运行中改localStorage不生效,必须刷新让所有模块按新模式重建|版本测试版1.2.50- 拒收旧名 starving 并解释原因测试线独有 —
setKamiMode('starving')不再作为别名接受,打印「旧名已移除,请用 greedy」及改名原因。输入侧别名移除,读取侧localStorage迁移保留。触发:命令输入 'starving' 时|命令setKamiMode('starving')(会被拒)|来历用户 0913 定案:该旧名指「停采线压到 5% 榨干周期」,与「0 血还在采」的 STARVING 状态无关,两个概念共用一个词,读代码的人和 AI 都会混|版本测试版1.2.50 getKamiMode查看模式命令 — 打印当前模式、杀手状态、恢复倒计时、当前停采线、默认值和切换提示,并返回 {mode,killerDetected,cooldownRemain}。恢复倒计时 = max(0,SAFE_COOLDOWN_MS- 距上次发现杀手时长);greedy 下显示极限线(5%)或安全线(LT+3%,上限80%)。触发:命令|命令getKamiMode()- 模式启动 banner仅观测 — greedy 打印橙色「[贪婪模式] 已启用,无杀手时使用
GREEDY_THRESHOLD% 极限停采线,杀手监控由独立脚本负责」;normal 打印绿色「[安全模式] 停采线=LT+3%(上限80%)或默认65%/76%」。纯日志。触发:脚本加载时 - 杀手检测自动切安全线(滑动窗口)已停用 — 设计为:5 分钟窗口内收到 2 条 liquidated 消息判定有活跃杀手,贪婪模式自动改用安全线;15 分钟无新击杀才恢复极限线。数据源是杀手监控脚本的 Feed 监控,现已停用,
__killerDetected默认无人写入恒为 false;本段只定义常量与初始化变量,规则默认不触发。实际杀手保护是杀手监控脚本的位置轮询直接触发紧急停采。触发:(原)杀手监控脚本写入全局变量|参数LIQUIDATE_WINDOW_MS=5分钟;LIQUIDATE_COUNT_TRIGGER=2;SAFE_COOLDOWN_MS=15分钟|来历冷却 15 分钟是为避免杀手只是短暂停手就贸然切回激进线 - 跨脚本模式/杀手全局变量约定 — 初始化
window.__kamiMode、__killerDetected=false、__lastKillerTime=0、__liquidatedTimestamps=[]。__kamiMode被下游读取生效;三个杀手变量为兼容保留,默认无人写入。触发:脚本加载时 - 贪婪模式说明(文档)仅观测 — 对比 normal(LT+3%,封顶80%)和 greedy(极限线,测试版 30% / 公开版 5%)两种模式,说明本地无记录时默认是 greedy,杀手检测主路径是轻量杀手监控按名单轮询位置、直接触发紧急停采。紧急停采触发条件(文档):杀手监控报告名单内杀手逼近,或任意 kami 的 HP 低于清算线+2%。1.2.50 起
setKamiMode只接受 normal/greedy。公开版杀手名单默认为空,没配置好就建议先切 normal。触发:文档(不执行)|参数normal=LT+3%封顶80%;greedy=GREEDY_THRESHOLD|命令setKamiMode('greedy')/setKamiMode('normal')/getKamiMode()|存储kami_mode|来历v1.1.13 饥饿模式改名贪婪模式,避免与0血仍在采的STARVING状态混淆|版本1.1.13 - 清算消息计数杀手检测(辅路径)已停用 — 旧的辅助检测路径:按 liquidated 清算消息计数,5分钟内2条就切安全线,15分钟无新击杀再恢复。依赖杀手监控脚本的 Feed 监控写
__killerDetected标记。Feed 监控已停用,标记恒为 false,安全线自动切换这条路不再生效,greedy 的安全性因此更依赖杀手名单是否填全。触发:原依赖 Feed 监控写标记|参数5分钟2条切安全线;15分钟无击杀恢复|来历Feed监控停用 - Feed(被喂食)监控外置说明已停用 — 本脚本没有 Feed 监控(
openChatAndSwitchToFeed/startFeedMonitor),注释说明由配套的轻量杀手监控脚本负责,启动流程里也不启动。1.2.51 的注释把它称作「已停用的 Feed 监控」。
1.11 TX 双锁与 nonce
同一钱包的 tx 必须串行,否则 nonce 冲突。紧急锁给紧急停采,普通锁给其他所有操作;另有停采发送通道(mud / raw)的选择。
双锁机制
- TX双锁机制说明(文档)仅观测 — 说明为什么需要紧急锁和普通锁:所有操作共用一个 Operator nonce 序列,紧急停采被抢号就可能漏停。文档写:紧急锁10分钟超时,普通锁5分钟超时,紧急停采最多等普通锁释放30秒;锁纪律是「先确认有活干再拿锁、锁内只留发tx段」;并列出各模块的锁检查点。具体实现在后文,数值以实现为准。触发:文档(不执行)|参数文档值:紧急锁10min/普通锁5min/等待普通锁30s|命令
getTxLockStatus()|来历最危险场景:紧急停采被其他操作抢走nonce→停采失败→kami被清算 - 双锁串行化全部链上 TX — 用挂在 window 上的两把内存锁(紧急锁/普通锁)把核心脚本与辅助脚本所有发 TX 的动作排成一条队,避免同钱包并发发 TX。锁对象记录 operation(操作名)、script(哪个脚本)、since(上锁时间戳)便于诊断;紧急锁专供紧急停采,持有期间任何普通 TX 不允许启动。触发:所有发 TX 的入口在发送前调用|参数
TX_LOCK_TIMEOUT=300000(5分钟);TX_EMERGENCY_TIMEOUT=600000(10分钟)|来历同一钱包并发发 TX 会造成 nonce 冲突,交易被替换或丢弃 - 锁防重复初始化 — 只有当
window.__txEmergencyLock/__txNormalLock为 undefined 时才置 null。typeof === 'undefined' 判断后再初始化,不覆盖已有值。触发:脚本加载时|来历脚本热重载时不能清掉另一个脚本正持有的锁 hasEmergencyLock紧急锁前置检查 — 所有发 TX 的操作都先过这一关:有紧急锁就返回 true。无锁返回 false;有锁先做超时检查再返回 true。触发:被所有 TX 入口、抢普通锁、排队让路等调用;挂 window 供辅助脚本用- 紧急锁超时自愈 — 紧急锁持有超过 10 分钟视为持有方异常(页面卡死/流程中断),检查时强制释放并打警告日志。
Date.now()-since >TX_EMERGENCY_TIMEOUT→ 置 null,打印已持有秒数;紧急锁超时取普通锁 2 倍,因为批量停采耗时更长。触发:每次调用hasEmergencyLock时顺带检查|参数TX_EMERGENCY_TIMEOUT=600000|来历防止一把僵尸锁永久卡死整个自动化 setEmergencyLock上紧急锁 — 设置 operation='emergency_stop' 的紧急锁并打日志。直接覆盖写入 {operation, since},打印「🚨 设置紧急锁」。触发:紧急停采、raw 模式饿死救援调用;挂 windowreleaseEmergencyLock释放紧急锁 — 存在紧急锁时清空并打日志。无锁时什么都不做。触发:紧急停采/救援收尾;挂 window- 紧急锁长持锁诊断仅观测 — 释放紧急锁时若持有超过 120 秒,打一条「⏱️ [锁诊断] 紧急锁持有 Ns(长持锁)」日志。since 为取锁时刻;只在 >120s 才打,防刷屏;纯日志不改行为。触发:
releaseEmergencyLock时|参数阈值 120000ms|来历可观测性批次:定位长时间占锁的流程|版本1.1.17 - 抢普通锁时紧急锁优先 — 紧急锁存在时任何常规操作都拿不到普通锁,直接返回 false 并打「已停用 紧急锁存在,无法获取普通锁」。
tryAcquireNormalLock第一步调用hasEmergencyLock。触发:部署/喂食/合成/升级等常规 TX 抢锁时|来历保命操作(紧急停采)先行,常规 TX 一律让路 - 普通锁非阻塞抢占 — 普通锁已被占用且未超时时立即返回 false(打印占用者脚本/操作),不硬等,由调用方延后重试;抢到则写入 {operation, script, since} 并打「🔒 获取普通锁」。同一时刻只允许一个操作持有普通锁。触发:常规 TX 入口调用
tryAcquireNormalLock(operation, script) - 普通锁超时强制接管 — 普通锁持有超过 5 分钟视为异常,抢锁方强制释放并由本次直接接管。
Date.now()-since >TX_LOCK_TIMEOUT→ 打警告「普通锁[脚本/操作]超时,强制释放」后继续获取。触发:tryAcquireNormalLock发现锁被占时|参数TX_LOCK_TIMEOUT=300000|来历避免僵尸锁卡死自动化;调小恢复快但可能误杀执行中的慢 TX - 普通锁释放双匹配校验 — 只有 operation 和 script 都与当前持有者一致才释放普通锁。不匹配静默不动。触发:常规 TX 收尾调用
releaseNormalLock(operation, script)|来历防止 A 操作误释放 B 操作持有的锁 - 普通锁长持锁诊断仅观测 — 释放普通锁时持有超过 120 秒打「⏱️ [锁诊断] 普通锁[脚本/操作] 持有 Ns」。纯日志,>120s 才打。触发:
releaseNormalLock时|参数阈值 120000ms|版本1.1.17 waitForNormalLockRelease紧急停采礼让等待 — 上紧急锁前先等在途普通 TX 收尾,最多 30 秒,每 1 秒轮询;返回普通锁是否已清空。等待期间若普通锁超过 5 分钟也强制释放并立刻结束等待;超 30 秒仍被占则返回 false(由调用方决定怎么办)。触发:紧急停采、raw 模式饿死救援读 nonce 前调用|参数maxWaitMs默认 30000;轮询 1000ms|来历紧急场景不能久等,但先让在飞 TX 收尾可减少 nonce 冲突waitForEmergencyRelease常规 TX 排队让路 — 部署/升级/合成/喂食等常规 TX 遇到紧急锁时不直接失败,每 500ms 轮询等待,紧急停采完成后自动继续发送。无锁直接返回 true;开始等待打「排队等待(最多N秒)」;超时打「放弃本次」返回 false;等到后打「紧急锁已释放(等待N秒),继续tx」。触发:常规 TX 入口(核心与辅助脚本)调用|参数maxWaitMs默认 300000(与runAutomation主流程等待对齐);轮询 500ms|来历尽量不丢弃本次操作getTxLockStatus查看锁状态 — 返回两把锁当前持有者与已持有秒数的描述串。如「紧急锁(12秒) + 普通锁脚本/操作」,无锁返回「无锁」;只返回字符串不主动打印。触发:命令|命令getTxLockStatus()- 锁函数挂 window 跨脚本共享 — 把
hasEmergencyLock/setEmergencyLock/releaseEmergencyLock/tryAcquireNormalLock/releaseNormalLock/waitForNormalLockRelease/waitForEmergencyRelease/getTxLockStatus挂到 window。辅助脚本直接调用这些全局函数,两个脚本共享同一份锁状态。触发:脚本加载时|来历辅助脚本的 tx 入口同样要排队让路 - 抢锁失败建议重试间隔常量已停用 — 定义抢锁失败后延后重试的建议间隔 3 分钟。注释写「供各调用方引用」,代码实际在全文件中只有定义、没有任何引用处。参数:
TX_RETRY_DELAY=180000
主循环里的锁协调
- 本轮有活才等紧急锁(最多 300 秒) — 有停采或部署候选、但紧急停采正持锁时,原地等它结束,不直接丢掉整轮扫描结果。
__hasWorkThisRound为真且有紧急锁→打「紧急锁存在,等待紧急停采完成…(最多300秒)」,每 3 秒轮询;结束后打实际等待秒数和是否已释放。触发:锁协调开始|参数等待上限 300000ms;轮询 3000ms|来历立即跳过会把扫描好的候选池整个丢掉,只能等下个周期重扫,白白错过可部署的 kami - 紧急锁仍在则跳过本轮停采/部署 — 等过之后紧急锁还在,放弃本轮普通停采和部署,直接去喂食检查。
hasEmergencyLock()为真打「紧急锁存在,跳过本轮普通停采/部署」。触发:紧急锁等待之后 - 抢普通锁 + 被占时等最多 60 秒 — 有活时申请普通锁;被别的模块占着就等它释放,等待中出现紧急锁立即放弃。
tryAcquireNormalLock('batch_stop_deploy','core')失败→打占用者 script/operation,每 3 秒轮询window.__txNormalLock最多 60 秒,遇紧急锁 break;之后再尝试一次,仍失败打「等待60秒后仍无法获取普通锁,跳过本轮停采/部署」。空轮不碰锁。触发:无紧急锁且本轮有停采/部署候选|参数普通锁等待上限 60000ms;轮询 3000ms|来历先查后锁,空轮不白占锁挡住辅助脚本的合成等模块 - 锁归属校验 — 进执行段前确认手上的普通锁确实是自己的,不把别的模块刚抢到的锁当成自己的。
window.__txNormalLock.operation==='batch_stop_deploy' 且 script==='core' 才进 try 执行段。触发:抢锁流程后 - 停采/部署普通锁 finally 释放 — 停采和部署执行段结束后一定释放普通锁。try…finally 中
releaseNormalLock('batch_stop_deploy','core')。触发:执行段结束 - 停采进行中标记(防刷新打断) — 停采执行期间挂一个「操作进行中」标记,强制刷新等自保逻辑看到会避让。
apiList或domList非空时window.__kamiOperationInProgress=true,DOM 兜底停采结束后置 false。代码实际:复位语句不在 finally 里,停采段中途抛异常时标记可能残留。触发:拿到普通锁、有停采候选时|来历防止停采中途被刷新打断
停采发送通道(mud / raw)
- 停采发送通道启动播报仅观测 — 启动时播报当前停采发送通道和切换命令。读
localStoragekami_stop_tx_channel,只有值为 'mud' 才是 mud,否则一律 raw(缺省 raw)。banner 里setStopTxChannel的说明写「mud=MUD队列(默认)」,与代码实际缺省 raw 不一致。触发:脚本注入后立即|命令setStopTxChannel('mud'|'raw')|存储kami_stop_tx_channel|来历v1.1.21会诊:MUD队列回执形状未经实盘验证,默认保守回退到验证过的raw原始签名器|版本1.1.21 - 停采发送通道选择(默认 raw 原始签名器) — 紧急停采交易有两条路:MUD 队列(和游戏 api 共用一本 nonce 账)和原始签名器。默认走 raw,只有明确设成 mud 才走队列。
localStorage里的值严格等于 'mud' 才走 MUD,其余情况(包括读取抛错)一律 raw。触发:每批停采发送时读取|存储kami_stop_tx_channel|来历0710 取证发现,原始签名器和 MUD TXQueue 各记一本 nonce 账,两边分叉,每夜约 46 次 sequence mismatch,所以 1.1.19 切到 MUD。但 MUD 队列 resolve 出来的对象长什么样没在实盘验证过,1.1.21 默认又保守切回了验证过的 raw|版本1.1.19 停采通道统一 / 1.1.21 默认通道保守回raw - 控制台命令
setStopTxChannel— 一条命令切换紧急停采的发送通道,立即生效。参数不是 mud 或 raw 时打印用法和当前通道,不做修改;合法时写入localStorage并打印确认。触发:命令|命令setStopTxChannel('mud')切 MUD 队列(nonce 统一);setStopTxChannel('raw')切回原始签名器(旧路)|存储kami_stop_tx_channel|来历留一个开关,出事时秒回退,也方便实盘验证 mud 通道|版本1.1.19 停采通道统一 - MUD 队列入口缺失时自动跌落 raw 并计数 — 通道设为 mud 但队列上的
executeBatchedAllowFailure不是函数时,打一行 ⚠️,本批自动改走原始签名器。跌落次数计入__stopChanFallbackCount,供完成小结显示。触发:mud 通道每批发送时|版本1.1.19 停采通道统一 D3 - 停采发送通道选择(mud / raw) — 通道设为 mud 且队列入口可用时,走 MUD
txQueue发送,与游戏 api 共用同一个 nonce 账本;否则走原始签名器,把harvestId转成BigInt按 uint256[] 做 ABI 编码后signer.sendTransaction。通道由localStoragekami_stop_tx_channel决定,只有值为 'mud' 才走 mud,其余(含默认、读异常)都是 raw。两路用同一个入口、同一组harvestIds,容错语义一致。触发:_allowFailureStop每次发送|命令setStopTxChannel('mud')/setStopTxChannel('raw')(即刻生效,定义在本段之外)|存储kami_stop_tx_channel(读)|来历signer.sendTransaction与 MUD api 各走各的 nonce 账本,会造成 sequence mismatch|版本1.1.19 - MUD 入口缺失自动跌落 raw 并计数 — 选了 mud 通道但队列停采入口不是函数时,打「MUD 队列停采入口不可用,本批自动回退原始签名器」,跌落计数 +1,本批改走 raw。累加
__stopChanFallbackCount,供停采小结统计。触发:mud 通道发送时入口不可用|版本1.1.19
1.12 gas 账本与余额
每笔脚本 tx 按动作分类记账,后台按 hash 补真实 gas,showGasReport 汇总;余额偏低时大字告警。
低余额告警
- 账户余额DOM抓取 — 从页面读出账户余额文本,比如「12.34
mETH」。锚点是 #tx-logs(id 稳定,不依赖 sc-* 哈希 class)。在它父容器的 div/span 叶子节点里找匹配「数字+可选单位前缀+ETH」的文本;找不到返回 null。触发:checkLowBalanceOnce调用|版本1.2.7 - 余额单位换算为
mETH— 把余额文本换算成mETH数值。ETH×1000,m×1,µ/μ/u×1e-3,n×1e-6,p×1e-9,未识别的单位前缀按×1。格式不匹配返回 null。触发:checkLowBalanceOnce调用|版本1.2.7 - 低余额高亮充值提醒 — 余额低于10
mETH时,用红底白字16px 大字警告,并追加一行充值提醒(注明是启动时还是刷新前检测到的)。两行 clog 高亮,再 log 一条「🚨 [余额警告]」。clog 本身已写日志,再加 log 会让缓冲区出现两份同内容(「同步进 buffer」那条注释写于 clog 改造之前)。触发:余额<阈值时|参数__LOW_BALANCE_THRESHOLD_METH=10|版本1.2.7 - 低余额检查触发与去重 — 会话启动时检查一次余额,页面刷新前再检查一次。stage='start' 用
__lowBalanceChecked保证每会话只查一次;stage='end' 不去重,每次刷新前都查。抓不到余额或解析失败就静默跳过,整体 try/catch。触发:printAccountAndRooms会话启动时调 'start';刷新前调 'end'|版本1.2.7 - 读数为 0 先复核再告警测试线独有 — 余额读的是游戏界面文字,页面还没渲染出余额时读到的就是
0.00mETH。所以读到低于警戒线时:大于 0 的按真低余额立刻告警;恰好为 0 的不立即报,20 秒后重读,两次都是 0 才报;复核后变正常则只打一行ℹ️ [余额] 首次读到 0.00mETH(页面多半还没渲染出余额)…不告警,读不到元素也不报。刷新前那次检查的复核可能赶不上页面卸载,那种情况最多少报一次,下次启动照样查。触发:checkLowBalanceOnce|参数__LOW_BALANCE_RECHECK_MS=20000|来历0921 夜账户最大的那台每次启动都报「余额仅剩 0.00mETH」共 6 次、并把健康看板刷红,链上实测该号 operator 有 65 mETH,纯误报|版本测试版1.2.61 - 旧余额差gas统计已停用 — 原来靠刷新前后余额求差来统计 gas,包括
recordGasStart、recordGasEnd、showGasUsage、clearGasUsage、showGasStats、showEpochGasStats。1.2.7 整体删除,改用链上真值 gas 账本showGasReport。本板块只保留独立的低余额告警。账户信息 banner 处原先启动这套统计的代码也已删除,只剩注释。触发:已删除|来历余额差混入充值/买道具/充能,统计不准|版本1.2.7
gas 真值账本
- gas账本存储与有界裁剪 — 本地保存一份 gas 账本,每笔脚本 tx 一条,按时间和条数双重限长。读取损坏或缺失时回退空数组;写入超配额静默。裁剪先删35天前的,再按5000条上限截掉最旧的。
gasWei用BigInt转 string 存,不丢精度。触发:每次记账/回填时|参数GAS_LEDGER_MAX=5000;GAS_LEDGER_RETAIN_DAYS=35|命令showGasReport()|存储kami_gas_ledger|版本1.2.7 - 记账钩子
_gasLedgerRecord— 各发 tx 点只加这一行调用,按动作类型(deploy/stop/feed/revive/scavenge/xp_potion,辅助另有 craft/upgrade 等)记一条待补 gas 的账。条目格式 {ts, action,kamiIds(字符串数组), n, hash,gasWei:null, status:null}。全程 try/catch、异常静默,绝不改 tx 发送逻辑、返回值或时序(非侵入红线 I1)。触发:被核心各发 tx 点同步调用|存储kami_gas_ledger|来历单kami/动作gas不是链上字段,只能在发tx时打标签;取代不准的余额差统计|版本1.2.7 - tx哈希抓取(三种形态) — 从 tx 对象、hash 字符串或 promise 里拿到交易哈希。字符串直接用;有 .hash 取 .hash;promise 不 await,在 .then 里取 hash/
transactionHash,按 ts+action 且 hash 为空找回条目再落盘。抓不到保持 null;无 hash 的条目(如拾荒 UI 点击)永远不会被补 gas,只计动作次数。触发:_gasLedgerRecord内调用|存储kami_gas_ledger|来历fire-and-forget发送也要能事后按hash补账|版本1.2.7 - MUD队列回执当场入账测试线独有 — 发送结果里已经带着解析好的回执时,直接当场算出 gas 和状态,不再让 reconciler 去链上查一遍。
txOrHash.receipt是非 promise 对象时,gasWei=gasUsed×(gasPrice或effectiveGasPrice)(算法照搬 reconciler),status 取receipt.status,缺 hash 时用receipt.transactionHash补。算不出就留 null 交给 reconciler。触发:记账时检测到 receipt|存储kami_gas_ledger|来历0913实测MUD队列(api通道)返回{hash,receipt};喂食是发tx最密的动作,省下每笔一次RPC|版本测试版1.2.46 - 暴露记账钩子给辅助脚本 — 把记账函数挂到
window.__kamiGasRecord,让辅助脚本的合成/升级/加点/重置技能也能记进同一本账。辅助脚本调用前有 typeof 守卫,核心是旧版没有这个接口时静默跳过。报告说明写需辅助≥1.2.4。触发:脚本注入时挂载;辅助发 tx 时调用|版本1.2.10 - 只读provider获取 — 取一个只用来读回执和余额的 provider,绝不拿它发 tx。优先
network.signer.provider,其次network.provider,都没有返回 null,异常也返回 null。触发:reconciler/owner查询/余额续航调用|版本1.2.7 - reconciler 按回执回填真实gas — 后台定时扫描账本,给有 hash 但还没 gas 的条目查链上回执,回填
gasWei和 status。__gasReconcileRunning防重入。每轮按账本顺序从最旧取前20条,逐条getTransactionReceipt:没上链或缺字段就保持 null 下轮再补;gasWei=BigInt(gasUsed)×BigInt(gasPrice||effectiveGasPrice);status=0 表示 revert,照样计入。回写时重新读当前账本、只填仍为 null 的同 hash 条目,避免覆盖期间新记的账,回写后裁剪。注意:从最旧开始取,一直查不到回执的条目会持续占住前20个名额,直到35天后被裁掉。零新增 tx(I2)。触发:主循环启动时挂setInterval,每3分钟一次(window.__gasLedgerReconcilerStarted防重复挂,并打一条启动日志)|参数GAS_LEDGER_RECONCILE_MS=3分钟;GAS_LEDGER_RECONCILE_BATCH=20|存储kami_gas_ledger|来历ethers v6单笔字段是gasPrice(兼容effectiveGasPrice),0713实测|版本1.2.7
链上 gas 查询与动作分类
- owner手动tx gas查询仅观测 — 查 owner 主身份地址(充值、转 kami 这类脚本发不了的手动 tx)的链上 gas。调 Yominet 官方索引器 Rollytics 的 evm-txs/
by_account?is_signer=true,每页100条、最多翻5页。累加gasUsed×effectiveGasPrice(hex 值用BigInt解析)。回复里没有时间戳,就用当前块号减「窗口秒数÷2.29秒/块」切窗;拿不到当前块时全部计入各窗,并标blockBased=false。结果按同地址缓存1小时。失败返回 {ok:false,error}。纯只读。触发:showGasReport调用|参数OWNER_GAS_TTL_MS=1h;OWNER_GAS_MAX_PAGES=5;OWNER_SEC_PER_BLOCK=2.29|命令showGasReport()|存储kami_owner_gas_cache|来历owner tx稀少(实测约0.7笔/天),游戏页CORS放行Rollytics|版本1.2.8 - operator 24h链上全量总账仅观测 — 从链上索引器的 cosmos 端点拉 operator 最近24小时的全部 tx,算出真实扣费总额,用来和本地账本对照。端点 txs/
by_account,每条自带真实时间戳。单笔实扣=fee_amount×gas_used÷gas_limit,先乘后除保精度;fee 或 limit≤0 的跳过。索引严格新→旧,看到24h外的 tx 就停止翻页;最多15页,翻满仍没越过24h边界则标 truncated(报告标「仅下限」)。缓存1小时。触发:showGasReport调用|参数ADDR_GAS_24H_TTL_MS=1h;ADDR_GAS_24H_MAX_PAGES=15|命令showGasReport()|存储kami_addr_gas_24h_cache_v3|来历0717深夜三口径对账:Rollytics evm-txs索引不完整(7d仅约75%)会把缺口算成负数;fee_amount是预付额比实扣高约43%不可直接累加;该口径与kamistats偏差+1.2%|版本1.2.11 - 链上tx动作反推分类仅观测 — 对链上拉到的每笔 tx,从
MsgCall的函数选择器和合约地址反推动作类型并分桶,连没记过账的 tx(含拾荒 UI 点击、账本启用前的 tx)也能分类。取第一个MsgCall的 input 前10字符和contract_addr,交给_chainActionLabel打标签;没有MsgCall记「未知(非MsgCall)」。分类异常不影响总额统计。缓存 key 升到 v3,防旧结构串味。触发:_fetchAddrGas24h内逐笔执行|命令showGasReport()|存储kami_addr_gas_24h_cache_v3|来历0718实测cosmos tx自带完整calldata,选择器+合约地址就是天然动作标签|版本1.2.13 - 运行时系统合约地址动态分类 — 每次出报告前,从游戏运行时读取各系统合约的当前真实地址并优先匹配;合约换代后分类自动跟上。用64个官方 key 逐个读
window.network.txQueue.systems[key].target(容器是 Proxy,没实现ownKeys,无法枚举,只能点名)。结果缓存10分钟,只按地址前缀匹配、不比选择器。触发:_chainActionLabel调用|参数缓存10分钟|来历0911实盘gas报告48%开销落进「未知」——停采合约已换代,硬编码表patch后必然腐烂;铁律:严禁硬编码系统合约地址,唯一安全源是运行时target|版本1.2.32 - 官方64个系统key清单 — 维护官方客户端全部64个 systems key 及中文标签(移动、部署、停采、道具使用、升级、加点、拾荒领取、市场、交易等)。从官方客户端源码 packages/client/src/network/api 里把
systems['...']字面量全部抓出来,替代原先手写猜测的11个;容器路径按源码校准为txQueue.systems。触发:运行时分类读取|来历Proxy容器不可枚举,只能拿已知key逐个点名|版本1.2.34 - 硬编码历史地址分类表 — 给系统合约换代前的旧 tx 打标签,保证旧账也看得懂。老条目要同时匹配选择器和地址。0911 从 World 注册表链上解析出的64个系统真值条目 sel 为 null,只比地址。拾荒重掷/领取按地址前缀匹配(0717日志对时认领)。另有0825发现的道具使用新址(1.2.28)。触发:运行时表未命中后使用|来历1.2.33曾把加点标成道具使用、升级标错;选择器0xe60f3a76=
executeTyped(uint256,uint32)被多个系统共用,光看选择器会张冠李戴,必须认地址|版本1.2.34 - 未知动作指纹兜底 — 两张表都没匹配上的 tx 归进「未知 选择器@合约前12位…」桶,显示指纹方便后续认领。见到一个就改表认领一个。触发:分类未命中时|版本1.2.13
showGasReport 报告
showGasReport()链上真值gas报告命令 — 汇总输出 gas 报告:账本概况、operator 分窗分类统计、链上24h全量对照、owner 手动 tx、合计、余额续航。开头列账本条目数/上限/保留天数、未补 gas 条数(有 hash)、无 hash 无法补的条数。地址用connectedAddress→getByOperator解析出 operator(发 tx 地址)、owner(主身份)和账户名。全部行先攒进数组,最后用一次 clog 输出,所以会进日志。生成过程异常时输出「❌ 生成报告异常」。在后文被wrapManual包装。触发:控制台手敲;页面加载10分钟后自动一次|参数窗口24h/3d/7d/30d|命令showGasReport()|存储kami_gas_ledger/kami_owner_gas_cache/kami_addr_gas_24h_cache_v3|版本1.2.10- operator分窗按动作分类统计仅观测 — 对24h/3d/7d/30d四个窗口分别统计总 gas、笔数、日均、revert 白烧,并给出11类动作各自的金额、占比、笔数、每笔均价。只统计已补上
gasWei的条目。status=0 的单列为 revert 白烧(已含在总额里)。动作包括部署/停采/喂食/复活/拾荒/XP药水/合成/升级/加点/重置技能/补步长。单位mETH(1e15 wei)。触发:showGasReport内|命令showGasReport()|存储kami_gas_ledger|版本1.2.10 - 日均按账本实际覆盖时长折算仅观测 — 日均的分母取「窗口长度」和「账本实际覆盖时长」里较小的那个,避免账本刚启用时日均被稀释、续航虚高。覆盖时长最少按1小时算,防冷启动除以极小数爆表。窗口没被覆盖满时,在日均后面加人话说明。触发:
showGasReport内|参数最小覆盖1小时|来历0718实测账本只记了1.5小时就按整窗口除,续航曾算出4988天|版本1.2.12 - 报告重复窗口折叠仅观测 — 账本覆盖不足时,更长窗口的数据会完全相同;只展示第一个不满的窗口,其余折叠成一行说明。折叠只影响显示,合计段和续航用的统计数据在折叠前就已填好。触发:
showGasReport内|版本1.2.14 - 链上总账对照与账本外缺口仅观测 — 报告里列出 cosmos 全量24h总账(金额/笔数,截断时标「仅下限」)、链上分类明细(按金额降序),以及「账本外缺口=链上全量−账本24h」。缺口里包括账本启用前的 tx 和拾荒等无 hash 项。失败时显示「链上总账对照不可用」,不影响主报告。结果同时留给余额续航复用。触发:
showGasReport内(有 operator 地址时)|存储kami_addr_gas_24h_cache_v3|来历防gas记账漏挂;账本=实时口径能细分道具/单kami,链上=全量兜底,两者互补|版本1.2.13 - owner段报告与不假分窗仅观测 — 报告里列出 owner 手动 tx 在各窗口的 gas,拉取时先提示「⏳ 拉取中…」。拿不到当前区块(
blockBased=false)时不假装分窗:只报「全部拉到的总额」,也不掺进各窗口和合计,免得把全部历史算进24h。查询失败时提示可用命令行脚本 bash 查gas.sh兜底,合计段只按 operator 算。没解析到 owner 地址就跳过这段。触发:showGasReport内|存储kami_owner_gas_cache|来历grok审查non-blocking:拿不到区块时假分窗会显示误导数字并污染合计|版本1.2.8 - operator+owner合计段仅观测 — 按窗口列出 operator 和 owner 的合计 gas,以及合计日均。operator 部分按实际覆盖天数折算,owner 部分按整个窗口算;7天合计日均留给续航段作参考。触发:
showGasReport内|版本1.2.8 - 余额续航智能数据源仅观测 — 读 operator 链上余额,估算还能用几天,并用人话说明用了哪份数据、为什么。账本覆盖不足24小时且链上24h消耗>0时,用链上真实24h消耗算(链上样本被截断时提示续航可能更短);否则用 operator 7天日均(按实际覆盖折算);两者都没有就提示暂无数据。另附 operator+owner 合计日均作参考,续航仍以 operator 为准(owner 你控不了)。异常静默。触发:
showGasReport内|参数账本成熟线=24h|来历用户0718定案:账本不足24h时续航直接用链上真实数据,攒满24h自动切回账本口径(能分类、能剔除owner)|版本1.2.12 - gas报告美化配色与图标仅观测 — 按行的类型自动配色(标题、各段标题、窗口行、动作行、续航行、警告行),动作行前加图标,比如部署🚀、停采🛑、喂食🍎。一次 clog 用多个 %c 分段着色。纯展示层,数据不动;美化出任何异常就原样纯文本输出,保证数据永远可读。触发:
showGasReport末尾|版本1.2.14 - 页面加载后10分钟自动gas报告仅观测 — 每次页面加载后10分钟自动跑一次
showGasReport(),报告会进日志。只挂一个setTimeout,不是 interval。调用前设__kamiCallSource='auto_10min_timer',所以日志标记为「🤖 [脚本调用@auto_10min_timer]」。核心大约每45分钟例行刷新一次,等效每45分钟一份报告;Rollytics 查询有1小时缓存,多数轮次零网络成本。异常静默。触发:自动:加载后10分钟|参数10*60*1000 ms|命令showGasReport()|版本1.2.16
gas 规则说明
- Gas消耗规则说明(文档)仅观测 — 分清哪些失败不花钱(
estimateGas失败、RPC错误、签名/参数错误、预检过滤),哪些会花钱(revert、超时但其实成功、nonce冲突两笔都上链、重复停采)。脚本的预检体系(estimateGas、状态过滤、防重复标记)都是为了把失败拦在不花钱的阶段。触发:文档;showGasRules()可查看|命令showGasRules() - Gas 消耗分类说明 — 说明不耗 gas 的(
estimateGas失败、RPC 连接错误、签名失败、参数错误、预检CALL_EXCEPTION)与耗 gas 的(上链 revert、超时但成功、nonce 冲突重试两笔上链、重复操作)。静态文本,不读状态不发 tx,随时可安全调用。触发:命令|命令showGasRules() - 部署/停采批量 gas 实测表与结论 — 列出 N=1..12 部署与停采单 tx 总 gas、每只摊销与省比;结论:部署 N=10 起省约 45%(边际 0.69M/固定 0.66M),停采 N=12 省 40%(边际 1.36M/固定 1.07M),停采反比部署贵 80~94%;省 gas 优先级①少停不必要的②停采批量③部署批量。单账户单次实测(间隔 1.5s,cooldown 3min),不同账户/时段有偏差,以省比为准。触发:命令|命令
showGasRules()|来历停采涉及收益结算+状态清理+事件 emit,写操作更多(推测) - 脚本省 gas 优化清单文案 — 列出紧急停采每批随机 6-10、预检过滤已死/已停、动态等待防重发、一键停采 6-10 随机、喂食每批 3 个、喂食 4 层预检+失败冷却、部署
estimateGas预检、默认 API 批量,并提醒 cooldown/revert/nonce 冲突会拉高真实成本。静态历史文案,不随代码自动同步,个别描述可能与当前实现不一致(本段未核实)。触发:命令|命令showGasRules()
1.13 死亡监控与停摆检测
每 3 分钟查一次自家 kami 有没有死;电脑睡眠或页面被冻住醒来后,立刻做一次急救扫描。
自家 kami 死亡监控
- 定时全量死亡检测(启动自动开启) — 每 3 分钟查一次本账户所有 kami,统计 state 为 DEAD 的数量和编号。脚本启动时自动开启,排在部署流程之前。用连接地址调
window.network.explorer.accounts.getByOperator(addr)拿全量 kami 列表,state 转大写后比较。注释写的是「链上 API」,代码实际读的是 explorer 本地索引器。触发:启动序列自动调startMyKamiDeathMonitor(),之后每 3 分钟一次|参数MY_KAMI_DEATH_CHECK_INTERVAL=3min|来历刻意不用 DOM:渲染不保证完整,可能漏掉部分 kami,以 API 全量查询为准|版本1.1.13 饥饿模式改名贪婪模式 - 地址或列表缺失时早退 — 拿不到连接地址时打「无法获取账户地址」并返回;kami 列表为空时打「未找到 Kami 列表」并返回。触发:每轮检测开头|来历页面没就绪时不误判
- 发现死亡:红色大横幅仅观测 — 有 kami 死亡时打白字红底大号横幅「发现 N 个 Kami 死亡」,并列出死亡 kami 的编号。触发:检测到 DEAD 时
- 发现死亡:置杀手警戒,切安全停采线 — 置
window.__killerDetected=true,记下__lastKillerTime。贪婪模式下由此改用安全停采线。首次进入警戒且处于贪婪模式时,打橙色提示「自动切换到安全停采线,15 分钟后自动恢复贪婪模式」;已在警戒中则打「已处于安全模式中」。注:1.2.51 盲扫注释称__killerDetected只有已停用的 Feed 监控会置位,但代码里本死亡监控发现我方死亡时也会置位。触发:检测到 DEAD 时|参数SAFE_COOLDOWN_MS=15min|版本1.1.13 - 发现死亡:立即批量复活(不等主循环) — 发现死亡后立刻 fire-and-forget 调
_reviveDeadBatch([]),传空数组,由它内部重新走 API 查死亡名单再复活。挂了 .catch 打日志。本监控、主循环兜底复活、辅助脚本启动窗口复活三路互不重发,并发安全靠_reviveDeadBatch内部的 revive 锁和__reviveSentAt15 分钟防重发窗口。触发:检测到 DEAD 时|来历主循环周期约 10 分钟,等它会让死掉的 kami 白白闲置;传空数组是因为两次查询之间状态可能变化,不复用手里的旧列表 - 发现死亡:触发紧急停采 — 发现死亡后调
emergencyStopHarvest(),保护其余还在采集的 kami。调用前检查函数是否存在。代码既没有 await 也没有挂 .catch;注释说 fire-and-forget 都有 catch,实际只有复活那条挂了。触发:检测到 DEAD 时 - 无死亡时自动解除杀手警戒 — 没有死亡时打「检测 N 个 Kami,无死亡」。如果正处于警戒中,且距上次发现死亡已满 15 分钟,就解除警戒(贪婪模式恢复极限停采线,打绿色日志);没满就打剩余分钟数。剩余分钟数向上取整。触发:每轮检测无死亡时|参数
SAFE_COOLDOWN_MS=15min;GREEDY_THRESHOLD|版本1.1.13 - 单轮检测异常隔离 — 整个检测体包在 try/catch 里,某一轮出错只打日志,不影响后续轮次。触发:每轮检测
- 控制台命令
startMyKamiDeathMonitor(防重复启动) — 启动死亡监控:打启动日志和检测间隔,立即检测一次,然后每 3 分钟定时检测。定时器已存在时打「已在运行中」并拒绝二次启动。触发:命令(启动时也会自动调用)|参数MY_KAMI_DEATH_CHECK_INTERVAL=3min|命令startMyKamiDeathMonitor() - 控制台命令
stopMyKamiDeathMonitor— 清除定时器,停止死亡监控,打「已停止」。没在运行时静默不做事。触发:命令|命令stopMyKamiDeathMonitor() - 控制台命令
checkMyKamiDeath— 手动立即跑一轮死亡检测,发现死亡会同样触发复活、紧急停采和警戒。触发:命令(停摆检测器急救也会调用)|命令checkMyKamiDeath()
停摆检测器
- 启动中断时长回溯仅观测 — 启动时读上次运行的心跳时间戳,距今超过5分钟就用红底大字报告中断了多久(推测是睡眠、关机或浏览器关闭),并提示即将做死亡扫描。只读
localStorage,不在这里触发急救。读不到(如隐私模式)静默。时长显示为「X小时Y分」或「Y分」。触发:脚本注入后立即|参数中断阈值5分钟|存储kami_last_heartbeat(只读)|来历只读不急救,避免与死亡监控双跑|版本1.2.9 - 停摆检测器幂等初始化 — 脚本注入后在模块级自动启动。
window.__stallDetectorOn已置位时直接返回,重复加载不会重复挂定时器。初始化整体 try/catch,失败只打「初始化失败(不影响主流程)」。触发:脚本加载时自动|来历防止双挂setInterval(I3)|版本1.2.9 停摆检测器 - 30 秒心跳写入
localStorage— 每 30 秒记一次心跳时间戳,启动时也立即写一次,供下次会话在启动阶段回溯上次中断(回溯在启动提示板块,只打日志、不急救)。只写这一个键,写失败静默(I4)。触发:启动时及每 30 秒|参数STALL_TICK_MS=30s|存储kami_last_heartbeat|来历启动回溯不急救,避免和startMyKamiDeathMonitor双跑|版本1.2.9 停摆检测器 - 事件循环跳变检测 — 每次 tick 计算和上次 tick 的间隔 gap。gap<120 秒算正常抖动,忽略;≥120 秒说明 JS 事件循环被系统睡眠或浏览器挂起打断过。触发:每 30 秒 tick|参数
STALL_SHORT_MS=120s|版本1.2.9 停摆检测器 - 停摆事件 2 小时窗口计数 — 短停摆和睡眠级停摆都记为事件,只保留最近 2 小时,用于判断是不是碎片化睡眠。每次记录后剪掉超过 2 小时的旧事件。触发:gap≥120s 时|参数
STALL_EVENTS_KEEP_MS=2h|版本1.2.9 停摆检测器 - 短停摆黄色日志仅观测 — 停摆 2~5 分钟时打黄色日志:停摆多久、从几点到几点(HH:MM)、近 2 小时第几次。时长格式化成「X 分钟 / X 小时 Y 分」,时间按本地时区偏移
__TZ_OFFSET_MS显示。触发:120s≤gap<5min|参数STALL_SLEEP_MS=5min|版本1.2.9 停摆检测器 - 睡眠级停摆大红横幅仅观测 — 停摆 ≥5 分钟时打白字红底大横幅,写明停摆时长、时间段和推测原因;第二行提示「停摆期间 kami 血量仍在流失且无人停采,请立即插上电源/关闭自动睡眠;正在自动急救」。触发:gap≥5min|参数
STALL_SLEEP_MS=5min|版本1.2.9 停摆检测器 - 停摆原因人话诊断(决策树)仅观测 — 按顺序判断:
navigator.onLine为 false → 网络当前断开;近 2 小时停摆 ≥3 次 → 碎片化睡眠,笔记本没插电源,电池模式下系统反复自动睡眠(Mac 的防睡眠设置只在插电时生效);否则 → 电脑睡眠/待机或浏览器被挂起。不发任何新请求。触发:睡眠级停摆时|参数碎片化判定阈值=近2小时≥3次|版本1.2.9 停摆检测器 - 醒来急救去重 — 急救正在进行时再次触发,只打「急救已在进行中,跳过重复触发」,不重复执行。用
__stallRescueInFlight标记,finally 里复位。触发:睡眠级停摆触发急救时|版本1.2.9 停摆检测器 - 急救尊重紧急锁(排队不硬闯) — 紧急锁被占用时,调
waitForEmergencyRelease最多等 300 秒;超时就跳过本次急救死亡扫描(橙色日志)。没有等待接口时也直接跳过。和常规交易入口的锁策略一致。触发:醒来急救开始时|参数等锁超时=300000ms|版本1.2.9 停摆检测器 - 醒来立即死亡扫描 — 醒来后立即 await
checkMyKamiDeath()。发现死亡时会连带触发批量复活、紧急停采和杀手警戒。函数不可用时只打日志。不新增任何直接发交易的路径,只调现有的死亡扫描。注释写「checkMyKamiDeath内部锁保护」,但它本身代码里没有锁,复活防重发在_reviveDeadBatch内部。触发:睡眠级停摆急救|版本1.2.9 停摆检测器 - 醒后 90 秒复检 — 急救扫描之后 90 秒再做一次死亡扫描,复检前同样等紧急锁(最多 300 秒,超时放弃复检)。用
setTimeout异步执行,异常 catch 后打日志;排定时打一行「醒后索引可能滞后,90s后复检」。触发:醒来急救后 90 秒|参数STALL_RECHECK_MS=90s|来历0716 实测:刚醒时扫描可能读到滞后的索引,误报「无死亡」|版本1.2.9 停摆检测器 - 醒来不强制抢跑主循环 — 醒来后不直接调
runAutomation,只提示「建议等主循环下轮(≤10分钟)」。触发:醒来急救末尾|来历查证runAutomation开头没有 running 标志或重入锁,醒来强行调用会和周期主循环并发|版本1.2.9 停摆检测器 - 检测器自身异常隔离 — tick、急救、急救 Promise、90 秒复检都各自 try/catch,自身出错只打日志,不影响主流程。急救以 fire-and-forget 方式调用,不阻塞 interval 回调。触发:全程|版本1.2.9 停摆检测器
- 停摆检测器启动日志仅观测 — 启动时打绿色日志,写明每 30 秒心跳、≥120 秒记短停摆、≥5 分钟触发睡眠级急救。触发:初始化完成时|版本1.2.9 停摆检测器
1.14 保活
防止页面长时间没人操作被降速或断开。
防断连模拟点击
- 真实鼠标活动计时(
isTrusted守卫) — 记录用户最后一次真实动鼠标或点击的时间,供判断「是否长时间无人操作」。window 上监听 mousemove 和 click,只有e.isTrusted为 true(真人事件)才刷新lastMouseActivity,合成事件一律忽略。触发:initIdleKeepAlive()启动时注册,常驻|来历两套保活各自独立判断真人:忽略人化保活和本模块自己派发的合成事件,免得被当成真人活动而误跳过模拟 - 定期空闲检查循环(带随机抖动) — 一个永不退出的循环,隔一段时间醒来检查一次,长时间无人操作就模拟一次鼠标点击,防止被判离线或断开。每轮等 60 秒 +
getRandomDelayMs(1)。注释写「约 60 秒 + 抖动」,代码实际getRandomDelayMs(1)是 0~60 秒,所以每轮间隔 60~120 秒。按代码实际:这个模块没有开关,setKeepAlive('off')关不掉它(那个命令只管人化保活)。触发:启动主流程第一个拉起initIdleKeepAlive(),之后常驻|参数循环基础间隔 60*1000ms + 0~60s 随机|来历间隔不完全固定,行为更接近真人 - 60 秒无真人活动才模拟 — 距上次真实鼠标活动不到 60 秒就跳过这一轮,真人在用时不打扰。now -
lastMouseActivity> 60 秒才执行模拟,否则打「🟢 有鼠标活动,跳过模拟」。触发:每轮循环醒来时|参数空闲判定阈值 60*1000ms|来历真人在用时不干扰 - 左下角合成鼠标点击 — 在屏幕左下角 (10, 窗口高-10) 处,按真实点击的顺序派发一整组鼠标事件。
elementFromPoint取该坐标下的元素,取不到退回document.body;依次派发 mousemove、mousedown、mouseup、click,bubbles 和 cancelable 都为 true,保证游戏的事件监听收得到。代码没有检查该坐标下的元素是不是按钮,安全性依赖左下角恰好是空白处(不像人化保活那样用白名单)。触发:判定空闲时|参数坐标 x=10, y=innerHeight-10|来历固定点左下角空白处,避免误点游戏内按钮 - 模拟点击日志限流仅观测 — 模拟点击的日志只在第 1 次和之后每满 10 次各打一条,避免每分钟一条刷满日志缓冲区。
__idleSimCount累加,等于 1 或能被 10 整除时打「💻 模拟点击屏幕左下角(累计 N 次)」。按代码实际:「有鼠标活动,跳过模拟」那条日志没有限流,真人在用时每轮都会打。启动时另有一条「🌀 已启动 idle 模拟机制」。触发:每次模拟后|参数限流步长 10|来历防止日志缓冲区被刷满
人化活动保活
- 人化保活总开关
setKeepAlive— 开关人化活动保活,默认开启。localStorage的kami_keepalive值为 'off' 时禁用;读取出错按开启处理。setKeepAlive只接受 'on' 或 'off',其他参数打印用法和当前状态;设置后打「✅ 活动保活已切为 X」。每次定时触发都会重新读开关,改完立即生效。整个模块全程 try/catch,异常静默跳过,不发任何 tx。触发:命令;每次定时触发时读取|命令setKeepAlive('on')/setKeepAlive('off');不带参数则显示当前状态|存储kami_keepalive|版本1.1.22 - 真人活动监听与 3 分钟让路 — 监听真人操作,3 分钟内有过真人操作时,合成移动和列表扫掠全部跳过,不打扰玩家,也不污染对照实验。用 capture+passive 方式监听 mousedown、keydown、wheel、mousemove、pointerdown、touchstart,只有
isTrusted的事件才刷新__lastRealActivity;_humanActive()= 距上次真人操作不到 3 分钟。注释只列了 5 种事件,代码实际还多监听了 touchstart。触发:模块注入即注册,常驻|参数REAL_IDLE_MS=3分钟|来历不打扰玩家,也不污染对照实验数据|版本1.1.22 - 约 75 秒一次合成 mousemove — 定期在页面上派发一次随机位置的合成鼠标移动,让页面「看起来有人在」。在 document 上派发 mousemove,坐标 x 100~700、y 100~500 随机(
isTrusted=false)。开关开且无真人活动时才派发并计数;开关开但真人活跃时计一次「让路」。周期 75 秒 + 0~15 秒随机;脚本注入 45 秒后第一次触发,不等启动主流程。触发:自动:setTimeout自循环|参数MOVE_BASE_MS=75000 + 0~15s 随机;首次 45s|来历0710 某账户组件同步滞后风暴时,页面可见、WS 和区块流都正常,嫌疑集中在 app 级「无操作降速」,于是做模拟真人活动的对照实验|版本1.1.22 - 约 5 分钟一次人化全程扫掠 — 定期把 kami 列表像真人一样慢慢滚到底、再滚回顶、最后回到原位,全程约 20 秒。只在开关开、无真人活动、未持有紧急锁时启动扫掠,否则(开关开时)计一次「让路」。周期 5 分钟 + 0~60 秒随机;脚本注入 90 秒后第一次触发。触发:自动:
setTimeout自循环|参数SWEEP_BASE_MS=5分钟 + 0~60s 随机;首次 90s|来历对照实验:风暴消失就坐实并保留,无效就下轮撤掉|版本1.1.22 - 紧急锁期间跳过扫掠 — 紧急停采持有 TX 紧急锁时,本轮不扫掠。直接读
window.__txEmergencyLock,没走hasEmergencyLock(),所以不会顺带触发 10 分钟超时自愈。被锁挡住也记进「真人让路」计数。注释写「紧急锁期间顺延」,代码实际是本轮跳过,等 5~6 分钟后的下一轮再判断。扫掠开始后中途不再检查锁。触发:每次扫掠定时触发时|存储window.__txEmergencyLock(读)|版本1.1.22 - 扫掠防重入 — 上一轮扫掠还没结束时,不会再开新一轮。用
__sweepRunning标记,开始时置 true,finally 里复位为 false。触发:_sweep()入口|版本1.1.22 - 定位 kami 列表滚动容器 — 找到真正可以滚动的 kami 列表容器,找不到这一轮就不扫。从 Party 列表第一张卡片开始沿父元素往上找,第一个
scrollHeight>clientHeight+10 的元素就是滚动容器;一张卡片都没有、或一直找到 body 都没找到时返回空,本轮直接退出。触发:_sweep()开始时|版本1.1.22 - 保活点击白名单锚定(HP 文本 / 状态图标) — 扫掠前的一次点击,只点两种确认安全的纯展示元素,绝不随机点。主锚点:卡片里没有子元素、宽高都大于 5px、整段文字完全匹配「数字/数字 (数字%)」(
_HP_RE全匹配,防止含这个格式子串的其他元素混进来)的 HP 文本。备用:状态图标,优先 img[src*="/assets/kami_harvesting"],否则kami_* 图标(宽高大于 3px)。主锚点有就只用主锚点,都没有才用图标;从候选里随机选一个,在中心坐标派发 mousedown、mouseup、click。触发:每次扫掠开始时|参数_HP_RE=/^\s*\d+\s*\/\s*\d+\s*(\s*\d+\s*%\s*)\s*$/|来历真钱教训:游戏的喂食、停采按钮都是纯 div,旧版「排除 button/a 后随机点 div」点开了喂食菜单,下轮又点中食物,误喂了 Half Heart / Cleaning Fluid 等真钱道具|版本1.2.3 - 点击找不到锚点时本轮不点(fail-closed) — 两种锚点都认不到时,这一轮干脆不点,并计数方便排障。候选池为空就
__keepaliveNoTarget++ 后 return,这个数会出现在小时汇总里。触发:_safeClick内|来历交互必须正向锚定,认不出就不动手|版本1.2.3 - 语义按钮兜底排除 — 选中的锚点如果在按钮、链接或可点击元素里面,就不点。
t.closest('button,a,[role=button],[onclick]')命中就 return。注释说明:事件会冒泡,这个检查挡不住 React 在根节点的事件委托;但两个锚点的祖先是卡片行(点了只是选中,不发 tx),里面不含喂食按钮,所以误喂链已经断了(经 grok 审查确认)。触发:_safeClick内|来历多一层语义兜底|版本1.2.3 - 扫掠滚动节奏(仿真人) — 模仿真人的节奏滚动列表:小步走、随机停顿、偶尔停下来,到底后歇一会再回顶,最后回到原位。每步滚 250~420px,停 300~700ms,每步有 12% 概率额外停 1.5 秒;到底后停 0.8~1.5 秒再往回滚;每个方向最多 120 步(防死循环);结束后
scrollTop恢复原位,扫掠计数 +1。触发:_sweep()内|参数步长 250~420px;停顿 300~700ms;驻足 12%×1.5s;每方向最多 120 步|版本1.1.22 - 扫掠中真人来了立即还原 — 滚动过程中一旦检测到真人操作,立刻停下并把列表还原到原来的位置。每滚一步都检查
_humanActive(),是则scrollTop=origin、让路计数 +1,打橙色日志「检测到你在操作,已立即停止滚动并还原位置」后退出。触发:扫掠每一步后|来历不打扰玩家|版本1.1.22 - 扫掠可见性提示日志仅观测 — 扫掠开始和结束时各打一条控制台说明,让用户知道列表自己滚动是脚本行为。开始时打蓝色说明:脚本在自动滚动 kami 列表防浏览器降速,约 20 秒,不是电脑被入侵,动一下鼠标就停止并还原,永久关闭用
setKeepAlive('off')。结束时打绿色「自动滚动结束,列表已回到原位」。只打日志,不碰游戏 UI。触发:每次扫掠开始/结束|命令setKeepAlive('off')|来历用户反馈列表突然自己滚动,不明真相会以为电脑被黑|版本1.2.18 - 保活每小时汇总日志仅观测 — 每小时汇总一次:合成 mousemove 次数、全程扫掠次数、真人让路次数、点击找不到锚点次数,汇报后清零。在
_tickMove里判断距上次汇报 ≥3600000ms 就输出。统计口径:开关开且真人活跃时每个 75 秒周期计一次让路,扫掠被紧急锁挡住也计让路,所以「真人让路」里混有锁让路。按代码推断:计时从脚本注入开始算,而定时刷新每 45~50 分钟整页刷新一次(计数随之归零),正常会话里到不了 60 分钟,这条汇总基本不会输出,除非刷新被紧急停采或操作让路推迟。触发:_tickMove每次运行时检查|参数汇报间隔 3600000ms|版本1.1.22 - 人化保活启动说明日志仅观测 — 模块启动时打两条说明:机制概要(75 秒合成移动 + 5 分钟扫掠,真人 3 分钟内让路,关闭命令),以及「每约 5 分钟自动滚动一次列表不是异常,届时有蓝色提示」。模块是注入即执行的 IIFE,不经过启动主流程的 120 秒等待。触发:脚本注入时|命令
setKeepAlive('off')|版本1.1.22
1.15 日志、调试与诊断
日志怎么打、怎么存、怎么开调试,以及一批只打日志、不改行为的诊断传感器。
日志输出
- 日志时区设置 — 所有日志和时间显示使用的时区,默认 'auto' 跟随浏览器本地,也可写死小时数(可带小数)。加载时算一次偏移量
__TZ_OFFSET_MS,生成标签__TZ_LABEL(如 UTC+8)和 ISO 后缀__TZ_ISO_SUFFIX(供 gas 时间戳解析)。只影响显示;gas 账本按 epoch 毫秒存,中途改时区不影响统计。偏移只在加载时计算,时区变化要刷新才生效。触发:脚本注入时计算一次|参数TZ_OFFSET_HOURS='auto'|版本v1.1.3 起 log()统一业务日志出口 — 全脚本业务日志的唯一出口:自动加 [核心脚本][时间] 前缀,打到控制台,同时把纯文本副本写进内存日志缓冲区。首参以 %c 开头时把前缀插在 %c 后面,保证样式生效。时间按配置时区格式化为 YYYY-MM-DD HH:MM:SS。注释写「剥掉 %c 样式标记」,代码实际只删掉 %c 字符,配套的 CSS 样式字符串仍会原样写进缓冲区;多行文本也不拆行。触发:全脚本各处调用- 跨脚本共享日志缓冲区 —
window.__kamiLogBuffer是全局字符串数组,供saveKamiLogs()一键导出完整日志。用window.__kamiLogBuffer|| [] 初始化:辅助脚本已建就复用,脚本重载或多脚本共存时不清空已有日志。页面刷新即清空,持久化靠导出。触发:脚本注入时初始化|命令saveKamiLogs() - 日志对象序列化防抛错 —
log()把对象参数转成 JSON 写入缓冲区,序列化失败(比如循环引用)就回退成 String,保证 log 自身不因序列化抛错。JSON.stringify外包 try/catch。触发:每次log() - clog 控制台输出同步入日志 — 替代脚本自有的
console.log:控制台显示一字不变(%c 彩色照常),另把纯文本副本写进日志缓冲区。纯文本按换行拆开,每行单独入库、各带时间戳,方便 grep。缓冲区不是数组时重建。入库部分全程 try,日志功能绝不能把主流程搞挂。触发:启动命令清单、gas报告、模式/通道切换回执、告警等处调用|来历0911实盘:showGasReport()被自动触发164次,日志文件里零记录;用户定案「我要都走日志,方便事后检查日志」|版本1.2.32 __plainArgs样式剥离 — 把 console 参数转成可读纯文本:删掉首参里的 %c,并跳过与之配套的 CSS 字符串参数。数出首参里 %c 的个数 N,跳过后面 N 个字符串参数;其余字符串原样保留,对象 JSON 化,失败回退 String。触发:clog 调用|版本1.2.32- ctable 表格输出入日志 — 替代
console.table:控制台照常画表,日志里先写「(表格 N 行)」,再每行一条 JSON。console.table失败时回退console.log。非数组包成单元素数组。每行序列化失败回退 String。整体 try 包住。触发:脚本需要表格输出处调用|版本1.2.32 wrapManual调用来源标记 — 包装挂到 window 上的控制台命令,每次调用先打一条入口标记,区分用户手敲和外部脚本调用。先读window.__kamiCallSource,读完立即 delete(一次性消费)。有来源打蓝色「🤖 [脚本调用@来源] 命令(参数)」,没有打紫色「🖐️ [手动调用] 命令(参数)」。参数里字符串加引号、函数记 fn、序列化异常记 …,然后原样透传调用并返回结果。只包装window.xxx入口(后文统一包装16个命令),内部以裸函数名调用的路径不打标,避免刷屏。触发:window 命令被调用时;杀手监控等外部脚本调用前写__kamiCallSource|来历读后立即删除,防外部调用崩溃后残留,把下一次手动调用误判成脚本调用_fmtMinSec耗时格式化仅观测 — 把毫秒数格式化成「X分X.X秒」,用于日志里的耗时说明。分钟向下取整,秒保留1位小数。触发:各日志处调用- 部署清单单行格式化
_fmtStartList仅观测 — 把待部署清单压缩成一行,每只格式为「#数据库编号/img图片编号/kid:前6位…」,逗号拼接。缺kamiId时显示 kid:N/A;截断长度 6 写死;字段缺失不抛错。触发:批量部署日志输出处|来历便于在控制台快速对照每只 Kami - 停采清单单行格式化
_fmtStopList仅观测 — 把待停采清单压缩成一行,每只格式为「#数据库编号/img图片编号/hid:前6位…/Δ=两位小数」。缺harvestId时显示 hid:N/A;delta 不是数字时不附 Δ;Δ 是清单构造方传入的指标,只原样展示。触发:批量停采日志输出处
错误与异常入日志
console.error抄送入日志仅观测 — 替换console.error:先原样转发给控制台,再把内容以 [ERROR] 标签写进日志缓冲区,保证导出的日志带完整错误现场。Error 对象展开成 name、message 和 stack,其余参数 JSON 化,失败回退 String。游戏自身的 error 也会被抄送。触发:任何console.error调用|命令saveKamiLogs()console.warn抄送与噪声过滤仅观测 — 替换console.warn:控制台照常显示,写日志前先过噪声名单,命中的不入库,其余以 [WARN] 标签写入。噪声名单6条正则,都锚定行首:getSourceID/getAccountIndex/getOperatorAddress/getOwnerAddress/getRoomIndex的「(): undefined」告警,以及「no onyx item found」。[TXQueue]/[queue] 这类有价值的 WARN 全部保留。触发:任何console.warn调用|参数__WARN_NOISE_PATTERNS(6条正则)|来历游戏自身对entity 0/未知实体的getter告警每5秒一轮,不过滤时单份日志30-40%是噪声- 全局未捕获异常记录仅观测 — 监听 window 的 error 事件,把未捕获异常以「[UNCAUGHT] 消息 at 文件:行:列」写进日志。只写缓冲区,不影响控制台。触发:页面出现未捕获异常时
- 未处理Promise拒绝记录仅观测 — 监听 unhandledrejection,把未处理的 Promise 拒绝以 [REJECTION] 标签写进日志。reason 是 Error 时记 name: message,否则记 String(reason)。触发:出现未处理 rejection 时
日志保存
- 日志导出为 txt 文件下载 — 把内存日志缓冲区一次性导出成 .txt 文件,触发浏览器下载到「下载」文件夹。日志按换行拼接成 Blob(text/plain;charset=utf-8),创建隐藏的 <a download> 并
click()触发下载,随后移除 <a> 并revokeObjectURL释放内存。完成后打绿色日志「已保存 N 条日志到 下载文件夹/文件名」。触发:smartReload刷新前、定时刷新前、卸载钩子、用户手动saveKamiLogs()|命令saveKamiLogs()|存储window.__kamiLogBuffer(读)|来历刷新后内存日志会丢,必须先落盘 - 文件名带账户名(反查失败则省略) — 文件名格式是
kami_log_<账户名>_<日期>_<时间>.txt,多号日志一眼能分清。由connectedAddress通过explorer.accounts.getByOperator反查账户名,整段包在 try/catch 里,接口没就绪时静默留空;账户名为空时文件名直接省略这一段。触发:saveLogsBeforeReload()内部 - 导出前做一次低余额告警检查 — 导出日志前先检查一次账户余额是否偏低,产生的告警日志会一起写进同一份文件。调用
checkLowBalanceOnce(账户名或'(unknown)', 'end'),外面包 try/catch;stage='end' 不受「只查一次」限制。顺序不能颠倒,否则告警进不了文件。注释写「导出前结算一次 gas 统计」,代码实际调用的是低余额检查,没有 gas 结算。触发:每次保存日志时|来历先检查后导出,告警日志才能进同一份文件 - 空缓冲区不生成文件 — 缓冲区里没有日志时不下载空文件,只打一条「无日志需要保存」。
logs.length===0 时直接 return;这种情况不更新__lastLogSaveAt。触发:saveLogsBeforeReload()内部|来历避免下载空文件 - 文件名时间按配置时区换算 — 文件名里的日期和时间统一按配置的时区生成,多台机器的日志文件名能对齐。now +
__TZ_OFFSET_MS后取 ISO 字符串,日期取 YYYY-MM-DD,时间取 HH-MM-SS(冒号换成连字符)。TZ_OFFSET_HOURS='auto' 时用浏览器本地时区。注释写「时区偏移(8 小时)内置」,代码实际可配置,默认 auto。触发:saveLogsBeforeReload()内部|参数TZ_OFFSET_HOURS='auto'|来历便于多机日志按同一时区对齐 - 记录上次保存时刻供去重 — 每次成功保存后记下时间,供「卸载前抢救日志」判断刚才是不是已经存过。
__lastLogSaveAt=Date.now()。这是内存变量,页面刷新(新会话)后归零。触发:每次成功保存后|来历自动刷新路径是先保存再 reload,不去重会产生两份重复文件|版本1.2.19 - 控制台命令
saveKamiLogs— 随时手动把当前全部日志导出到下载文件夹。window.saveKamiLogs=saveLogsBeforeReload;也在「手动调用」标记列表里,调用时日志会标 🖐️ [手动调用]。触发:命令|命令saveKamiLogs()
手动调用标记
- 控制台命令统一加手动/脚本调用标记仅观测 — 把 16 个面向用户的控制台命令统一包一层,每次调用都在日志里写明是手动敲的还是脚本调的,并带上参数,事后查日志能分清人工和自动。遍历命令名列表,仅当 window[name] 是函数时才替换成
wrapManual(name, fn),不存在就静默跳过。wrapManual:读window.__kamiCallSource,读完立即 delete(一次性消费);有来源打蓝色「🤖 [脚本调用@来源] 命令(参数)」,没有则打紫色「🖐️ [手动调用] 命令(参数)」。参数用 JSON 序列化,函数显示为 fn,序列化失败退回 String 或「…」。原命令功能不变。触发:脚本加载时执行一次原地替换;之后每次调用命令时记日志|命令emergencyStopHarvest()、stopCurrentRoom()、setKamiMode(mode)、getKamiMode()、clearBlockedKamis()、clearStopBlockedKamis()、showStopBlockedKamis()、showBlockedKamis()、clearFeedFails()、clearStarvingStuck()、showGasRules()、clearXPPotionFed()、clearFortifiedFed()、saveKamiLogs()、showGasReport()、showMyKillers()|来历排查问题时区分自动流程和人工操作非常关键 - 跨脚本调用来源约定
__kamiCallSource仅观测 — 杀手监控等外部脚本调用核心脚本的 window 命令前,先写入调用来源,这样日志里显示「脚本调用@来源」,不会被误记成手动。调用方在调用前给window.__kamiCallSource赋值;包装器读完立即删除,防止上次调用崩溃后残留,把下一次真正的手动调用误判成脚本调用。控制台手敲不会赋值,默认归为手动。只包装window.xxx入口,脚本内部直接用函数名调用的路径不受影响。触发:外部脚本调用被包装的命令时|存储window.__kamiCallSource(跨脚本全局变量)|来历一次性消费,防崩溃残留误判下一次手动调用 - 刻意不包装的入口 — 有几个入口故意不加手动标记。
kamiDebugOn/kamiDebugOff内部会location.reload,刷新后标记不稳定;waitForEmergencyRelease是给辅助脚本用的 API,不算用户手动命令。另外按代码实际:setKeepAlive定义在这个列表之后、也不在列表里,所以调用时同样不带手动标记。触发:脚本加载时|来历debug 开关带刷新副作用;辅助脚本 API 不属于人工操作
调试开关
- 分类调试日志 dlog — 按标签分类的调试日志开关,平时全部静默,排查时只开需要的几类;输出走 clog 进日志。总开关
kami_debug为 '1' 才开。标签默认值 parse/start/stop/api/dom/feed 全为 false。dlog 在总开关关闭时第一行就 return(零开销);标签被显式设为 false 才拦,默认表里没有的新标签放行。开关只在加载时读一次,改完要刷新。__DBG_ON那次localStorage读取没包 try/catch。触发:各板块调用dlog(tag, ...)|参数__DBG_DEFAULT={parse,start,stop,api,dom,feed 全 false}|命令kamiDebugOn({ parse: true })/kamiDebugOff()|存储kami_debug/kami_debug_flags|来历避免控制台刷屏 - 调试flags损坏回退 —
kami_debug_flags内容损坏或不存在时,自动回退到默认标签表。JSON.parse包 try/catch;空字符串解析失败也走默认值。触发:脚本注入时初始化|存储kami_debug_flags kamiDebugOn(flags)开启调试命令 — 打开调试总开关,并把传入的标签合并进已有设置,然后刷新页面生效。写kami_debug='1',kami_debug_flags=JSON({...当前,...flags}),再location.reload()。因为默认全 false,只有传入 true 的标签会打开。触发:控制台手敲|命令kamiDebugOn({ parse: true })(可选标签 parse/start/stop/api/dom/feed)|存储kami_debug/kami_debug_flags|来历开关只在加载时读取,必须刷新kamiDebugOff()关闭调试命令 — 关闭调试总开关并刷新页面。写kami_debug='0' 后location.reload()。kami_debug_flags不清除,下次开启时沿用。触发:控制台手敲|命令kamiDebugOff()|存储kami_debug
前端冻结传感器与每小时心跳
- 前端冻结门闩读取口
_isFrontendFrozen— 给停采/喂食/拉黑等业务门闩读「前端是否冻死」的统一入口。window.__frontendFrozen是布尔就返回它,否则返回 false,传感器没接线时保持旧行为。后文多处业务代码调用。触发:被业务门闩调用|来历Bug B:前端卡死时禁止新增拉黑、禁止放弃喂食|版本v1.1.25 - 前端传感器·冻死判定 — 判断 MUD 前端同步流是否卡死(
WebSocket断或后台被节流),结果写入window.__frontendFrozen供门闩使用;本身不发 tx、不刷新页面。至少两个独立信号、以blockStalled为锚才判冻死:blockStalled&&connDown、blockStalled&&timerDrift、blockStalled&&canaryNull、connDown&&canaryNull;hidden 只作旁证。代码注意:canaryNull读window.__frontendCanaryNull,全文件无处写入,恒为 false;gated 读window.__emergencyLockHeld,全文件也无处写入,门控恒不生效。实际等效为blockStalled且(connDown或timerDrift)。触发:runAutomation/emergencyStopHarvest入口调用window.__evalFrontendFrozen(expectedIntervalMs)|参数BLOCK_STALL_MS=90000|命令showFrontendSensor()|来历v1.1.25根因:MUD同步流卡死→explorer读冻旧值→停采发不出、喂食库存读0、gas谎报成功→误拉黑/放弃喂食→kami饿死|版本v1.1.25 - 前端传感器·区块号订阅与基线 — 订阅
window.network.network.blockNumber$,区块号一变就记下前进时间。订阅成功才建立基线,没基线不判 stalled,避免刚启动就误判冻死。订阅时立即读一次当前值。首次订阅成功打一条「🧊 已订阅blockNumber$,传感器启动」,靠_subscribed天然去重。触发:每次__evalFrontendFrozen时尝试(已订阅则跳过)|来历Initia出块快,区块号是最可靠的同步心跳|版本1.1.17 - 前端传感器·不可订阅时轮询退路 —
blockNumber$不是可订阅对象时,每轮直接读值比较,也能判断区块有没有前进。依次尝试getValue()、.value、原始值。首次读到值时建立基线,并只播报一次「退化为轮询读值模式」(_fallbackAnnounced防刷屏)。触发:每次评估且未订阅成功时|版本1.1.17 - 前端传感器·WS断连信号 — 读
network.connected,判断WebSocket是否断开。兼容布尔、Subject.getValue()、{value} 三种形状,只有明确为 false 才算断。读不到或异常一律算未断(不判冻死)。触发:每次评估|版本v1.1.25 - 前端传感器·后台节流计时漂移 — 两次评估的实际间隔远超预期时,判为疑似被浏览器后台节流。实际间隔 ≥ 预期×3(预期缺省600000ms)时开始计时,持续≥60秒才判
timerDrift=true;间隔一恢复就清零。触发:每次评估|参数TIMER_DRIFT_RATIO=3;TIMER_DRIFT_SUSTAIN_MS=60000;缺省预期间隔600000ms|版本v1.1.25 - 前端传感器·读失败降级 — 评估过程出任何异常都返回 false,不判冻死,也不打断主循环。整个评估包在 try,catch 里 return false(注释标 I2)。触发:评估异常时|来历读失败不应误触门闩或中断主流程|版本v1.1.25
- 前端传感器·冻死翻转日志仅观测 — 冻死状态发生翻转时打一条日志(冻死红色、恢复绿色),附带各信号的当前值。frozen 和上一次不同才打,不重复刷屏。内容含
blockStalled、块号、停滞秒、已订阅、connDown、timerDrift、canaryNull、hidden。触发:判定结果翻转时|版本v1.1.25 - 前端传感器·冻结瞬间深快照仅观测 — 刚判为冻死的那一刻,dump 一份现场快照,用来抓组件仓滞后的根因。内容:
blockNumber$当前值和停滞秒、connected 原值、explorer.kamis/txQueue是否就绪、停采退避队列长度(__stopPendingVerify.size)、document.hidden/visibilityState、rAF近窗最大帧隔、WS 状态。每项独立 try,纯观测。触发:frozen 翻转为 true 时|来历抓某账户组件滞后风暴根因(区块流正常但组件仓单独滞后=已知盲区;connected翻false=WS问题)|版本1.2.1 - 前端传感器·每小时心跳仅观测 — 每小时打一条灰色健康心跳,把区块同步、渲染帧率、WS、可见性、RPC 延迟几条健康线汇总在一行。
_lastHeartbeatAt初始为0,所以启动后第一次评估就打首条,之后间隔≥1小时再打。内容含 frozen、已订阅、当前块、停滞秒、connDown、timerDrift、hidden、componentLag、退避未确认只数、rAFfps/最大帧隔/是否冻结、WS 描述、visibility、RPC p50/p90/失败数及测量时间。触发:评估时距上次≥1h|参数间隔3600000ms|版本1.1.17 - 组件滞后补盲信号
componentLag仅观测 — 停采退避复读队列里只要有条目复读≥4次(≥90秒档)仍没确认已停,心跳就标componentLag=疑似。只进心跳日志,不进 frozen 判定,不影响门闩,留给后续标定。触发:每小时心跳时计算|参数attempts≥4|来历某账户组件滞后可达数十分钟而blockNumber$仍正常前进=blockStalled盲区|版本1.1.22 rAF渲染帧率监视器仅观测 — 用一个空的requestAnimationFrame回调计帧,测前端渲染循环是否被浏览器节流冻结;和区块同步是两条独立的健康线。启动即运行,只计数和记最大帧间隔,不会阻止节流(正好如实测量)。心跳时读窗口统计后清零;fps<1 或单帧间隔>5000ms 判renderFrozen。首次读窗口按1小时算分母。只进日志,不进门闩。触发:脚本注入即启动;心跳时读取|参数renderFrozen判据:fps<1 或 最大帧隔>5000ms|来历用户0712实测:关显示器/切后台时浏览器节流rAF,游戏推算的实时HP停更,晃鼠标后HP突跳|版本1.2.5- RPC网速诊断仅观测 — 测到链上 RPC 的往返延迟和失败率,结果写进心跳。连续调4次
provider.getBlockNumber、每次间隔300ms,排序取 p50/p90,统计失败数。启动30秒后先测一次;之后每次心跳踢一次、不等结果,结果留给下次心跳打印。_rpcProbeRunning防重入;没有 provider 就记 note='无provider'。零 tx,零 gas。触发:启动30s + 每小时心跳|参数RPC_PROBE_N=4;间隔300ms|来历tx快慢的真因是到链上RPC这条链路而非本机宽带;用户0712定案纯诊断、不据此自动调行为|版本1.2.6 showFrontendSensor()传感器查看命令 — 在控制台打印传感器当前状态。用 clog 输出对象:已订阅、当前块、区块停滞秒、frozen。不经wrapManual包装,不打调用标记。触发:控制台手敲|命令showFrontendSensor()|版本v1.1.25- Tampermonkey长source link无法缩短说明仅观测 — 说明控制台每行后面那串
userscript.html虚拟 URL 在脚本内部改不掉,想看清爽日志请用saveKamiLogs()下载。在kamigotchi.io的 CSP 加 V8 规则下实测过:new Function、iframe console wrapper 等改写 console 的方案都缩不短。触发:文档(不执行)|命令saveKamiLogs()|来历source link是Tampermonkey注入机制生成的虚拟URL
环境指纹与账户信息打印
- 环境指纹自动诊断仅观测 — 启动60秒后自动把排障常问的环境事实写进日志,用户只要发日志,不用再手贴控制台探针。
window.network/explorer/txQueue没就绪时每30秒重试,最多再试3次,仍不行就打一条跳过日志。共输出4行:①通道、模式(kami_mode缺省 greedy)、explorer/txQueue/api/clock/LoadingState是否存在;②blockNumber$能否订阅及当前块、connected 的形状、frozen;③停采 system 的executeBatched与executeBatchedAllowFailure类型、api.stop源码前80字;④CSP meta 内容前120字。纯只读,全 try/catch。触发:自动:启动60s(未就绪再重试3次×30s)|存储kami_stop_tx_channel/kami_mode(只读)|来历首战立功:发现不同会话的allowFailure形状差异(一边=function一边=undefined)|版本1.1.21 - 账户信息 banner仅观测 — 在控制台打印账户总览:账户名、Owner、Operator、Kami 总数(高亮),以及当前地块名、编号和亲和属性。名称缺失显示「(未命名)」,地址缺失显示「(无)」;地块名通过
__getRoomNameByIndex查,查不到显示「未知」;以分隔线包围。触发:启动流程中游戏加载成功后执行一次 - 采集中地块分布统计(6 路并发池)仅观测 — 找出所有采集中的 Kami,逐只查它所在的采集节点,按地块聚合计数、降序打印「地块名 (#编号) | 采集中 N 个」;没有采集中的就打「(无采集中 Kami)」。内置小型并发池
runPool,并发度 6;单项查询异常写入 {error} 占位,不中断整批。触发:printAccountAndRooms执行时|参数runPoolconcurrency=6(调大更快但链上 API 压力更大)|来历避免一次性对链上 API 发起几十个请求 - 当前地块亲和属性展示仅观测 — 从全部采集节点里找到当前地块,把它的亲和属性转大写后用 + 拼接,附在「当前地块」行后面。用
nodes.all()按roomIndex严格相等查找;取不到或出错就静默不显示。触发:printAccountAndRooms执行时 - 启动时低余额检查仅观测 — 会话启动时以账户名调用一次
checkLowBalanceOnce(名称,'start'),检查余额是否低于警戒线。失败静默忽略,不影响主流程。注释头写「以账户名为 key 启动本会话 gas 消耗统计」,代码实际只剩低余额检查。触发:printAccountAndRooms执行时一次|版本1.2.7 - 钱包 deadzone/死地址自检自动刷新 — 账户所在房间号为 0,或 Owner/Operator 变成 0x…dead 占位死地址时,判定钱包连接异常:红字提示,10 秒后自动刷新页面,并跳过后续分析和预警。只有数值 0 触发(
roomIndex缺失显示「(未知)」时不触发);地址转小写后做包含匹配;调用smartReload('钱包连接异常:deadzone/死地址') 后 return。触发:printAccountAndRooms打印完地块分布后|参数刷新前等待 10000ms|来历钱包连接异常时继续挂机没有意义 - 调用辅助脚本地块适配分析 — 如果辅助脚本注入了
window.kamiAnalyze,就在 banner 之后 await 调用它做地块适配分析;没有就跳过。这里没有单独 try,kamiAnalyze抛错会被外层 catch 吞掉,同时跳过后面的高清算线预警和缺录提示。触发:printAccountAndRooms执行时(跨脚本可选) - 高清算线预警仅观测 — 以 API 实时 Kami 列表去关联本地库,把清算线 LT 超过 65% 的 Kami 按 LT 降序红字逐只列出(#编号、LT、body、链上状态),并提示「停采线上限仅 80%,极易被清算」和维护建议(升级 harmony / 重置技能加 defense,从最高的开始)。LT 为 null 或
NaN的不计。注释写「阈值 80% 与停采线上限 80% 对应」,代码实际判据是rec.LT> 65,80 只出现在提示文案里。触发:printAccountAndRooms执行时(启动一次)|参数LT 预警阈值 65(写死)|来历停采线封顶 80%,清算线过高的 Kami 缓冲空间极小,应优先升级降线;以实时列表为基准,已转出账户的 Kami 不会产生幽灵警告 - 清算线全部良好提示仅观测 — 没有高清算线 Kami 且本地库非空时,打绿色「账户内所有已收录 Kami 清算线均 ≤ 80%,状态良好」。文案写 ≤ 80%,代码实际判据是没有 LT > 65 的(或 LT 缺失);库为空时不打这句。触发:
printAccountAndRooms执行时 - 数据库缺录提示仅观测 — 账户里有但本地库没收录的 Kami,用橙色列出编号,建议刷新页面重跑精简数据库脚本。与高清算线预警在同一次遍历中收集。触发:
printAccountAndRooms执行时 - 账户打印整体异常兜底 — 整个账户/地块打印流程出任何错,都只打一行「获取账户/地块信息失败」,不影响启动。最外层 try/catch。触发:
printAccountAndRooms执行时
1.16 数据库恢复与自愈
启动时从 localStorage 恢复精简数据库;账户有新 kami 时增量补录,只补不删。
启动恢复
- 复用已加载的内存数据库 — 脚本注入时若
window.kami_core_db已经是数组,直接沿用、不覆盖,并打印条数和查询提示。用Array.isArray判断,是数组才走沿用分支,打印共 N 条和「控制台输入window.kami_core_db查询」提示。触发:脚本加载时执行一次(顶层代码)|命令window.kami_core_db(查看数据库内容)|来历避免覆盖同页其他脚本刚构建好的内存版 - 从本地存储恢复精简数据库 — 读取
localStorage的kami_core_db(JSON 数组),挂到window.kami_core_db供部署、停采、喂食、XP 药水等板块反查kamiId和清算线。读不到时按空数组 '[]' 处理;JSON.parse放在 try/catch 里,数据损坏只打「读取kami_core_db失败」,不中断脚本(此时window.kami_core_db保持未定义,下游用 || [] 兜底)。首次使用要先启用精简数据库脚本做一次全量构建,看到「构建完成」后可停用;不做的话库为空,部署、停采、喂食会因查不到kamiId/LT 而大量跳过。触发:脚本加载时执行一次|命令window.kami_core_db|存储kami_core_db(读) - 精简数据库 17 字段记录约定(跨脚本) — 每条 Kami 记录固定 17 个字段:index、
imgNumber、kamiId、harvestId、name、level、harmony、maxhp、body、hand、ratio、shift、LT、LTHP、vioBase、harmBase、powBase。由精简数据库脚本全量生成,本脚本增量维护。imgNumber是 string 类型,匹配时要String()转换;body/hand 是小写亲和字符串;LT 是清算线占最大 HP 的百分比,LTHP 是对应的 HP 绝对值;harvestId未部署时为 null。触发:部署/停采/喂食/XP 药水等板块按 index 或imgNumber反查时使用|存储kami_core_db
增量自愈 syncKamiDb
- 接口就绪等待 — 开始补全前先轮询等 network/explorer 接口就绪;最终拿不到钱包地址就跳过本次补全。每 200ms 探测
accounts.getByOperator、kamis.getByIndex、connectedAddress.value_三者,最多等 10 秒;地址仍为空时打「[DB增量] network/explorer 未就绪,跳过增量补全」,返回 {added:0,total:0,skippedFail:0}。触发:syncKamiDb调用时|参数就绪等待上限 10000ms / 探测间隔 200ms|来历兼容页面加载慢的情况 - 账户 Kami 列表拉取重试 — 用
getByOperator拉账户全量 Kami 列表,失败最多重试 3 次,间隔递增。拿到acc.kamis就停。注释写间隔 300/600/900ms(行内注释写 600/900ms),代码实际每次失败后等 300+tries×300,即 600/900/1200ms;第 3 次失败后还会白等 1200ms 才退出。3 次都失败时按空列表继续。触发:syncKamiDb调用时|参数列表重试 3 次|来历抗网络抖动 - 按 index 做差集,只补不删 — 把账户 Kami 列表和本地库按 index 对比,只挑出库里没有的 Kami 去补;已有记录即使数据陈旧也不动。库的 index 数值化后放进 Set,列表中 index 为有限数且不在 Set 里的算缺失。没有缺失时打「[DB增量] 账户 N 只 vs DB M 条,无新增」并返回 {added:0,total:N};有缺失时打橙色「发现 N 只账户内有但 DB 没有的 kami」。触发:
syncKamiDb调用时|命令rebuildKamiCoreDb()(全量重建,由数据库构建脚本提供)|来历全量刷新交给手动rebuildKamiCoreDb,自动流程绝不删改旧数据;库缺条目会导致部署跳过、停采漏判清算线、XP 药水找不到目标 - 逐只串行拉详情 + 指数退避 — 对每只缺失 Kami 串行调用
getByIndex(stats/traits/bonus/harvest/progress/time 全开)拉详情,失败最多重试 3 次。退避间隔 250×2^(t-1),即 250/500/1000ms(行内注释只写到 250/500ms);第 3 次失败后同样会白等 1000ms 再放弃。触发:发现缺失 Kami 时|参数详情重试 3 次|来历缺失量通常很小,串行即可,省掉并发控制的复杂度 - 详情拉取失败时写占位记录 — 某只详情 3 次都拉不到时,仍写入一条 17 字段齐全的占位记录,数值字段全部为 null。占位记录用列表里已有的信息填:index、从 image URL 正则提取的
imgNumber、kamiId、harvest.id、name、level,其余 null;skippedFail+1,打「#index 拉详情失败,已写入占位记录」后继续下一只。触发:单只详情拉取彻底失败时|来历保证下游 find 至少能按 index 命中,避免每轮都重复 miss、无限循环补拉 - 详情字段解析入库 — 成功拉到详情后,解析出 17 个字段组成记录并追加到内存库,打绿色「➕ #index (名字)
Lv.xLT=y% 已入库」。imgNumber从立绘 URL /kami/(数字).gif 提取(string);body/hand 亲和转小写;防御 threshold 的 ratio/shift 缺失按 0;harmony/maxhp 和三项 base 值缺失为 null。记录就地 push 进库,同时记进本次新增列表。触发:单只详情拉取成功时 - 旧公式清算线初值
__computeLT— 用「假想满配杀手」旧公式给新补的 Kami 算一个 LT/LTHP 初值,只在拿不到辅助脚本精确算法时兜底。harmony、maxhp、body 任一缺失就不算(LT 为 null);harmony 或 maxhp 不是正数返回 null。公式:E=normal 取 0.2,其余 0.5;deltaR=max(0,0.5−ratio);deltaS=max(0,0.4−shift);phi=正态 CDF(ln(V/harmony)),用 Abramowitz-Stegun 近似并夹到 [0,1];frac=clamp(phi×0.4×(1+E+deltaR)+deltaS);LT 保留两位小数。注释矛盾:常量上方行内注释仍写「必须与数据库构建脚本同公式同V_CONST」,板块头 0912 更正说这句是假的,代码实际只把它当入库初值。触发:详情解析入库时自动|参数V_CONST=41|来历0912 更正:数据库脚本 1.2.3 没有V_CONST,走DB_TOP_PREDATORS四档取最坏,辅助还含装备余量,三处算同一只得 66.13/62.75/72.75,真正对齐靠下面的精确重算 - 新增记录改用现役精确清算线重算测试线独有 — 补录结束后,只对本次新增的记录调用辅助脚本的
window.computePreciseLTForRecord,就地改写 LT/LTHP,老记录一个字节不动。逐条 try/catch:返回的 LT 不为 null 才覆盖,值有变化计为修正;单条异常就保留旧值并计失败。完成后打蓝色「新增 N 条已改用现役精确清算线重算(修正 x 条,y 条失败保留旧值)」。这个入口就是辅助每小时用的那个(现役杀手档案 + 装备余量 + 沉寂降级)。触发:syncKamiDb有新增记录时自动|来历0912 实测旧公式只会低估、从不高估(NORMAL 最大 −9.52pp,harmony=45 的例子停采线 74.48 低于真清算线 76.49,会漏停);辅助启动重算约 95s、核心补录约 120s+,辅助每小时重算又常赶不上 45~55 分钟一次的整页刷新,只靠辅助兜底会永久带病|版本测试版1.2.42 - 刻意不做全库重刷(防反向压低 LT)测试线独有 — 精确重算只作用于本次新增记录,不调用
refreshPreciseLT重算整个库。用__newRecs保存新增记录的对象引用,只遍历它。触发:新增记录重算时|来历全库重刷时,如果kami_top_predators缺失或四桶全空(例如辅助正在强制全网重扫),会回落内置默认档案,把全库 LT 压低,正好是漏停方向;辅助有__usable和 6 小时 TTL 闸门挡这一下,核心没有|版本测试版1.2.42 - 辅助脚本缺失红底告警测试线独有仅观测 — 有新增记录但检测不到辅助的
computePreciseLTForRecord时,打红底白字警告:这些记录用的是旧公式初值,只会低估,有漏停风险,请确认辅助脚本已启用。typeof 检查不是函数就进这个分支,只告警,不改数据。触发:新增记录重算时检测不到辅助入口|来历旧公式 NORMAL 最大低估约 9.5pp,停采线会跟着偏低|版本测试版1.2.42 - 补全结果写回本地存储 — 处理完后先把库挂回
window.kami_core_db,再整体写回localStorage持久化,打「完成:新增 x 条(失败占位 y 条),DB 现有 z 条」。setItem失败(例如配额满)时打「写回localStorage失败(内存已更新但下次刷新会丢失)」,本次会话仍可用内存版。触发:syncKamiDb补全流程末尾|存储kami_core_db(写) - 整体异常兜底 —
syncKamiDb任何未预料的异常都会被捕获,打「syncKamiDb异常」并返回全 0 结果,调用方不需要额外容错。最外层 try/catch,返回 {added:0,total:0,skippedFail:0};正常时返回 {added, total: 账户 Kami 数,skippedFail}。触发:syncKamiDb调用时 - 控制台命令
syncKamiDb()— 把syncKamiDb暴露到 window,控制台可随时手动执行一次增量补全。控制台输入syncKamiDb(),返回 Promise<{added,total,skippedFail}>。脚本启动或主循环也会在需要时自动调用(注释称页面加载后约 120s+)。触发:命令;启动/主循环按需调用|命令syncKamiDb()|存储kami_core_db
1.17 杂项工具
安装说明、Epoch 提醒、房间映射和几个底层小工具。
安装、更新与说明文档
- 四件套安装指引仅观测 — 四件套(核心/辅助/精简数据库/轻量杀手监控)装齐的步骤、每缺一件的后果,以及 beta 目录的四条 raw 安装链接。要在 Chrome 扩展详情里打开「允许用户脚本」(最容易漏,漏了装上也不跑)。装好后以启动横幅和篡改猴面板里的版本号为准,别拿「[版本检查] 已是最新」当判据:游戏页有 CSP,这行基本不会出现。触发:文档;控制台
安装说明()可打印|命令安装说明()|来历游戏页CSP使脚本fetch不到GitHub,版本检查行基本不出现 - 夜间挂机两项系统设置指引仅观测 — 必做:关掉电脑自动睡眠。可选:关掉 Chrome 对后台页的强节流(
IntensiveWakeUpThrottlingEnabled),不关夜里紧急停采会慢1~2分钟,测试版 1.2.59 起脚本按「默认会被节流」设计。Windows 管理员 cmd 用 reg add 写 HKLM Policies(强制级),完全退出 Chrome 再打开后生效。Mac 上 defaults write 写的是「推荐」级,这一项 Chrome 只认强制级,chrome://policy 显示 false 也不生效(2026-09-15 实测,看「级别」列);要 ⌘Q 完全退出后用open -na "Google Chrome" --args --disable-background-timer-throttling重开(同机多开每个实例加原来的 --user-data-dir)。触发:文档(不执行)|来历2026-09-14实测:窗口被挡或熄屏超5分钟后Chrome把定时器降到每分钟醒一次,紧急停采慢1~2分钟 - 更新与回退说明仅观测 — 更新不用重装,篡改猴会自动拉新版,也可以手动点「检查用户脚本的更新」。回退有两条路:从 Releases 页装历史快照,或请维护者发一版「旧内容+更高版本号」。Releases 快照里的 @
downloadURL仍指向 main。装完必须逐个脚本关掉「检查更新」,否则下个检查周期会被静默升回最新版。触发:文档(不执行)|命令安装说明()|来历篡改猴没有自动回退 - 新手术语速查仅观测 — 解释一批术语:清算线LT、停采线、delta、四种状态(RESTING/HARVESTING/STARVING/DEAD)、
mETH、musu、Operator、nonce、凑批、步长、幽灵kami、majority/minority、eye-half。normal 模式停采线=LT+3%,封顶80%。v1.1.1 起 LT 是精确清算线,由辅助脚本按官方公式加全网最强杀手档案维护。greedy 模式停采线=5%。启动时自动切到 eye-half 显示模式。触发:文档(不执行)|参数normal=LT+3%封顶80%;greedy=5%|版本v1.1.1 起 - 常用控制台命令速查(文件头)仅观测 — 在文件头按「模式与状态 / 停采与部署 / 黑名单与冷却 / XP药水 / 数据库与日志 / Gas统计」分组列出常用命令,完整清单以启动 banner 为准。列出
setKamiMode、getKamiMode、getTxLockStatus、stopCurrentRoom、stopMinorityForTransfer、resumeDeploy、clearBlockedKamis、showBlockedKamis、clearStopBlockedKamis、showStopBlockedKamis、clearFeedFails、clearStarvingStuck、feedXPPotionNow、clearXPPotionFed、showMyKillers、syncKamiDb、saveKamiLogs、showGasRules、showGasReport。触发:文档(不执行)|命令见 how 中列举 安装说明()命令 — 打印四件套安装链接、配套规则、夜间挂机设置、更新机制、回退办法和完整说明书链接,并显示当前测试版核心版本号。中文命令安装说明()与英文别名showKamiInstall()指向同一函数;全是硬编码字符串,不能改成 fetch 远端;只读不发 tx。触发:命令|命令安装说明()/showKamiInstall()|来历游戏页 SPA 运行时注入 CSP,raw.githubusercontent.com在游戏页永久拉不到(版本检查就是这么废掉的)- 测试版与公开版互斥警告 — 橙色提醒:这是测试版,公开版必须在篡改猴里停用。触发:
安装说明()输出|命令安装说明()|来历两个核心同时跑=双份交易+抢 nonce - 四件套缺一不可与安装步骤 — 说明核心/辅助/精简数据库/轻量杀手监控缺一块少一块功能;安装步骤:装 Tampermonkey→打开「允许用户脚本」(最易漏)→点四条
GitHubbeta 目录 raw 链接→看到启动横幅即成功,别拿「[版本检查] 已是最新」当判据。文案「缺杀手监控→不会自动切安全停采线」与贪婪模式板块「Feed 监控已停用、自动切换默认不生效」不一致,实际杀手保护是位置轮询触发紧急停采。触发:安装说明()输出|命令安装说明()|来历游戏页 CSP 拦外联,版本检查行基本不会出现 - 夜间挂机设置指引 — ①必做:关闭电脑自动睡眠(Mac 能耗设置/Win 电源永不);②可选:Chrome 后台页强节流(测试版 1.2.59 起按默认会被节流设计)。Win 给 reg add 命令(强制级);Mac 说明 defaults write 是推荐级不生效,给 ⌘Q 后
--disable-background-timer-throttling启动参数(多开加 --user-data-dir)。触发:安装说明()输出|命令安装说明()|来历电脑一睡页面 JS 全停;窗口被挡/熄屏超 5 分钟后定时器每分钟只醒一次,紧急停采会慢 1~2 分钟 - 更新与回退指引 — 更新:@name 固定不带版本号,篡改猴自动拉新版,可在面板手动检查更新;回退:从
GitHubReleases 装历史快照,装完必须逐个关掉「检查更新」否则会被静默升回;或联系维护者发「旧内容+更高 @version」全网秒回滚。篡改猴没有自动回退。触发:安装说明()输出|命令安装说明()|来历快照资产的 @downloadURL仍指向 main
Epoch 结束提醒
- Epoch结束提醒 — 当前 Epoch 进入最后3天时打红色大字提醒:销毁 VIP 点数(vipp),需要的话投票给 kamigotchi,附投票入口链接,并显示结束时间和剩余小时。Epoch30 起点 2026-06-04 08:00 UTC,每期14天,用
_epochAt/_epochRange推算当前期号和结束时间。剩余天数向上取整 ≤3 才打印。启动30秒后首查,此后每6小时查一次,异常静默。触发:自动:启动30s + 每6h|参数__EPOCH_REMIND_DAYS=3;__EPOCH_LENGTH_MS=14天;起点2026-06-04 08:00 UTC|来历1.2.7删余额差统计时连带误删,grok审查抓出(B1)后恢复(只留提醒,不含旧showEpochGasStats)|版本1.2.7
房间映射
- 房间名→房间号静态映射表 — 手工维护约 70 个房间名到
roomIndex的映射(0 deadzone … 90 Scenic View)。编号跳号(如 7、8、14、17)是游戏本身的缺口不是遗漏;游戏新增房间需手动补充,否则显示 'Unknown'。触发:被部署/停采/救援/日志模块按需查询|参数ROOM_NAME_TO_INDEX __getMyRoomIndex当前账户房间号 — 取当前连接钱包地址→本地 ECS 按操作地址查账户实体→读roomIndex。window.network.network.connectedAddress.value_→explorer.accounts.getByOperator(addr);无网络请求;全程 try/catch,network 未就绪/未登录等任何异常返回 null,调用方自行处理。触发:被其他模块调用__getRoomNameByIndex房间号反查名 — 遍历映射表按房间号反查房间名,主要用于日志显示。查不到返回 'Unknown',不抛异常。触发:被日志输出等模块调用
模拟点击与链上查询小工具
- 完整鼠标事件序列模拟点击 — 按真人操作节奏给元素依次派发 mouseover、mousedown、mouseup、click,让游戏按钮真正触发业务逻辑。四个事件分别在
delayMs+50、+150、+200、+300ms 触发,全部 bubbles:true,上层的事件委托监听也能收到。触发:各 UI 操作板块需要点页面按钮时调用|参数delayMs默认 0;四步相对间隔 50/150/200/300ms(调太小可能被前端忽略)|来历游戏前端用 React 合成事件,部分按钮不响应孤立的 click 事件 - 点击后补 mouseout 复位悬停态 — click 后立刻补发一个 mouseout(
relatedTarget指向document.body),让按钮 hover 样式正常复位。mouseout 设 bubbles:true、cancelable:true,和 click 在同一个定时回调里发出。触发:每次simulateClick|来历避免 UI 卡在 hover 态 - 空元素保护与
delayMs错峰 — 传入元素为空时直接返回、不抛错;需要连续点击多个元素时,可传不同的delayMs把事件序列错开。首行判断 !element 就 return;delayMs是整体时序偏移量。触发:每次simulateClick|参数delayMs|来历避免多个事件序列互相交叠 - 按图鉴编号实时查
kamiId/harvestId— 用getByIndex实时查单只 Kami 的链上实体 ID 和当前采集实体 ID,本地库缺记录或harvestId缺失、非法时作为回退来源。只开 harvest 选项,查询开销最小。触发:部署/停采流程按需调用 - ID 查询失败返回空值不中断批量 — 查
kamiId/harvestId时,接口不存在就静默返回双 null;查询抛错就打日志后返回双 null,调用方据此走跳过或重试。接口缺失时不打日志;异常时打「[explorer] index=N 取 Kami/Harvest ID 失败」。触发:_fetchKamiAndHarvestIds调用时|来历不中断批量流程 delay(ms)等待工具 — 返回一个 ms 毫秒后 resolve 的 Promise,供全脚本 await 等待使用。基于setTimeout。触发:全脚本调用
二、辅助脚本
辅助脚本是核心的配套,必须和核心同时启用:清算线显示与精确计算、杀手扫描、升级加点、自动合成、启动窗口复活、地块分析和代码健康看板。
2.1 启动、日志与调试
脚本头说明、单实例守卫、日志与版本检查、调试开关。
脚本头与说明
- 篡改猴元数据与注入时机 — 声明脚本名、版本、生效站点和注入时机。脚本只在
kamigotchi.io各子域运行,等页面空闲后才注入。@name 为「Kamigotchi辅助脚本-测试版-1.2.19」,@version v1.2.19,@match https://*.kamigotchi.io/*,@grant none(跑在页面上下文,可直接读window.network),@run-at document-idle。头部 SYNC 注释规定:升版本时 @name、@version、banner、启动 log、命令清单 banner 要一起改。触发:篡改猴加载页面时|来历版本仪式要求多处同步升号,防止各处版本号不一致|版本测试版1.2.16;版本仪式标签 1.1.20 - 功能总览与跨脚本协作约定(头注释) — 头注释列出本脚本 9 大能力,并写明与核心、精简数据库、杀手监控三个脚本之间通过哪些 window 全局接口互相调用。八项能力:LT 显示增强、自动升级与技能管理、自动合成(步长只认 DOM 读数)、启动窗口复活(与核心共享防重发记录和 revive 锁)、地块适配分析、杀手候选扫描、实时步长接口、精确清算线、自动买松果(测试版 1.2.17 加,1.2.18 起默认开)。协作约定:核心调用本脚本的
getStaminaFromDOM()和__getMinorityKamis();本脚本升级或重置前调用核心的 TX 锁接口(如hasEmergencyLock),给紧急停采让路;精简数据库脚本提供window.kami_core_db;杀手监控脚本维护window.MY_KILLER_KAMIS。本段只是声明,各项实现在后文。触发:文档声明|来历链上 API 的stamina.sync是检查点旧值,不随时间回复,所以步长只能以 DOM 为准 - 自动任务时刻表(头注释) — 写明各自动任务的时间节奏:自动升级在游戏加载完成后执行,之后每 30 分钟一次;自动合成在启动 5 分钟后首检,之后每 30 分钟一次;LT 显示每 5 分钟刷新;自动买松果买满一批后 7 天内不再买、到期后每天查一次(1.2.18)。本范围内只实现了 LT 的 5 分钟兜底定时器,升级和合成的定时实现在后文。触发:文档声明|参数升级间隔 30min;合成首检 5min、之后 30min;LT 刷新 5min
- 术语速览(新手文档)仅观测 — 给新手解释 TX、TX 双锁、清算线 LT、步长、build、标准技能、Respec、affinity、majority/minority、db-first 等术语。TX 双锁指紧急锁(紧急停采专用,最高优先级)加普通锁(升级、合成、复活互斥),用来防止多个脚本同时发 TX 导致 nonce 冲突。db-first 指先在本地
kami_core_db内存里筛选,只对少量候选发 API 查询。触发:文档 - 常用控制台命令速查表 — 列出 16 个常用控制台命令及用途(测试版 1.2.17 加
showPineBuy()、startAutoBuyPine()/stopAutoBuyPine()、buyPineNow())。这些命令大多在后文定义并挂到 window 上,本段只是速查表。compareLT()、setPredatorEquipHeadroom()、setPredatorEquipCapacity()不在速查表里。触发:文档|命令upgradekamis():手动跑一轮升级和技能管理;checkAllKamiSkills():只查不改,检查技能是否符合标准 build;getRespecPotionCount():查背包里的洗点药水;kamiAnalyze():地块适配分析(四色分类);findKillerCandidates():在自己号里找适合转杀手的 kami;scanTopPredators():全网扫描最强杀手;showTopPredators():查看当前生效档案;refreshPreciseLT():按档案重算全库清算线;showHealth():代码健康看板(每 30 分钟自检);autoCraft()、startAutoCraft()、stopAutoCraft():合成手动跑、启动、停止;window.__refreshLT():手动刷新 LT 显示
单实例守卫
- 同页双辅助脚本拦截测试线独有 — 同一页面已有一份辅助脚本(公开版或测试版)在跑时,本份不启动,控制台打一条红底警告,然后整个脚本直接 return。读取
window.__kamiHelperInstance,若存在且带 variant 字段就打警告并 return;否则写入 {variant:'测试版', at: 当前时间}。刷新后 window 被清空,旗子自然消失。警告直接走console.log,不进日志缓冲区。触发:脚本载入时立即执行|参数HELPER_VARIANT='测试版'|存储window.__kamiHelperInstance(跨脚本全局旗子)|来历测试版和公开版 @name 不同,篡改猴当成两个脚本可同时启用。两份一起跑等于双份升级、合成、复活 tx,还会抢普通锁、争同一份步长和材料,而__txNormalLock的 5 分钟超时抢占窗口挡不住这种情况|版本辅助测试版 1.2.10 - 守卫 fail-open测试线独有 — 只有确实读到旗子才拦截;读或写旗子出任何异常都照常启动。整段包在 try/catch 里,catch 中什么也不做。注释说这只是兜底,测试机上仍应在篡改猴里停用公开版辅助。触发:守卫执行出错时|来历辅助不跑只是少升级、少合成,比整个脚本挂掉强|版本辅助测试版 1.2.10
日志输出
- 统一日志函数
log()仅观测 — 全脚本统一的留痕输出:控制台打印带「[辅助脚本][时间]」前缀的日志,同时把一份明文推进与核心脚本共享的日志缓冲区,核心的saveKamiLogs()会把两个脚本的日志合并导出。控制台分两行打印:先打前缀行,再原样打内容,保留对象展开能力。存档时字符串去掉 %c,对象用JSON.stringify。注释写「+8 小时偏移」,代码实际按TZ_OFFSET_HOURS的偏移计算,默认跟随浏览器本地时区。另外log()存档只删 %c、不跳过配套的 CSS 参数,样式字符串会被当正文写进日志文件(clog 没有这个问题)。触发:脚本内所有需要留痕的输出|命令saveKamiLogs()(核心脚本提供,导出合并日志)|存储window.__kamiLogBuffer(跨脚本共享数组)|来历让核心和辅助的日志能合并导出,方便事后复盘 - 时区设置
TZ_OFFSET_HOURS— 日志时间戳的时区可配置:默认 'auto' 自动跟随浏览器本地时区,也可写死小时数,如 8、-5、5.5。值为 'auto' 时偏移取 -getTimezoneOffset()分钟,否则取 Number(值) 小时。log、clog、ctable 共用__TZ_OFFSET_MS。触发:脚本载入时计算一次|参数TZ_OFFSET_HOURS='auto'|版本v1.1.3 起 - 共享缓冲区自建兜底 — 哪个脚本先启动就由谁创建日志缓冲区;本脚本发现缓冲区不存在或不是数组时,会自己初始化成空数组。写缓冲区整体包在 try/catch 里,缓冲区不可用只影响存档,控制台照常输出。触发:每次
log()写缓冲区时|存储window.__kamiLogBuffer - 日志对象序列化容错 — 对象序列化失败时(如循环引用)退回
String(),不让日志本身抛错。try {JSON.stringify(a)} catch { String(a) }。触发:log()参数含对象时 - clog:控制台输出同时入日志仅观测 — 替代直接
console.log:控制台原样输出(%c 彩色不变),另把纯文本副本写进共享缓冲区,多行文本按行拆开逐行入库。每行加「[辅助脚本][时间]」前缀后 push。写缓冲区全程 try 包住,绝不影响主流程。触发:启动命令清单、参数设置回执、调试行等原先直接console.log的地方|存储window.__kamiLogBuffer|来历用户 0911 定案「我要都走日志,方便事后检查日志」。之前命令清单、设置回执、表格等控制台看得见,保存出的日志文件里一行没有,事后无法复盘|版本SYNC→内部版[全量输出入日志] - %c 样式参数剥离(
__plainArgs) — 生成纯文本副本时,不仅删掉首参里的 %c,还按 %c 个数跳过后面对应数量的 CSS 字符串参数,避免样式代码混进日志文件。首参统计 %c 个数得到cssLeft,后续字符串参数先抵扣cssLeft,其余字符串原样拼接,对象走JSON.stringify(失败则 String)。触发:clog 调用时|版本SYNC→内部版[全量输出入日志] - ctable:表格输出入日志仅观测 — 控制台打表格,同时在缓冲区写一行「(表格 N 行)」,再把每行 JSON 逐行入库。
console.table失败时回退console.log;传入非数组时包成单元素数组。两段各自 try 包住。触发:被checkAllKamiSkills等表格输出调用|存储window.__kamiLogBuffer|来历用户 0911 定案全量输出入日志|版本SYNC→内部版[全量输出入日志]
版本号与版本检查
- 版本号、线名、构建时间三常量收敛测试线独有 — 版本号、线名(测试版)、构建时间统一收敛到
SCRIPT_VERSION、SCRIPT_LINE、SCRIPT_BUILT三个常量,启动 log、命令 banner、版本检查的SELF_VERSION全部引用它们,不会再各改各的。SCRIPT_VERSION='1.2.16',SCRIPT_LINE='测试版'。触发:脚本载入|参数SCRIPT_VERSION='1.2.16';SCRIPT_LINE='测试版'|来历0913 账户 A 实盘日志里辅助 @version 已是 1.2.10,启动 log 却打公开版 v1.2.8(数据库、监控也各差一版),直接导致误判「beta 没装全」「监控没更新」两次。日志撒谎比没有日志更糟|版本测试版1.2.11 - 构建时间自报(
SCRIPT_BUILT)测试线独有仅观测 — 启动日志括号里显示发布时间:发布器打包时注入真实发布时间(同 @x-release-date,版本没变就沿用旧日期),本地没发布过的显示「(本地未发布)」。占位值为 '(本地未发布)',注释标注勿手改。看到这个占位值就说明这份不是从GitHub装的。触发:启动日志|参数SCRIPT_BUILT='(本地未发布)'|来历方便从日志一眼判断安装来源和发布批次|版本测试版1.2.11 - 醒目启动横幅仅观测 — 启动时打一条绿底白字、16px 加粗的横幅,内容是「✅ Kamigotchi辅助脚本-测试版 v1.2.13(构建时间)已成功启动,等待网页加载完成…」。走
log(),同时进入日志缓冲区。触发:脚本载入|来历让启动确认一眼可见|版本1.1.23 启动横幅醒目化 - 启动在线版本检查仅观测 — 启动 8 秒后拉取
GitHub上测试线的meta.js,与本机版本比较,告诉用户是否已是最新。URL 带 ?_=时间戳,fetch 用 cache:'no-store'。正则解析 @version 和 @x-release-date。cmpVer逐段按数字比较(1.2.10>1.2.9)。结果有三种:一致打「✅已是GitHub最新(发布于 X;本机安装/更新于 Y)」;本机旧打橙色「⚠️…请到篡改猴面板『实用工具→检查用户脚本更新』拉取」;本机更新打「ℹ️本地开发版」。延迟 8 秒是为了避开启动拥挤。触发:脚本载入后setTimeout8s 自动执行一次|参数延迟 8000ms;META_URL=GitHubbeta/kamigotchi-helper-beta.meta.js|来历内部版没有GitHub分发,同步到内部版时可整块跳过|版本1.1.21 版本检查 - 本机版本首次运行时间记录仅观测 — 记录本机第一次运行某版本的时刻,近似当作篡改猴安装或更新时间,显示在版本检查结果里。键名为 '
kami_ver_seen_辅助脚本_' 加版本号;没有值时写入当前时间(zh-CN 格式)。读写localStorage出异常时显示「未知」。触发:版本检查 IIFE 执行时|存储kami_ver_seen_辅助脚本_<版本号>|来历篡改猴的更新时间无法直接读,只能取首次见到该版本的时刻|版本1.1.21 版本检查 - 远端版本解析失败跳过仅观测 — meta 文件里解析不出 @version 时(网络或格式异常),打一行 ℹ️ 提示后跳过,不做比较。
remoteVer为空时 log 并 return。触发:版本检查拿到响应后|版本1.1.21 版本检查 - CSP 外联失败提示 24 小时降噪仅观测 — fetch 失败(绝大多数是游戏页 CSP 拒绝外联
GitHub)时,24 小时内只提示一次「游戏页 CSP 限制外联,属正常,不影响篡改猴自动更新」,并附手动检查路径。读localStorage里的上次提示时间,距今 ≥86400000ms 才打并刷新时间戳,否则静默。localStorage本身出异常时退化为每次都打。代码 8000 那行的注释写「raw 带 CORS *,页面上下文可直接 fetch」,但 catch 里的注释说明游戏页 CSP 会永久拦截,两处注释互相矛盾;代码按失败降噪处理。触发:版本检查 fetch 抛异常时|参数节流窗口 24h|存储kami_vercheck_csp_note_辅助脚本|来历游戏 SPA 运行时注入 CSP meta 的 connect-src 白名单,raw 外联在游戏页永久失败,旧版每次页面加载都打一行,刷屏|版本1.1.24 版本检查降噪
调试开关
- [调试] 前缀日志统一拦截 — 给
console.log打补丁:首参是以「[调试]」开头的字符串时,开关关(默认)直接吞掉,开关开时放行。调试语句可以常驻代码,平时不刷屏。用RAW_LOG保存原生console.log;其余输出一律透传。补丁作用于整个页面的console.log调用。前缀判断包在 try/catch 里,异常时照常透传。触发:脚本载入时打补丁,此后每次console.log都经过它|参数拦截前缀 '[调试]'|来历高频调试输出常驻代码,但正常使用不能刷屏 - 调试开关优先级与持久化 —
window.__AUX_DEBUG__是布尔值时以它为准;否则看localStorage的aux_debug是否为 '1',可跨刷新保持。开关只在载入时确定一次,运行中改localStorage要刷新才生效。代码读localStorage这一步没包 try,浏览器禁站点数据时理论上会抛错并中断后续脚本。触发:脚本载入时|命令开启:localStorage.setItem('aux_debug','1')后刷新;关闭:localStorage.removeItem('aux_debug')后刷新;或载入前设window.__AUX_DEBUG__=true|存储aux_debug - 调试模式开启一次性提示仅观测 — 调试开着时,第一条被放行的调试日志前额外打一句「测试线独有 调试模式已开启(仅此一次提示)」。用
shownOnce标志保证只提示一次。触发:首条 [调试] 日志放行时 - 调试状态只读查询 — 在 window 上定义只读属性
__AUX_DEBUG_ACTIVE__,随时查看当前是否处于调试模式。Object.defineProperty,只设 getter,返回 !!DEBUG。触发:控制台查询|命令window.__AUX_DEBUG_ACTIVE__
启动命令清单
- 启动命令清单 banner仅观测 — 脚本启动 3 秒后在控制台打一份带样式的可用命令清单,标题含版本线、版本号和发布时间,并附带各自动任务的时间说明和使用提示。标题「Kamigotchi辅助脚本-版本线 v版本(发布时间)可用命令」。命令包括:
checkAllKamiSkills()、upgradekamis()、getRespecPotionCount()、findKillerCandidates()(可自定义阈值如 {vio:25,harm:17,pow:10};db-first;需配合精简数据库脚本重建 db,老 db 自动降级为全 API 扫描)、scanTopPredators()(6 小时自动重扫)、showTopPredators()/refreshPreciseLT()、compareLT()(传 true 看全量)、showHealth()(每 30 分钟自检)、autoCraft()(步长≥80 合成)、getStaminaFromDOM()、showPineBuy()/startAutoBuyPine()/stopAutoBuyPine()/buyPineNow()(测试版 1.2.17 加,含买入规则与每 7 天一次一行)、window.kami_core_db。提示部分:自动合成启动 5 分钟后首检、之后每 30 分钟;UI 更新每 5 分钟;日志已接入saveKamiLogs();grep「技能重置摘要」查 Respec;findKillerCandidates启动自动跑一次;升级和重置会联动检查window.MY_KILLER_KAMIS;LT 显示改用 CSS 伪元素,调试用window.__refreshLT()。只打一次。触发:自动:脚本加载后 3 秒|参数setTimeout3000ms|来历等各模块的启动日志先刷完,让命令清单出现在控制台靠后的位置,更显眼。banner 是写死的字符串,功能变了要人工同步修改|版本1.1.20 看板白名单三批
2.2 清算线显示与计算
在每张卡片上显示清算线(LT)、危险的标红;按合约官方公式 + 最强杀手档案 + 装备余量算精确清算线。
数据库恢复
- 复用已加载的内存数据库 — 精简数据库脚本先加载时,直接使用它放在内存里的
window.kami_core_db,并打印条数和查看提示。Array.isArray(window.kami_core_db)为真时打「📦 已检测到已加载的精简数据库,共 N 条」和「🔍 控制台查看」。数据库每条 17 个字段,含清算线 LT。没有数据库时,LT 显示、重置门槛、地块分析、杀手候选扫描都会跳过或降级;日常新增的 kami 由核心脚本的syncKamiDb()增量补全。触发:脚本载入时|命令window.kami_core_db(控制台直接查看) - 从
localStorage恢复数据库 — 内存里没有数据库时,从localStorage的持久化副本反序列化到内存。JSON.parse(localStorage.kami_core_db,缺省 '[]'),经Array.isArray校验:是数组才采用,否则置空数组。触发:脚本载入且内存无数据库时|存储kami_core_db(读)|来历数据库建好后持久化,刷新页面无需重跑 - 数据库损坏兜底为空数组 — JSON 损坏等解析异常时打「❌ 读取
kami_core_db失败」并置空数组,保证后续 find 或遍历不抛错。catch 分支中window.kami_core_db=[]。触发:解析localStorage失败时
卡片 LT 显示与危险标红
- LT 标签与危险标红的 CSS 渲染通道 — 向页面注入一段全局 CSS:带 data-lt-tag 属性的元素在末尾用伪元素显示灰色小字 LT 值;带 data-kami-danger="1" 的元素文字标红。[data-lt-tag]::after 规则:content 为 " "
attr(data-lt-tag),颜色 #7F8C8D !important,margin-left 6px,0.8em,不加粗。[data-kami-danger="1"] 规则:颜色 #E74C3C !important。不往卡片里插 DOM 节点,只写 data 属性。触发:脚本载入时立即执行一次(IIFE)|参数LT 标签颜色 #7F8C8D;危险色 #E74C3C;间距 6px;字号 0.8em|来历游戏前端是 React,重渲染会整批删掉脚本插入的子节点,而 data 属性通常不会被清掉。改 attribute 也不会触发childList型MutationObserver,不会和本脚本的 DOM 监听形成「写入→触发→再写入」的反馈循环 - 样式防重复注入 — 用固定 id
__aux_lt_style__判断是否已注入,已存在就直接返回;document.head还不存在时挂到根元素上。document.getElementById('__aux_lt_style__')存在则 return。触发:注入时|来历脚本被多次执行也安全 imgNumber主键提取(getimgNumber) — 从卡片或 img 节点中提取 kami 形象图编号(src 里的 /kami/<数字>.gif),作为和精简数据库对齐记录的主键。节点本身是 IMG 就直接用,否则找内部 src 含 /kami/ 的图;匹配不到返回 null。实现必须和核心脚本保持一致。触发:combinedUIUpdate处理每张卡片时- 页面就绪等待(
waitForPageReady) — 用MutationObserver监听 body 的 DOM 变化,眼睛图标(eye-half)和 party 卡片列表同时出现时视为就绪,再启动 LT 显示。先同步检查一次,已就绪就直接 resolve;否则挂 observer(childList+subtree),满足条件就 disconnect 并 resolve。卡片选择器为 div#party>div>div:nth-of-type(3)>div:nth-of-type(2)>div:nth-of-type(2)>div,依赖站点当前 DOM 层级。触发:脚本载入后|来历替代每秒轮询,省 CPU - 就绪等待 60 秒超时强制放行 — 60 秒内等不到就绪条件也强制继续,避免异常页面把流程卡死。超时后 disconnect 并 resolve,照常执行后续。此时如果 #party 还不存在,就不会挂 #party 监听,只剩 5 分钟兜底定时器生效。触发:就绪等待超过 60s|参数超时 60000ms
- 旧版 DOM 插入方案残留清理 — 启动时删除历史方案插入的
span.__lt_percent__节点和 data-lt-inserted 标记,防止视觉重复或 dataset 干扰。有残留时打「🧹 清理旧版残留: X 个 LT span + Y 个dataset.ltInserted」。querySelectorAll后逐个 remove 或 delete dataset。触发:页面就绪后执行一次|来历旧方案往卡片插 span,已被 data 属性加 CSS 方案取代 - 卡片 HP 后显示清算线 LT — 在 party 面板每张 kami 卡片的 HP 文本后,用灰色小字显示该 kami 的清算线,例如「31.25%」。用
imgNumber在window.kami_core_db里查记录,取 LT 字段,格式化成两位小数加 % 写入hpDiv.dataset.ltTag,由 CSS ::after 渲染。状态图标(src 含 /assets/kami_)的下一个兄弟节点就是 HP 文本。触发:就绪后执行一次、#party 变化防抖、5 分钟兜底、手动__refreshLT()|参数小数位toFixed(2)|命令window.__refreshLT()|来历不用appendChildspan,是因为 React 重渲染会删掉脚本插入的节点 - LT 标签值相同不重写 — 写 dataset 前先比对旧值,相同就不写,把属性突变减到最少。只在
hpDiv.dataset.ltTag!==newTag时写入;危险属性同样先比对再写或删。触发:每次combinedUIUpdate|来历大量卡片同屏时不引发突变风暴,也不和 #party 监听形成反馈循环 - 危险 Kami HP 标红 — 采集中且 HP% 低于「清算线 + 2 个百分点」的 kami,HP 文本整体标红;不满足时移除标红。状态图标 src 含
kami_harvesting才算采集中,休息中的不参与(不会被清算)。满足条件写 data-kami-danger="1",否则 delete。HP% 或 LT 为NaN时不判危险。触发:每次combinedUIUpdate|参数安全余量 +2 百分点(killPercent+ 2)|来历+2 是安全余量,宁可早提示。不用style.color是因为 inline style 会被 React 重渲染重置 - HP 百分比解析回退 — 从 HP 文本解析百分比:优先匹配带括号的「(xx%)」,找不到再回退匹配裸「xx%」。正则只认整数,/((\d+)%)/ 失败再用 /(\d+)%/,都失败得
NaN。触发:处理每张卡片时 - 单卡容错跳过 — 每张卡片单独 try/catch;
imgNumber、HP 节点或数据库记录任一缺失就跳过该卡,不报错也不影响其他卡。缺imgNumber或hpDiv时 return;查不到记录时 LT 为NaN,不写标签。记录缺失时不会清除该节点上已有的旧ltTag。触发:处理每张卡片时 - 清除历史 inline 颜色残留 — 发现 HP 节点上有旧方案写的
style.color,顺手清空,避免和 CSS 规则叠加冲突。if (hpDiv.style.color)hpDiv.style.color=''。触发:处理每张卡片时|来历旧版用 inline style 标红 - LT 显示健康心跳仅观测 — 每次执行 LT 刷新都记下心跳时间,供代码健康看板检查辅助脚本的定时器层是否还活着。写
window.__kamiHealthBeats['LT显示']=Date.now()。据后文健康看板,页面存活超过 20 分钟仍无此埋点就报 🔴「辅助脚本自身定时器层疑似半死」。触发:每次combinedUIUpdate|命令showHealth()|存储window.__kamiHealthBeats(跨脚本全局) - #party 区域变化自动重算(300ms 防抖) — 监听 #party 区域的 DOM 变化(如点眼睛图标切换卡片展开),300ms 防抖后重跑 LT 显示和标红。
MutationObserver(childList+subtree)配合clearTimeout/setTimeout,挂上后打「仅观测 已启动 #party 区域监测」。触发:#party 子树变化|参数防抖 300ms|来历卡片被 React 重建后要重新写入属性 - LT 5 分钟兜底定时重跑 — 每 5 分钟无条件重跑一次 LT 显示,覆盖
MutationObserver漏触发的极端情况。setInterval内 try/catch,异常时打「⚠️ [LT兜底] 异常」;启动时打「⏰ 已启动 LT 5 分钟兜底定时器」。触发:就绪后每 5 分钟|参数间隔 5*60*1000ms - 手动刷新 LT 命令 — 在控制台立即重跑一次 LT 显示和危险标红。
window.__refreshLT=combinedUIUpdate。触发:控制台手动|命令window.__refreshLT() - 逐卡调试输出仅观测 — 调试模式下逐卡打印检测到的卡片数、
imgNumber、HP 原文和解析值、未命中数据库、找不到 HP 节点、单卡异常。全部以「[调试]」开头,走console.log,由调试总开关拦截,默认不显示,也不进日志缓冲区。触发:每次combinedUIUpdate(需开调试开关)|命令localStorage.setItem('aux_debug','1')后刷新
精确清算线公式
- 旧清算线公式(满配假想杀手)已停用 — 本地复刻的旧版清算线公式,按假想杀手 Violence=41 计算 LT 和 LTHP。v1.1.1 起不再参与任何决策,只保留作参考和对照。body 为 normal 时 E=0.2,其他 0.5;
deltaR=max(0,0.5-ratio);deltaS=max(0,0.4-shift);phi=Φ(ln(V/harmony));frac=clamp01(phi×0.4×(1+E+deltaR)+deltaS)。harmony 或 maxhp 非正时返回 null。据后文仍有两处调用:compareLT的「旧公式参考」列;健康看板的保守包络检查(档案里没有 ATS>0.4 的条目时,精确值突破旧公式上界就报 🟡)。触发:仅被compareLT和健康看板作对照调用|参数V_CONST=41|命令compareLT()(后文)|来历相当于把满配假想杀手(V41、满攻击技能、永远克制)焊死在公式里,普遍偏保守约 30 个百分点|版本v1.1.1 起停用 - 官方精确清算线公式(
computePreciseLT) — 按游戏合约官方公式,计算单个攻方杀手对单只守方 kami 的清算线百分比(0~100)。animosity=Φ(ln(攻vio/守harm))×0.4;efficacy=1+亲和+攻ATR−守DTR;shift=攻ATS−守DTS;结果=clamp01(animosity×efficacy+shift)×100,负值截断为 0 表示不可杀。vio 或 harm 非正时返回 0。触发:被__worstLTOver对每个档案调用|参数基准常数 0.4|来历用真实存在的最强杀手参数代替焊死的假想杀手:清算线更准,停采线更低,采集更久也更省 gas - 亲和加成规则(
__ltAffinity) — 按合约LibAffinity规则算攻方手型对守方体型的亲和加成。双方都是 NORMAL 为 +0.2;只有一方是 NORMAL 为 0;攻方手克守方体为 +0.5(EERIE 克 SCRAP、SCRAP 克 INSECT、INSECT 克 EERIE);被克为 −0.5;其余为 0。入参先转大写。触发:computePreciseLT内调用|参数克制 ±0.5;双 NORMAL +0.2|来历合约源码确认 special 加成只在双方都是 NORMAL 时生效 - 标准正态 CDF 共享工具(
__ltCdf) — 用 Abramowitz–Stegun erf 近似计算标准正态分布 CDF,供精确公式使用。系数与旧公式相同,误差约 1.5e-7,负半轴用对称性处理。触发:computePreciseLT内调用 - 对档案列表取最坏清算线(
__worstLTOver) — 对每个威胁档案逐个算清算线,取最大值作为该 kami 的清算线,同时返回对应的绝对 HP 值。harmony 或 maxhp 非正时返回 {LT:null, LTHP:null};守方参数 dtr、dts 缺省为 0;返回 LT 两位小数,LTHP=LT/100×maxhp。据后文,compareLT也用它分别算默认兜底线和实战线。触发:computePreciseLTForRecord、compareLT调用|命令compareLT()(后文) - 单条记录精确清算线入口(
computePreciseLTForRecord) — 按一条数据库记录的守方参数(harmony、body、ratio、shift、maxhp),对当前全部生效档案取最坏清算线。返回形状 {LT, LTHP} 与旧computeLiquidationLine一致,原调用点可直接替换。据后文:升级或加点后本地实时重算写库、refreshPreciseLT全库重算、健康看板一致性检查都调用它,并暴露为window.computePreciseLTForRecord供其他脚本使用。触发:升级或加点后更新数据库、refreshPreciseLT、健康看板|命令refreshPreciseLT()(后文)|来历升级后清算线立刻按新属性重算,不用等下一轮全量扫描
威胁档案与活跃度选档
- 威胁档案存储键与 6 小时有效期 — 全网最强杀手扫描结果存在独立的
localStorage键里,不覆盖其他数据;超过 6 小时视为过期,由后文启动自扫逻辑触发重扫。TTL 常量在本段定义;据后文,无档案、档案不可用或超过 TTL 时启动自扫。注释写核心脚本约每 45 分钟例行刷新会触发检查。触发:后文启动自扫判断时读取|参数TOP_PREDATORS_TTL_MS=6h|命令scanTopPredators()|存储kami_top_predators|来历0717 用户定案从 7 天改为 6 小时:#12649 案证明杀手属性可以在扫描窗口内跳变(疑似道具瞬时加技能);全网扫描是纯本地 ECS 读取,20~60 秒、零 gas,有__topScanRunning防重入,6 小时一扫成本可忽略|版本1.2.3 天敌档案地板 - 内置默认威胁档案(四桶) — 内置 EERIE、SCRAP、INSECT、NORMAL 四种手型的最强杀手参数,没有扫描数据时兜底,同时作为永久地板。EERIE vio38/ats0.32/atr0.50;SCRAP vio41/ats0.31/atr0.50;INSECT vio38/ats0.32/atr0.50;NORMAL vio34/ats0.40/atr0.50。EERIE 这项,上方 0717 注释写「提到 0.31」,代码实际已按 0908 全网实测(vio38/ats0.31 加 0.01 垫)改为 0.32。精简数据库脚本内置同值副本
DB_TOP_PREDATORS,改这里必须两处同步。触发:getEffectivePredators取档案时|参数四桶 vio/ats/atr 如上|命令showTopPredators()|来历0717 某账户 15 只死亡归因:杀手 #12649(NORMAL 手)ATS 从 0.30 练到 0.38,旧档案低估 8pp,15 只全死在停采线之上的「隐形死亡带」,所以 NORMAL 按实测加 0.02 垫到 0.40。0908 实测 INSECT 原记 36/0.26,两维都偏低,已抬齐|版本1.2.3 天敌档案地板;0908 实测更新 - 默认档案永久地板(扫描与默认取并集) — 有扫描结果时,不再让扫描结果替代默认档案,而是两者取并集后取最坏值,默认档案始终兜底。
result.list= 扫描活跃列表 concatTOP_PREDATORS_DEFAULT,source 标为「全网扫描+默认地板」,同时带上 at、demoted、benchHarm。__worstLTOver对整个列表取最大,地板自动生效,扫描信息也不丢。触发:getEffectivePredators读到非空扫描档案时|存储kami_top_predators(读)|来历0717 事故根因:周扫描窗口内杀手练级(#12649 ATS 0.30→0.38),旧扫描结果优先级高于默认档案,把更新后的默认值盖掉了|版本1.2.3 天敌档案地板 - 活跃度感知选档(剔除沉寂杀手) — 每个手型桶里,剔除主人超过 10 天没有链上动作的候选杀手,只让活跃的参与取最坏值。用
window.network.explorer.accounts.getByID(ownerId).time.last 实时查主人最后动作时间(本地 ECS,零 gas),算出沉寂天数。被剔除的记入 demoted(hand、index、owner、idleDays)供展示。精简数据库脚本的公式副本不含这套选档逻辑,建库初值保持静态保守档案,属有意差异。触发:getEffectivePredators读到扫描档案时|参数PREDATOR_INACTIVE_DAYS=10|命令showTopPredators()|存储kami_top_predators(读)|来历账面最强不等于真威胁:0707 实测 SCRAP 桶账面最强 #4711 的主人已沉寂 115 天,实战威胁极低 - 整桶沉寂保守回退 — 某个桶里所有候选的主人都沉寂时,不剔除任何人,整桶照常参与,清算线宁高勿低。只有存在活跃候选时才真正降级沉寂者并记入 demoted;否则 used=全部候选。触发:选档遍历每个桶时|来历防止整桶被剔空,导致清算线被系统性算低
- 活跃度查不到按活跃处理 — 没有
ownerId、API 还没就绪、查不到记录或查询异常时,沉寂天数按 0 算,即当作活跃杀手保留。__ownerInactiveDays在 try/catch 中,time.last不大于 0 时返回 0。触发:选档查主人活跃度时|来历保守:宁可多算一个威胁,也不漏掉 - 活跃度时间格式化工具仅观测 — 把时间戳格式化成「
x.x天前活跃」,无效时显示「活跃度未知」,供后文档案展示使用。__fmtAgoDays(ts)触发:被后文展示函数调用 - 选档结果 10 分钟记忆缓存 — 生效档案的选档结果缓存 10 分钟,避免每只 kami 算清算线时都反复解析
localStorage、查活跃度。__predSelCache={at,result},10 分钟内直接返回。两个装备设置命令会置空缓存;注释写扫描后立即失效重选(实现在后文)。回落内置默认的结果也会被缓存 10 分钟。触发:每次getEffectivePredators|参数缓存 10*60000ms - 档案损坏或为空回落内置默认 — 扫描档案不存在、JSON 损坏、各桶都没有 vio>0 的有效候选时,改用内置默认档案(来源标「内置默认」)。候选先过滤 e&&
e.vio>0;解析异常被 catch 吞掉,result 为 null 时回落 {list:TOP_PREDATORS_DEFAULT, source:'内置默认', at:0, demoted:[]}。触发:getEffectivePredators读档失败时|命令scanTopPredators()|存储kami_top_predators(读) - 回落内置默认档案红字告警仅观测 — 一旦回落到内置默认档案,打一条红色加粗日志,提示清算线可能对练级或带装备的新杀手偏低,建议手动
scanTopPredators()。用window.__predFallbackWarned保证每页只告警一次,try 包住。触发:getEffectivePredators回落内置默认时(每页一次)|命令scanTopPredators()|存储window.__predFallbackWarned|来历旧版只在健康看板用小字提示,漏看了数周|版本1.2.7
杀手装备余量
- 杀手装备 shift 余量(默认 +0.10) — 计算清算线时,默认假设杀手随时可以穿上 1 件 +10% attack threshold shift 的装备,给攻方 ATS 统一加余量;ratio(ATR)余量默认 0。读
localStorage值:非法或未设时用默认值;负数截为 0;读异常时用默认值。清算线整体上移约 10pp,停采更早,gas 略增。余量取值推演:假设读数已含装备则需 0,假设不含则需 0.10,取 0.10 覆盖两种最坏情况,早前的 0.20 属过度保守。触发:每次__worstLTOver计算|参数PREDATOR_EQUIP_HEADROOM_ATS_DEFAULT=0.10;PREDATOR_EQUIP_HEADROOM_ATR_DEFAULT=0|命令setPredatorEquipHeadroom(0,0)全关|存储kami_predator_equip_headroom、kami_predator_equip_headroom_atr|来历0826 用户实测:#12649 和 #11224 各装 1 枚 Ancient Tape(+10% shift,[til unequipped])。装备可随时穿脱,扫描只是时点快照,杀手可以平时裸装、动手前穿满。截图里的 Effects 是效果汇总不是槽位,所以是 1 枚不是 2 枚。ATR 实测 0.50 已是满值,装备抬不上去|版本1.2.7 装备余量 - 设置装备余量命令 — 控制台调整杀手装备的 shift/ratio 余量,并清空选档缓存。ats 非法或为负时打印用法和当前值;atr 可省略,只在合法时写入。设置后提示执行
refreshPreciseLT()立即重算全库。触发:控制台手动|命令setPredatorEquipHeadroom(shift, ratio),例setPredatorEquipHeadroom(0.10, 0.10);setPredatorEquipHeadroom(0,0)全关;无参调用查看用法和当前值|存储kami_predator_equip_headroom、kami_predator_equip_headroom_atr(写)|版本1.2.7 装备余量 - 假定杀手装备容量(默认 1 格) — 假定每只杀手最多装几件装备(默认 1),用于计算剩余空格还能加多少余量。读
localStorage:值须有限且 ≥0,向下取整;否则或读异常时用默认 1。不确定时宁可设大(只多停一档,不漏防)。触发:每次__worstLTOver计算|参数PREDATOR_EQUIP_CAPACITY_DEFAULT=1|存储kami_predator_equip_capacity|来历合约getEquipmentCapacity=max(1,1+EQUIP_CAPACITY_SHIFT),默认就是 1;0826 截图里 #12649 底部显示「📦 1/1」实证|版本1.2.8 - 设置假定装备容量命令 — 控制台调整假定的杀手装备容量,并清空选档缓存。n 非法或为负时打印用法和当前值;合法时向下取整后写入,并提示
refreshPreciseLT()立即重算。触发:控制台手动|命令setPredatorEquipCapacity(n),例setPredatorEquipCapacity(2);无参查看当前值|存储kami_predator_equip_capacity(写)|来历杀手可能通过道具抬高容量|版本1.2.8 - 装备加成精确计入(新档案路径) — 档案里带装备字段时,攻方有效 ATS/ATR 等于读数加该杀手实际装备加成,再加剩余空格按余量预留。
p0.eqN是数字时:空格数=max(0,容量−eqN);addAts=eqAts+空格×shift 余量;addAtr=eqAtr+空格×ratio 余量;两者都为 0 时沿用原档案。扫描档案的eqN、eqAts、eqAtr、eqItems由getEffectivePredators透传。触发:__worstLTOver遍历档案时|来历读数是否已含装备没有做过受控实验确认(源码指向含,但未证),保守按不含处理:宁可早停一档多花点 gas,也不漏防(漏防会丢一只 kami、复活丝带和数百 musu)|版本1.2.8 装备加成精确计入 - 旧档案和默认档案的笼统余量兜底 — 档案里没有
eqN字段时(旧档案、装备读取失败、内置默认),直接给 ATS/ATR 加上笼统余量,不留缺口。p = {...p0, ats: ats+shift 余量, atr: atr+ratio 余量}。内置默认档案不带eqN,所以默认设置下 NORMAL 默认档实际按 ats 0.50 参与计算,其他桶同样加 0.10。触发:__worstLTOver遍历到无装备字段的档案时|版本1.2.8 装备加成精确计入
2.3 杀手扫描
每 6 小时全网扫描最强杀手,结果写进档案供清算线计算;另有在自己账户里挑"天生适合做杀手"的候选扫描。
全网最强杀手扫描
- 全网最强杀手扫描 — 扫描全网全部 kami(含 RESTING/DEAD),按 hand 分四桶,每桶取帕累托前沿存进
localStorage,供精确清算线计算使用;并给每只入围者反查主人。读本地 ECS(window.network.explorer),零 gas,耗时 20~60 秒;每次读取都 await 让出主线程,不阻塞其他模块。触发:启动调度判定 + 命令|命令scanTopPredators()|存储kami_top_predators|来历杀手随时可以部署或复活,所以 RESTING/DEAD 也要扫 - 防重复扫描互斥 — 扫描进行中再次调用时,打印「⚠️ [杀手扫描] 已在进行中」并返回。
__topScanRunning标志,在 finally 里释放。触发:每次调用scanTopPredators - 游戏 API 就绪守卫 —
kamis.all、entities.get、accounts.getByID缺任意一个,就打印「游戏 API 未就绪」并返回。触发:扫描开头|命令scanTopPredators() - 威胁分基准猎物取库内 harmony 中位数 — 用作「标准猎物」的 harmony,取你数据库里全部 kami harmony 的中位数(四舍五入)。只统计 >0 的值;样本 <10 或读取异常时退回 24。这个值只影响威胁分的展示排序,清算线计算和入档都不经过威胁分。触发:每次扫描|参数
BENCH_HARM默认 24;最少样本 10|来历固定 24 偏离多数账户的实际水平,用中位数让「最强」排序贴近你真实的鱼群 - 装备加成精确计入档案 — 扫描前一次性建立「
kamiID→ 已装备物品」映射,读每件装备的 EQUIP 效果,得到该杀手的装备加成实值eqAts/eqAtr。连同件数eqN、前 3 件装备名eqItems一起写进档案。遍历OwnsEquipID组件,物品索引组件兼容ItemIndex/IndexItem两个名字;物品按itemIndex缓存。效果字段优先effects.equip,缺失再试 equip/bonuses。类型含ATK_THRESHOLD_SHIFT的累加到 ATS、含 RATIO 的累加到 ATR;|value|>1 时按精度 3 除以 1000。触发:每次扫描|存储kami_top_predators|来历用户 0908 定案:装备会抬高杀手属性,必须真正计入杀手属性和清算线,不能拍一个笼统余量|版本1.2.8 - 装备映射观测日志仅观测 — 映射建成后打印「全网已装备 kami N 只;单只装备 ATS 加成最大 +X」。触发:每次扫描|版本1.2.8
- 装备组件不可用时按余量兜底 —
OwnsEquipID组件不可用或建映射抛错时,只打一条 ⚠️,本轮装备加成改用余量兜底,不影响主流程。__eqReadOK保持 false,映射为空,每只eqN/eqAts/eqAtr记 0。触发:组件缺失或异常时|版本1.2.8 - 杀手苗头初筛 — 逐只读 stats/traits/bonus,只保留
violence.total≥20、或带攻击阈值技能(ATS>0 或 ATR>0)的 kami。hand 不在 EERIE/SCRAP/INSECT/NORMAL 四桶里的直接丢弃;记录里写入 index、名字、状态、vio、ATS/ATR(保留 3 位小数)和装备字段。触发:每次扫描|参数vio≥20 - ATS>0.4 哨兵仅观测 — ATS 读数超过 0.4 的 kami 单独进哨兵名单。汇总里用 🚨 报前 5 只编号(超过 5 只加省略号),要求人工复核档案;没有就打「✅ 哨兵:全网无 ATS>0.4 的 kami」。哨兵名单同时存进档案的 sentinel 字段。触发:每次扫描|参数阈值 ATS>0.4|存储
kami_top_predators|来历超出常见技能上限,说明威胁模型需要人工复核 - 单只读取失败跳过 + 每 2000 只进度 — 单只读取异常计入 skipped,继续下一只;每扫 2000 只打印一次进度。触发:每次扫描|参数进度间隔 2000 只
- 威胁分计算(对标准猎物的清算线) — 每只候选用完整官方公式
computePreciseLT,对「标准猎物」算清算线,记为 lt24,桶内按它降序。标准猎物:harm=BENCH_HARM,body 取该 hand 的天然被克体(EERIE→SCRAP、SCRAP→INSECT、INSECT→EERIE、NORMAL→NORMAL),dtr/dts=0(白板防御)。触发:每次扫描|参数PREY_BODY映射|来历vio 只是其中一维,「最强」要按含 ATS/ATR 偏移的完整公式衡量 - 帕累托前沿入档 — 每桶只剔除「被碾压者」:vio/ATS/ATR 三维都不高于某只、且至少一维严格更低。剩下的全部保留存档。先按威胁分降序遍历,保证支配者先进前沿;最终「对每只 kami 取最坏」在前沿集合上逐个计算。触发:每次扫描|存储
kami_top_predators|来历公式对三维都单调不减,被碾压者对任何猎物都算不出更高的线,剔除不会漏掉真正最坏的。前沿成员各自可能是某类猎物的最坏(高 harmony 怕高 ATS,低 harmony 怕高 vio) - 前沿存储上限 40 只 — 某桶帕累托前沿超过 40 只时,只保留威胁分最高的 40 只,并打日志说明被截断。触发:前沿异常庞大时|参数上限 40(正常前沿约十几只)|来历存储上限保护
- 入围者反查主人 — 只给入围者反查主人:
entities.get→OwnsKamiID→accounts.getByID,记录主人名 owner、账户 idownerId、最后链上动作时间ownerLast。单只反查失败时这三个字段置空或 0;反查后删掉 entity 字段给存储瘦身。触发:每次扫描|存储kami_top_predators|来历与杀手监控脚本同一条链路,方便决定是否加进监控名单;ownerId用于选档时实查活跃度 - 空档案不落盘 — 本次扫描四桶里没有任何 vio>0 的条目时,视为扫描没成功:绝不用空结果覆盖已有档案,并红字大字告警「零有效结果,清算线暂用内置默认」。判据是四桶里至少有一条
e.vio>0,满足才setItem。触发:每次扫描写档案前|存储kami_top_predators|来历否则会把好档案冲掉,而且新时间戳会骗过「超 6 小时才重扫」的判据|版本1.2.7 - 零结果时数据形状诊断仅观测 — 零结果时拿
kami_core_db第一只做探针,打印顶层 keys、stats.violence、bonuses.attack.threshold三行,并提示「发给维护者」。探针本身失败也打日志;数据库为空时不探测。触发:扫描零结果时|来历游戏 patch 可能改客户端数据形状(0825 发生过合约地址换代),字段路径读空就会被全部过滤成空桶,一眼能看出改了哪里|版本1.2.7 - 档案写入失败只告警 —
localStorage写档案抛错时只打 ⚠️,不中断后续汇总和重算。日志说「结果本次会话仍生效」,但生效档案由getEffectivePredators从localStorage读取,写失败时实际仍沿用旧档案或内置默认。触发:写档案异常时|存储kami_top_predators - 扫描完成汇总(单条输出)仅观测 — 汇总成一条输出:耗时、扫描只数、失败跳过数、存储键说明;每桶最强的编号、威胁分、vio/ATS/ATR、主人和几天前活跃;再附一行「最强」口径说明。没有候选的桶打「(无候选)」;活跃时间格式为「
X.X天前活跃 / 活跃度未知」。触发:扫描完成|来历合成单条输出,避免每行都带来源链接 - 与杀手监控名单比对仅观测 — 每桶最强者与杀手监控脚本的名单比对:在名单里打 ✅,不在打「⚠️ 未在监控名单,建议加入」;监控脚本没加载时提示「无法比对,可考虑加入」。读跨脚本全局
window.__killerWatchList(外部监控名单)和window.__killerSelfOwned(自家杀手),合并成 Set 比对。触发:扫描完成 - 活跃度选档提示仅观测 — 某桶账面最强的主人沉寂超过 10 天时,提示清算线改按活跃的次强者计算;整桶主人都沉寂则提示「保守回退按账面档案计算」。沉寂天数 = 现在减去主人
time.last;查不到(无 id、API 未就绪、无记录)按 0 天即活跃处理,属于保守方向。触发:扫描完成|参数PREDATOR_INACTIVE_DAYS=10 - 新档案立即生效:清缓存 + 全库重算 — 扫描完成后清空选档缓存,让新档案跳过 10 分钟缓存立刻生效,接着调用
refreshPreciseLT()重算全库清算线。__predSelCache=null。触发:扫描完成|参数选档缓存 10 分钟|存储kami_core_db - 扫描异常捕获 — 扫描过程抛错时打印「❌ [杀手扫描] 失败(可手动
scanTopPredators()重试)」,finally 里释放互斥锁。触发:扫描异常时|命令scanTopPredators() - 启动调度:档案过期才扫,否则只重算 — API 就绪后延迟 90 秒读本地档案。没有档案、档案没有有效条目、或超过 6 小时,就自动全网扫描(红字说明原因);否则只做一次
refreshPreciseLT(),保证数据库与档案一致。每 5 秒轮询kamis.all+accounts.getByID,最多等 5 分钟;档案 JSON 解析失败按无档案处理。标题写「每6小时自动」,代码实际只在启动时判断一次档案年龄,会话内没有 6 小时定时重扫,靠页面刷新重新走启动判定。触发:启动自动|参数TOP_PREDATORS_TTL_MS=6 小时;错峰 90 秒;POLL_MS=5000;MAX_WAIT=5 分钟|存储kami_top_predators|来历90 秒错峰是为了避开启动期的升级巡检和杀手候选扫描 - 空档案强制重扫 — 本地档案存在、但四桶没有任何 vio>0 条目时判为不可用:红字大字告警「清算线正在用内置默认,可能系统性偏低」,然后立即强制重扫。用
__usable判据补在「存在/新鲜」判据之上。触发:启动调度时|存储kami_top_predators|来历0826 血案:旧判据只看档案存不存在、新不新,不看里面有没有货。空桶档案「存在且新鲜」导致 6 小时内不重扫,全程回落内置默认,清算线系统性偏低,#12649 带装备连杀|版本1.2.7 - 每 60 分钟轻量重算清算线 — 启动判定完成后,每小时调用一次
refreshPreciseLT(),异常吞掉。只有 API 在 5 分钟内就绪时才设这个定时器;等待超时则本页不设。触发:自动每 60 分钟|参数间隔 60 分钟|存储kami_core_db|来历覆盖核心syncKamiDb自愈新增记录时写入的旧公式初值 - 启动调度等待超时提示仅观测 — 5 分钟内游戏 API 未就绪时,打印「⚠️ 等待游戏 API 超时,可手动
scanTopPredators()」。触发:启动等待超时|参数MAX_WAIT=5 分钟|命令scanTopPredators() - 控制台命令:
scanTopPredators立即重扫 — 随时手动触发一次全网最强杀手扫描,扫完立即更新档案并重算清算线。挂在window.scanTopPredators;扫描进行中重复调用会被互斥挡回。触发:命令|命令scanTopPredators()|存储kami_top_predators
档案查看与清算线重算
refreshPreciseLT全库精确清算线重算 — 用当前生效档案给kami_core_db每条记录重算 LT/LTHP 并写回localStorage。日志打印档案来源、总条数、更新条数、档案扫描时间。数据库为空时打 ⚠️ 跳过并返回 0;算出 LT 为 null 的记录跳过,不覆盖;只在 LT 变化时改写并计数;写回localStorage失败静默忽略。触发:启动调度 / 扫描完成 / 每小时 / 命令|命令refreshPreciseLT()|存储kami_core_db|来历覆盖精简数据库或核心自愈写入的旧公式初值,并在档案更新后做全量刷新showTopPredators查看生效档案 — 打印当前生效的威胁档案:来源、扫描时间、威胁分基准 harm;每只的 hand、标签、威胁分、vio/ATS/ATR;主人沉寂超 10 天被降级的杀手列表(编号@主人、手型、天数);再加一行清算线选档规则说明。用 clog 输出(控制台原样打印,并逐行写入共享日志缓冲)。触发:命令|参数PREDATOR_INACTIVE_DAYS=10|命令showTopPredators()|存储kami_top_predators(读)showTopPredators装备加成一览 — 列出带装备的杀手:装备名或件数 → ATS+/ATR+。同时说明装备计入口径:读数 + 实际装备加成 + 剩余空格 × 余量(假定容量 N 件,余量 ATS/ATR),并给出调节命令。整段 try 包裹,失败静默;容量和余量从localStorage读,缺省用默认值。触发:命令showTopPredators()|参数默认假定装备容量 1|命令setPredatorEquipHeadroom(shift, ratio);setPredatorEquipCapacity(n)|存储kami_predator_equip_headroom;kami_predator_equip_headroom_atr;kami_predator_equip_capacity(读)|来历让「清算线为什么这么高」可以核对|版本1.2.8compareLT清算线对照 — 每只 kami 现场重算两条线并对比。兜底线:内置包络档案,即数据库建库初值口径。实战线:现役档案 + 活跃度选档。输出差值的平均和中位数、实战高于兜底的只数,另附旧公式(V41 满配假想杀手)均值作参考。按差异绝对值降序,默认展示前 20 只,传 true 展示全部。实战高于兜底(>0.05pp)的只数大于 0 时,提示内置包络档案已被现役最强突破,需要同步更新辅助、数据库两处默认档案。数据库为空或无可比记录时打 ⚠️ 返回。触发:命令|参数默认展示 20 只;高于判定 0.05pp|命令compareLT();compareLT(true)|来历负值表示现役威胁低于包络假设,采集窗口可以更长- 导出
computePreciseLTForRecord供核心逐条重算测试线独有 — 把逐条精确清算线计算函数挂到 window,让核心syncKamiDb增量补库时只对新增的几条就地精确重算。window.computePreciseLTForRecord=computePreciseLTForRecord(跨脚本全局约定)。触发:脚本加载时挂载,由核心调用|来历核心补库用的旧「假想满配杀手」公式只会低估(NORMAL 最多 −9.5pp),停采线跟着偏低有漏停风险。辅助启动重算(约 95 秒)总是早于核心syncKamiDb(约 120 秒以上),修不到这批,漏停窗口有 45~55 分钟。不直接用refreshPreciseLT,是因为它会重刷全库,档案空桶时可能把老记录的 LT 反向压低|版本辅助测试版1.2.10
杀手候选扫描
- 杀手候选扫描(db-first 三阶段)仅观测 — 从当前账户全部 kami 里筛出「疑似杀手 build」:出生原始属性高 violence、高 harmony、低 power。结果交给用户决定要不要培养成杀手、加进杀手清单。只读扫描:不读 DOM、不写
localStorage、不发 tx。阶段1:在kami_core_db内存里按 base 阈值筛(瞬时,0 次 API)。阶段2:调 1 次getByOperator拿本账户持有列表求交集。阶段3:只给存活候选逐只getByIndex查 level/state/total(每只约 0.4 秒,候选通常 ≤10 只)。触发:启动自动一次 + 命令findKillerCandidates()|参数T_VIO_MIN=23;T_HARM_MIN=15;T_POW_MAX=12|命令findKillerCandidates()|来历防止自动升级巡检把杀手的技能点按采集方向重新分配。用 base 不用 total,是因为 base 永远不随等级、技能、装备变化,是判断 build 潜力唯一稳定的指标 - 控制台命令:自定义阈值扫描 — 控制台随时手动扫描,可以临时覆盖三个阈值。opts 里 vio/harm/pow 必须是有限数字(
Number.isFinite)才生效,传非数字自动回落默认值。函数返回候选数组,挂在window.findKillerCandidates。触发:命令|参数默认 vio≥23 / harm≥15 / pow≤12|命令findKillerCandidates();findKillerCandidates({ vio: 25, harm: 17, pow: 10 })|来历阈值调低候选变多、误报也变多;power 高说明属性点偏采集方向,不像杀手 build - 钱包地址守卫仅观测 — 取不到当前钱包地址时,打印「❌ 无法获取钱包地址」后直接返回。先读
connectedAddress.value_,没有再读 .value,两个都没有就终止。触发:被findKillerCandidates调用 - 杀手清单来源降级仅观测 — 判断候选是否「已在杀手清单」时,优先用核心脚本的
window.MY_KILLER_KAMIS;核心没加载就降级用本脚本的RESET_WHITELIST。要求window.MY_KILLER_KAMIS是 Set 类型才采用。触发:被findKillerCandidates调用 - 账户无 kami 终止仅观测 — 本账户 kami 列表为空时,打印「❌ 当前账户没有 kami」后返回。
getByOperator这一步没包 try,手动调用时 API 抛错会直接冒到控制台;自动扫描外层有 catch 兜住。触发:被findKillerCandidates调用 - 数据库为空终止仅观测 —
kami_core_db为空时红字提示「请先运行精简数据库脚本构建」,然后返回。注释写「db 为空直接终止」;代码实际上是先发了那 1 次getByOperator,之后才检查 db。触发:被findKillerCandidates调用 - 旧库无 base 字段时降级 API 全扫仅观测 — 数据库里完全没有
vioBase/harmBase/powBase字段(旧库)时,自动改走_scanByApiFallback,逐只查全量详情。同时红字提示重建数据库,并打印预计耗时(只数 × 0.4 秒)。只要有任意一条记录带 base 字段,就走 db-first 流程。缺 base 的记录会在阶段1被静默排除,日志里只打印「base 字段填充 N 只」。触发:数据库检测到无 base 字段时自动|参数每只约 0.4 秒|来历全量 API 扫描要 N×0.4 秒,db-first 内存筛选是 0;旧库只能慢速兜底 - 降级全扫:从数据库补 LT仅观测 — API 详情里没有 LT(清算线)这个衍生值,所以降级扫描时按 index 去
kami_core_db里找记录补上。base 取 stats.*.base(缺省 0);total 缺失回落 base;body/hand 转小写;state 转大写。触发:降级全扫时 - 降级全扫:单只失败跳过 + 每 30 只进度仅观测 — 单只查询异常计入 skipped,不中断整体。每扫 30 只打印一次进度(已扫/候选/跳过)。try/catch 包住每只的查询。触发:降级全扫时|参数进度间隔 30 只|来历避免长时间没有输出,让用户误以为卡死
- 阶段1:数据库内存筛选 + 填充统计仅观测 — 打印数据库总条数、base 字段填充只数、当前账户持有只数,然后在内存里筛出三字段齐全且满足阈值的候选。只有
vioBase、harmBase、powBase三个都不为 null 才参与比较,日志注明「瞬时,0 API」。触发:db-first 流程 - 阶段2:剔除「幽灵」候选仅观测 — 数据库里有记录、但已经不在当前账户名下(转走了)的候选会被剔除,并列出这些编号。候选与账户持有 index 的 Set 求交集,打印剔除数和确认在名下的数量。触发:db-first 流程
- 阶段3:动态查询,静态字段以数据库为准仅观测 — 只为存活候选发 API 拿动态字段(level/state/total/hp);静态字段直接用数据库记录。body/hand 优先用数据库值(静态可信),API 值只在缺失时兜底;level 在 API 缺失时用数据库值;total 缺失回落 base;单只异常计入 skipped。触发:db-first 流程
- 无候选时收尾仅观测 — 没有符合条件的候选时,打印「扫描完成:无符合条件的候选(已查 X 只,跳过 Y)」,返回空数组。触发:扫描结束
- 候选排序仅观测 — 按
vioBase降序;vioBase相同再按harmBase降序。触发:扫描结束 - 候选逐只明细输出仅观测 — 红色大字标题;每只打印 vio/harm/pow/hp 的 base→total 对照、body/hand、等级、状态、LT%。已在清单的加「✅[已在杀手清单]」用普通色,不在清单的新候选(NEW)整行红字。没有 hand 就不显示 hand 段;LT 不是有限数时显示「LT=未知」;先打印一行格式说明「base(出生原始值) → total(含等级技能加成)」。触发:有候选时|来历NEW 候选才是用户需要做决策的对象,所以红字突出;红色标题方便在大量日志里快速定位
- 按 body affinity 分组汇总仅观测 — 把候选按 body affinity 分组,打印每组只数和编号。没有 body 的归入 (unknown);组名转大写输出。触发:有候选时|来历杀手 build 与 body affinity 强相关,分组便于按地块规划杀手部署
- 清单状态统计 + 加入清单指引仅观测 — 统计「X 只已在杀手清单 / Y 只 NEW」,有 NEW 时红字加粗。有 NEW 时还会提示:要同时更新核心
MY_KILLER_KAMIS和辅助RESET_WHITELIST两份常量,并给出控制台临时验证方法。临时加入用window.MY_KILLER_KAMIS.add(index),刷新后失效;最后返回候选数组。触发:有候选时|命令window.MY_KILLER_KAMIS.add(<index>)|来历只改一处的话,下次升级时杀手的技能点会被自动重分配 - 启动后自动扫描一次仅观测 — 脚本启动后等游戏 API 就绪,再延迟 30 秒,自动按默认阈值跑一次候选扫描,打印三行红字横幅。每 5 秒轮询,钱包地址 +
kamis.getByIndex+accounts.getByOperator三者齐备才算就绪。只执行一次,不设重复定时器。触发:启动自动|参数MAX_WAIT=5 分钟;POLL_MS=5000;就绪后错峰 30 秒|来历30 秒错峰是为了避开启动期自动升级巡检的 API 高峰;出生属性是静态的,重复扫描没有意义 - 自动扫描异常与超时提示仅观测 — 自动扫描抛错时只红字提示「可手动
findKillerCandidates()重试」,不影响脚本其他功能;5 分钟 API 仍未就绪时打印超时提示。try/catch 包住findKillerCandidates。触发:自动扫描出错或超时|参数超时 5 分钟|命令findKillerCandidates()
2.4 升级与技能管理
自动升级休息中的 kami,按标准路线加点;发现非标准加点且清算线高时用洗点药水重置。自家杀手一律跳过。
保护名单与标准路线
- 标准技能集合 — 定义自动技能管理认可的 18 个标准技能 ID。kami 已点的技能不在集合内就算非标准加点,会触发技能重置并按标准顺序重加。生存类(3xx):311、312、313、321、322、323、331、341、343、352、353、361(不含 342、351)。收益类(4xx):411、412、413、421、422、431。据后文,
detectNonStandardSkills用 !STANDARD_SKILLS.has(skillId)判定非标准。增删 ID 必须同步改SKILL_ORDER和SKILL_CAP,否则会出现刚点完就被判非标准、再次重置的循环。触发:自动升级和技能巡检每次检查技能时|参数18 个技能 ID|来历这是作者面向「少被清算、采集更久」的个人策略,不是唯一解 - 手工杀手保护名单 — 手工填写不想被自动升级、技能重置、自动加点动到的 kami 编号,比如自养杀手或特殊 build 的个体。默认是空集合,在大括号里填 kami index(名字旁 # 后的数字),逗号分隔。已在杀手监控脚本配置过的 kami 会自动进
MY_KILLER_KAMIS,无需重复填。触发:每次自动升级或技能巡检前查询|参数RESET_WHITELIST=空集合|来历杀手 kami 的加点思路和采集 kami 完全不同,被按标准 build 重置会毁掉杀手 build - 保护名单合并判定(
_isProtectedKami) — 判断一只 kami 是否受保护:在RESET_WHITELIST或window.MY_KILLER_KAMIS里任一命中,就返回 true,被升级、重置、加点整体跳过。编号统一转 Number 再查两个集合。据后文调用点:handleOneKamiLevelAndSkill入口跳过;巡检时受保护 kami 的非标准技能列表直接置空;checkAllKamiSkills按白名单分组展示。关于MY_KILLER_KAMIS由谁维护,头注释写监控脚本,本板块注释写核心脚本维护、监控脚本注册;代码只读不写。触发:处理每只 kami 的入口处|存储window.MY_KILLER_KAMIS(跨脚本全局,只读)|来历两份名单合并判断,避免不同步导致杀手被误重置 - 杀手集合类型保护 —
window.MY_KILLER_KAMIS还没加载或不是 Set 时,安全跳过这项检查,不抛错。先判断 instanceof Set 再调用 .has。触发:_isProtectedKami调用时 - 技能投点上限表(
SKILL_CAP) — 登记每个技能的投点上限:大多数 5 点,331、361、431 这三个关键节点只有 1 点。表里也登记了非标准的 342、351(上限 5)。据后文,加点时用SKILL_CAP.get(id)?? 5,未登记的技能默认上限 5。触发:自动加点时读取|参数默认上限 5;331/361/431 上限 1 - 标准加点顺序(先生存后收益) — 自动加点严格按固定顺序,一个技能点满再进下一个。先点降低清算线的防御系技能,再点收益类。顺序:311→312→321→323→331→322→343→341→313→353→352→361→411→412→421→431→413→422,共 18 个,与
STANDARD_SKILLS一致。改顺序或上限要同步改STANDARD_SKILLS。触发:自动加点和重置后重新加点时|参数SKILL_ORDER(18 项)|来历对应整套脚本「减少 TX、省 gas、采集更久」的策略:先让 kami 能在地块上安全采集更久。未点 shift 类技能时,清算线天生高出一截
经验表与冷却工具
- 等级经验成本表 — 静态维护从 1 级到 56 级每一级升下一级所需的经验值。
XP_COST[n] 表示从 n 级升到 n+1 级所需经验,例如 1:40、10:317、30:31827、50:3182779、56:12675377。相邻等级增长约 1.259 倍(每 10 级 ×10)。游戏调整经验曲线时要同步更新。触发:computeLevelUps查表|参数XP_COST表;MAX_LEVEL_SUPPORTED=56 - 可连升级数计算(
computeLevelUps) — 根据当前等级和累计经验逐级模拟扣除,算出一次能连升几级。这决定自动升级要发几笔升级 tx。循环中经验够就扣除、等级 +1、次数 +1;返回 {ups,targetLevel,xpRemain}。据后文,handleOneKamiLevelAndSkill和技能巡检调用它。触发:自动升级查到 kami 的 level/xp 后|命令upgradekamis() - 升级计算上限与容错停止 — 等级达到 56 或查不到表项时安全停止,不会死循环;level 或 xp 非数字时按 0 处理。while (cur <
MAX_LEVEL_SUPPORTED),need 缺失或经验不足就 break;Number(x)||0。游戏开放更高等级时,要先扩充XP_COST再抬高上限,否则自动升级到 56 级即停。触发:computeLevelUps内|参数MAX_LEVEL_SUPPORTED=56 - 升级/复活冷却工具(本段只有头注释,函数体在 1266 行之后) — 头注释说明:后文的冷却计算工具与核心脚本
_cooldownRemainSec同源,同为 T=180s、以time.last为锚点。升级入口用它做冷却预筛:冷却中的 kami 本轮跳过,下轮再升。复活只观察、不预筛。据注释:handleOneKamiLevelAndSkill入口按 remain 判断跳过还是继续;升级流程里其余调用点(等级升级前、加点前、Respec 失败分支)已过入口预筛,remain 恒为 0,只打观察日志供事后 grep 核对。helperReviveOnce只打观察日志。具体实现以后文代码为准。触发:升级入口预筛(据注释)|参数冷却 T=180s(据注释)|来历死亡是保命操作,必须尽快复活,不能因为冷却跳过等下一轮;而且复活对象是 DEAD kami,冷却语义还没定|版本1.1.17 升级/复活冷却观察日志;1.1.18 升级冷却预筛 - 冷却已过时长/剩余时长计算 — 给一个
harvest.time.last(秒级时间戳),算出距上次操作过了多久(age)、离冷却结束还剩多久(remain)。remain>0 就是大概率还在冷却窗里。age=现在秒数-time.last;remain=max(0,180-age)。算法跟核心脚本_cooldownRemainSec同源(T=180s,以time.last为锚点)。一开始只用来打观察日志,1.1.18 起还给单只升级入口做预筛判定;复活那边仍只打观察日志。触发:被调用:单只升级入口预筛、等级升级/加点/Respec 的观察日志、复活观察日志|参数冷却窗 T=180s(写死在函数里)|来历冷却机制实测定为 T=180s、以time.last为锚点;一只 kami 在冷却窗里发 tx 会失败|版本1.1.17 升级/复活冷却观察日志;1.1.18 升级冷却预筛 time.last读不到时返回 null —time.last是 null、undefined、0 或非正数时,函数返回 null,不瞎算。用 !(timeLastSec>0) 判断后直接返回 null。调用方拿到 null 时:升级预筛那里跳过预筛、照原逻辑继续;观察日志那里干脆不打这条。触发:每次调用_obsCooldownAge时|版本1.1.17 升级/复活冷却观察日志
技能检测、洗点与加点
- 洗点药水库存查询(
getRespecPotionCount) — 查询当前账户背包里 Respec Potion 的数量,重置技能前必须先确认有库存。钱包地址兼容connectedAddress.value_和 .value 两种封装;用accounts.getByOperator(addr)查账户,在 inventories 里按item.index===11403 找到药水,返回 balance。据后文,一键升级开始时查一次,之后随重置消耗在内存里递减,不重复查询;并暴露为window.getRespecPotionCount。触发:一键升级流程开始、checkAllKamiSkills、控制台手动|参数RESPEC_POTION_INDEX=11403|命令getRespecPotionCount() - 药水查询失败按 0 处理 — 账户没有背包数据时返回 0;查询出任何异常就记「❌ 获取 Respec Potion 数量失败」并返回 0,把查询失败当作没有药水。没有
acc.inventories时返回 0;catch 中 log 后返回 0。触发:getRespecPotionCount异常时|来历宁可跳过重置,也不冒险 - 找出不属于标准 build 的投点 — 把一只 kami 的实际技能投点和标准 build 对比,挑出「点了、但不在标准集合里」的技能,返回 [{
skillId, points}]。空数组就是全部标准。遍历链上 investments:skillId=Number(inv.index),points=Number(inv.points)||0;points>0 且STANDARD_SKILLS里没有这个 id 就算非标准。触发:被调用:单只升级流程里等级升完之后、分配技能点之前;upgradekamis 预扫描;checkAllKamiSkills体检|参数STANDARD_SKILLS(标准技能 ID 集合,在别的板块维护)|来历非标准投点多半是以前手动加过点或沿用旧方案;点数被占着,标准的防御/收益技能就点不满,所以要先 Respec 再按标准重新加 - 投点数据异常时的容错 — investments 不是数组时当成「没有非标准技能」,不会触发重置;points 强制转成数字,转失败按 0 算。
Array.isArray不通过就直接返回空数组;Number(inv?.points)||0。触发:每次检测|来历数据读坏时宁可不重置,也不误烧 Respec 药水 - 发送技能重置 tx — 调链上
skill.reset(kamiId),把这只 kami 的技能点全部洗掉退回来,链上会自动扣 1 瓶 Respec Potion。先确认window.network.api.player.pet.skill.reset存在,没有就 throw,由主流程 catch 住记日志后接着往下走;有就发 tx。注意:这个函数本身在发送前不查紧急锁,靠上层 upgradekamis 每只处理前查一次紧急锁、并且拿着普通锁在跑。触发:被单只升级主流程调用(发现非标准技能,且药水≥1、LT>40) - Respec 确认等待与回退 — tx 带 wait 方法就等链上确认;没有(钱包封装不同)就固定等 10 秒再往下走。typeof
tx.wait==='function' 就 awaittx.wait(),否则delay(10000)。然后打「技能重置成功」日志。触发:每次 Respec 发送之后|参数回退等待delay(10000)=10 秒|来历防止重置还没生效就去读投点,读到的是旧数据 - Respec gas 记账挂钩 — Respec tx 发出后交给共享的 gas 账本记一笔,动作类型记为 respec。
window.__kamiGasRecord是函数才调用,传 ('respec',[kamiId],tx);整段包在 try/catch 里,出错直接吞掉,不影响重置流程。触发:每次 Respec tx 发送后|来历gas 真值账本要在发 tx 的那一刻按动作打标签,事后没法从链上反推是什么动作|版本1.2.4 gas挂钩 - Respec 冷却观察日志(发送前 / 成功)仅观测 — 发重置 tx 之前打一行日志,记下
time.last的 age/remain,并标出「⚠️冷却窗内」还是「已过冷却」;成功后再打一行「结果=成功(已消耗1瓶Respec药水)」。格式是「🔬 [Respec/冷却观察]」;_obsCooldownAge返回 null 就不打。只写日志,不影响发不发 tx。触发:每次 Respec 发送前后|来历事后 grep「🔬 [」,按 age<180 和 age>=180 分两组统计成败率,用来查清 Respec 到底受不受冷却影响|版本1.1.17 升级/复活冷却观察日志 - 按标准顺序制定加点计划 — 先把整份加点计划算好:沿
SKILL_ORDER一个技能一个技能点到上限,点数分完或顺序走完就停。每个技能本次投点=min(上限-已投, 剩余点数),结果存成 plan=[{skillId,times}]。SKILL_CAP里没登记上限的技能按 5 点算。开发送前会打一行「连续发送 N 笔(技能x次数…)」。触发:被单只升级主流程调用(确认还有可用技能点时)|参数SKILL_ORDER(标准加点顺序);SKILL_CAP未登记时默认上限 5 - 无点可投时不发任何 tx — 技能点≤0 直接返回 0;计划为空(相关技能都点满了)时打「无技能需要升级」,同样返回 0。入口先判
skillPoints,算完计划再判plan.length===0。触发:每次加点调用|来历省 gas,不发空 tx - 加点 API 前置校验 —
api.player.pet.skill.upgrade不存在就直接抛错,一笔都不发。抛出的错由单只流程往上冒,被 upgradekamis 逐只循环的 catch 接住,记日志后继续处理下一只。触发:每次加点调用 - 连续发送、只等最后一笔确认 — 整份计划的加点 tx 不间断地连着发(一笔投 1 点),最后只等最后一笔确认。链上按 nonce 顺序执行,最后一笔确认了就说明前面全部完成。双层循环逐笔调
upgrade(kamiId, skillId),记下lastTx和已发笔数;最后lastTx.wait(),没有 wait 就等 12 秒。收尾打「技能升级完成,发送 sent/total 笔」。触发:加点计划非空时|参数没有 wait 时回退delay(12000)=12 秒|来历总耗时从「N×确认时间」降到大约「1×确认时间」 - 每笔加点前给紧急锁让路 — 每发一笔加点 tx 之前都查一次紧急锁;有锁就等它释放(最多 300 秒),释放了再接着发。超时就置失败标记,跳出双层循环不再发。
window.hasEmergencyLock?.() 为真时调waitForEmergencyRelease('#编号技能升级', 300000);返回 false 就打「等待紧急锁超时,停止发送(已发x/y笔)」。已发出去的不回滚,没投完的点下一轮会补上。触发:加点连发循环里的每一笔|参数紧急锁等待上限 300000ms=300 秒|来历杀手来袭时核心脚本会发救急 tx,不让路就会抢 nonce,救急 tx 可能被挤掉 - 紧急锁在、等待接口却缺失时停止 — 紧急锁存在,但
waitForEmergencyRelease这个函数不存在时,不冒险继续发,直接停止并记日志。typeofwaitForEmergencyRelease!=='function' 就置 failed=true 跳出循环。触发:加点连发时检测到紧急锁|来历等不了就宁可不发(fail-closed),避免和救急 tx 撞 nonce - 单笔发送失败即停止后续 — 任何一笔加点 tx 发送抛错,就记日志(错误信息截 60 字)并停掉后面所有发送。catch 里置 failed=true 并 break,外层循环看到 failed 也退出。触发:加点单笔发送异常|来历防止中间出现 nonce 空洞,导致后面的 tx 全部卡住
- 确认阶段出错只警告不抛 — 等最后一笔确认时出了错,只打警告、不往上抛;tx 已经发出去了,结果以链上为准。
lastTx.wait()包在 try/catch 里,catch 中打「等待技能升级tx确认出错」。触发:加点连发结束后的确认阶段 - 加点 gas 记账挂钩 — 每笔加点 tx 发出后记进 gas 账本,动作类型记为 skill。
__kamiGasRecord('skill',[kamiId],tx),包在 try/catch 里吞错。触发:每笔加点 tx 发送后|版本1.2.4 gas挂钩 - 加点冷却观察日志(加点前 / 结果)仅观测 — 连发前记一条
time.last的 age/remain 和计划笔数;结束后记起始 age,以及结果是成功、部分成功还是失败(成功 x/y 笔)。前后两条成对出现。格式「🔬 [加点/冷却观察]」;sent≥total 算成功,sent>0 算部分成功,否则失败。拿不到冷却信息就不打。触发:每次加点连发前后|来历事后 grep,按冷却窗内/外两组统计加点成败率|版本1.1.17 升级/复活冷却观察日志 - 技能重置清算线门槛 — 只有清算线高于 40% 的 kami 才用 Respec 药水重置技能,≤40% 的暂不重置,把稀缺药水优先留给高危个体。据后文:重置条件是药水 ≥1 且 LT>40;LT≤40 时打「⏭️ 清算线较低,暂不重置」;
checkAllKamiSkills以 40 为分界分高低两组展示。触发:技能重置判定和巡检分组时|参数RESPEC_LT_THRESHOLD=40(精确清算线刻度)|命令checkAllKamiSkills()|来历旧公式刻度的 75% 约等于新刻度的 40%
单只升级流程
- 单只 kami 一条龙处理 — 对一只休息中的 kami 依次做:等级升级 → 检测非标准技能 → 满足条件就 Respec → 按标准顺序分配技能点 → 有变化就重算清算线、写回本地数据库。返回值表示是否用掉了一瓶 Respec 药水。
handleOneKamiLevelAndSkill(k, respecPotionCount, prefetched);具体每一步的守卫见下面各条。触发:被 upgradekamis 逐只调用|存储kami_core_db - 预取数据复用 — 上层预扫描已经查过的数据(progress+skills)直接传进来用,不重复调 API;没有预取数据时自己查一次,带上 progress、skills、harvest。res = prefetched ||
getByIndex(index,{progress,skills,harvest})。触发:每只进入单只流程时|来历省 API 查询次数,账户里 kami 可能有 198 只以上|版本1.1.17 升级/复活冷却观察日志(查询里的 harvest:true) - 升级预筛接线修复:缺 harvest 时补读一次 — 预取数据里没有 harvest 字段时,只对真正进到这个函数的 kami 补查一次 harvest,让冷却预筛和观察日志能拿到
time.last。res 存在且没有res.harvest时,调getByIndex(index,{harvest:true}),拿到了就回填res.harvest。预扫描那边的查询保持不带 harvest。触发:单只流程入口,prefetched 缺 harvest 时|来历0709 夜实盘审计发现冷却预筛和观察日志整夜 0 输出,升级却发了 45+ 笔 tx:预扫描没带 harvest,prefetched 一传进来就短路了带 harvest 的查询。#15348 在紧急停采窗口排队 179 秒后连发升级 tx,正是该拦的场景,却一条日志都没有|版本1.1.19 升级预筛接线修复 - 补读 harvest 诊断日志仅观测 — 补读成功时打「[升级诊断] #编号 补读
harvest.time.last: age=..s remain=..s」。_obsCooldownAge返回非 null 才打。触发:补读 harvest 成功后|来历用来实盘确认 1.1.19 的接线确实接通了|版本1.1.19 升级预筛接线修复 - 补读失败按无冷却处理 — 补读 harvest 抛错时只打「本轮按无冷却处理」,
res.harvest保持为空,预筛和观察日志照修复前的行为跳过,流程继续。try/catch 包住补读;拿不到冷却信息就不拦(fail-open)。触发:补读 harvest 异常|来历修复本身零风险,不引入新的失败模式|版本1.1.19 升级预筛接线修复 - 入口状态检查:非 RESTING 跳过 — kami 当前状态不是 RESTING(比如在采集)就整只跳过,打「跳过 #编号(状态=XX)」。String(
res.state).toUpperCase()!=='RESTING' 就返回usedPotion=false。注意这个检查排在补读 harvest 之后。触发:每只进入单只流程时|来历升级、重置、加点链上都要求 kami 处于休息态 - 杀手保护名单跳过 — 命中保护名单的 kami(自家杀手)不做任何自动升级、重置、加点,打日志提示需要手动操作。
_isProtectedKami(index):RESET_WHITELIST里有,或window.MY_KILLER_KAMIS是 Set 且包含这个编号,都算保护。触发:状态检查通过之后|参数RESET_WHITELIST(手工维护的编号集合)|来历杀手是专用加点,不能被当成非标准技能重置掉。window.MY_KILLER_KAMIS由监控脚本自动注册 - 升级冷却预筛(一处入口拦住全部下游 tx) —
time.last算出来还在 180 秒冷却窗里时,这一只本轮整只跳过:不发升级 tx,也不发重置和加点 tx。_obsCooldownAge(res.harvest.time.last).remain>0 就打「⏳ [升级/冷却预筛] 冷却中剩余 Ns,本轮跳过」并返回。不 await 死等、不占锁,只影响这一只。注释说「留到下一个 30 分钟周期」;代码实际上,首轮结束 2 秒后的复查轮也会再扫到它,那时已过冷却就会在复查轮里升。读不到time.last时不拦。触发:保护名单检查之后|参数冷却窗 180s;定时周期 30 分钟|来历升级是一只 kami 连发多笔 tx,撞上冷却会 N 笔全废,最需要预筛|版本1.1.18 升级冷却预筛 - 算可升级数并连续发送升级 tx — 根据当前等级和累计 XP 算出能连升几级,连着发这么多笔升级 tx,只等最后一笔确认,最后打「升级完成:旧等级 → 新等级」。
computeLevelUps按XP_COST表逐级扣经验,直到经验不够或到MAX_LEVEL_SUPPORTED。先打计划日志,再逐笔pet.level(kamiId);最后一笔有 wait 就等,没有就 delay 12 秒。升级 API 不存在直接 throw,由上层逐只 catch。触发:通过冷却预筛、且可升级数 ups>0 时|参数没有 wait 时回退delay(12000)=12 秒|来历不逐笔等确认,速度快很多;链上按 nonce 顺序执行,最后一笔确认就说明前面都完成了 - 每笔升级前给紧急锁让路 — 每笔升级 tx 前查紧急锁,有锁就等释放(最多 300 秒)。超时或者等待接口不存在,就停止发升级 tx。
waitForEmergencyRelease('#编号等级升级',300000)。代码实际:这里只是 break 出升级循环,没有置失败标记,流程会继续往下走非标准检测、Respec 和加点;加点阶段每一笔会重新让路一次,但 Respec 发送前不查紧急锁。触发:等级升级连发循环里的每一笔|参数紧急锁等待上限 300000ms|来历避免和核心脚本的救急停采 tx 抢 nonce - 升级单笔失败即停 / 确认出错只警告 — 某一笔升级 tx 发送失败就停掉后面的升级;等最后一笔确认出错只打警告,不中断流程。发送 catch 里 break;确认阶段用 try/catch 包
lastTx.wait()。触发:等级升级发送或确认异常|来历防止 nonce 空洞让后续 tx 卡住 - 升级 gas 记账挂钩 — 每笔等级升级 tx 发出后记进 gas 账本,动作类型记为 upgrade。
__kamiGasRecord('upgrade',[kamiId],tx),包在 try/catch 里吞错。触发:每笔升级 tx 发送后|版本1.2.4 gas挂钩 - 等级升级冷却观察日志(升级前 / 结果)仅观测 — 连发升级前记 age/remain 和计划升几次;结束后记结果是成功、部分成功还是失败(成功 x/y 笔)。格式「🔬 [升级/冷却观察]」。能走到这一步说明已经过了入口预筛,所以 remain 通常为 0,只供事后统计。触发:等级升级连发前后|来历事后 grep,按冷却窗内/外统计升级成败率|版本1.1.17 升级/复活冷却观察日志
- 没升级就复用投点数据 — 真的升过级才重新查一次技能投点;没升级就直接用已有数据,省一次查询。
actualLevelUps>0 时调getByIndex(skills),否则 res2=res。触发:等级升级阶段结束后|来历省 API 查询 - 发现非标准技能时打印完整分配仅观测 — 发现非标准技能时打一行日志,同时列出非标准技能和当前完整投点,并把重置前的完整明细存下来,留给后面的重置摘要做前后对比。
fullInvBeforeReset过滤掉 points>0 以外的项;日志格式「⚠️ #编号 发现非标准技能: id(n点) | 当前完整分配: [idxn,…]」。触发:检测到非标准技能时|来历方便对照看这只 kami 原来是怎么点的 - 重置条件一:Respec 药水至少 1 瓶 — 药水数量小于 1 时,只打「需要重置但 Respec Potion 不足」,跳过重置,接着分配现有的点。用上层传进来的
respecPotionCount判断。触发:发现非标准技能时 - 重置条件二:清算线 LT>40% 才洗点 — 从本地精简数据库读这只 kami 的清算线 LT,LT≤40% 就暂不重置,优先把药水留给清算线高的。
window.kami_core_db按 index 查 LT,查不到按 0 算,也就不会重置。注释写的「LT 门槛 75(%)」,代码实际用RESPEC_LT_THRESHOLD=40(精确清算线刻度,旧公式刻度的 75% 约等于新刻度 40%)。触发:药水充足、且发现非标准技能时|参数RESPEC_LT_THRESHOLD=40|存储kami_core_db(读window.kami_core_db)|来历Respec 药水稀缺,先洗清算风险高的个体;LT 低说明暂时安全,不急 - 重置后缓冲 2 秒再重查投点 — 重置成功后记下用掉了药水,等 2 秒让索引器同步,再重新查投点,后面按新数据分配。
resetKamiSkills成功 →usedPotion=true、resetSucceeded=true →delay(2000)→getByIndex(skills)。触发:Respec 成功后|参数delay(2000)=2 秒|来历防止读到索引器还没同步的旧投点 - 重置失败只记日志、照常加点 — Respec 抛错时只打「重置技能失败」,不中断,接着按当前投点分配剩下的点。catch 里不改
usedPotion,流程继续往下。触发:Respec 异常 - Respec 失败冷却观察日志仅观测 — 重置失败时打一行,记 age,并提示「药水是否仍被扣待核对余额」。格式「🔬 [Respec/冷却观察] 结果=失败」;这里重新调一次
_obsCooldownAge算 age。触发:Respec 失败分支|来历tx revert 通常不扣药水,但需要人工核对余额确认,这条日志用来和成功分支对照统计|版本1.1.17 升级/复活冷却观察日志 - 分配技能点前再查一次状态 — 升过级或重置过,就重新查一次最新状态;这时已经不是 RESTING(比如被派去采集或遭攻击)就跳过技能分配。没升级也没重置时复用已有数据。
actualLevelUps>0 或usedPotion为真时调getByIndex(skills)取 state;不是 RESTING 就打「状态已变为XX,跳过技能分配」并返回。触发:准备分配技能点前|来历处理过程中 kami 状态可能变了,防止误发 tx - 有可用点就按标准顺序分配 — 读出可用技能点,点数大于 0 就交给连续加点函数去投;等于 0 就打「无可用技能点,跳过」。先把已投明细做成 Map(技能ID→点数),再调
upgradeSkillsByOrder(kamiId, index, points, invMap, time.last)。触发:状态二次检查通过后 - 技能重置摘要日志仅观测 — 只有真的重置成功时,才打一行「📋 [技能重置摘要] #编号 LT=x% | 旧:[…] → 新:[…] | 用Potion=1 | 可分配点=n」。重新查询失败就只打警告。加点完成后再查一次投点生成「新」串;「旧」串来自重置前存下的明细;LT 从
kami_core_db读。触发:本只 Respec 成功时|存储kami_core_db(读)|来历方便日后 grep「技能重置摘要」审计所有 Respec 事件 - 没变化就跳过数据库更新 — 没升级、没有可分配点、也没重置时,打「无实际变化,跳过数据库更新」,直接返回。条件是
actualLevelUps===0 且 points===0 且 !usedPotion。注意 points 是可用点数;有点数但加点全部发送失败,仍会走数据库更新。触发:加点阶段结束后|来历省一次 API 查询 - 升级/加点后重算清算线并写回本地数据库 — 重新拉这只 kami 的属性,用精确公式重算清算线 LT/LTHP,更新本地记录后写回
localStorage,打「📊 [数据库更新] LT: 旧% → 新%,level,harmony,maxhp」。getByIndex取 stats/traits/bonus/harvest/progress,拿到harmony.total、health.total、defense.threshold的 ratio/shift、body 亲和、level,调computePreciseLTForRecord算。更新 rec 的 harmony、maxhp、ratio、shift、LT、LTHP、level(非空时)、harvestId(新值为空就保留旧值),然后setItem('kami_core_db')。触发:本只有实际变化时|存储kami_core_db|来历加点会改防御阈值,清算线必须实时重算,停采决策和其他脚本读的才是新值|版本v1.1.1(清算线改精确公式+顶尖杀手档案) - 数据库回写的容错 —
kami_core_db里找不到这只的记录时只警告、跳过;整个更新抛错也只警告,不影响返回值。rec 不存在就打「未找到记录」;外层 try/catch 打「更新失败」。window.kami_core_db不是数组时静默不更新。触发:数据库回写阶段|存储kami_core_db
批量升级 upgradekamis
- 轮询等待紧急锁和普通锁都释放 — 每 15 秒查一次紧急锁和普通锁,两把都没了就返回 true;等满 10 分钟还没释放就打「等待锁释放超时,放弃本次升级」并返回 false。紧急锁调
window.hasEmergencyLock?.();普通锁直接看 !!window.__txNormalLock。触发:被 upgradekamis 调用:预扫描时碰到紧急锁、正式升级前抢普通锁失败|参数MAX_WAIT=10 分钟;POLL_INTERVAL=15 秒|来历两把锁核心/辅助脚本共用,防止多个脚本同时发交易撞 nonce。10 分钟上限按注释是和锁自身的超时过期对齐,所以不会死循环 - 等锁时打印持锁方仅观测 — 每轮打「⏳ 紧急锁+普通锁[脚本/操作] 仍存在,已等待N秒」,谁拿着锁一眼就能看出来。从
__txNormalLock.script和 .operation 读持锁方。触发:锁还没释放的每一轮|来历便于排查卡锁 - 核心脚本没加载时不当作有紧急锁 —
hasEmergencyLock不存在时,可选链返回 undefined,按没有紧急锁处理。window.hasEmergencyLock?.()。触发:每轮查锁|来历注释说这样辅助脚本可以独立运行(但抢普通锁那一步另有问题,见 upgradekamis「抢普通锁」那条) - 批量升级:先只读、后加锁写入 — 一次处理所有休息中的 kami(升级、加点、必要时 Respec)。阶段一全程只读不占锁,确认真有 kami 要处理,阶段二才去抢普通锁发 tx。采集中的 kami 自动跳过。阶段一:取地址和 kami 列表 → 筛出 RESTING → 预扫描 → 过滤并排序。阶段二:防重入 → 抢锁 → 逐只调单只流程 → 复查一轮。触发:自动:游戏 API 就绪后跑一次,之后每 30 分钟;紧急锁中断 5 秒后续跑;等锁 5/15 秒后重试;手动
upgradekamis()|命令upgradekamis()|来历不无谓占锁,不阻塞其他脚本发交易 - 升级巡检健康心跳仅观测 — 每次进 upgradekamis(包括复查轮和各种重试)都把当前时间写进全局心跳表的「升级巡检」键。(
window.__kamiHealthBeats||= {})['升级巡检']=Date.now(),给代码健康看板判断「该跑没跑」。触发:每次调用 upgradekamis|命令showHealth()(在其他板块) - 入口防重入 — 普通锁已经被辅助脚本的 upgrade 操作拿着,说明已经有一个实例在跑,打「升级任务已在运行中,跳过」直接返回。判断
__txNormalLock.operation==='upgrade' 且 script==='helper'。触发:每次调用入口 - 钱包地址未就绪重试 — 钱包地址为空,或者还是个没解析出来的对象时,按未就绪处理,10 秒后重试,最多 6 次;一直不可用就跳过本次升级。
connectedAddress兼容 .value_ 和 .value 两种字段;typeof addr==='object' 也算没就绪。触发:阶段一取地址时|参数MAX_RETRY=6;间隔 10000ms|来历启动初期钱包地址可能还没准备好 - kami 列表为空或查询出错时重试 — 查到 0 只 kami,或者
getByOperator抛错,都等 10 秒重试,最多 6 次(大约 1 分钟)。最后还是失败或为空,就打日志跳过本次升级。0 只打「游戏数据可能未加载完成」;出错打错误信息;超过次数打「获取Kami列表失败」或「重试后仍无法获取 Kami 列表」。触发:阶段一取 kami 列表|参数MAX_RETRY=6;间隔 10 秒|来历启动初期游戏数据可能还没加载完 - 状态统计与无休息 kami 早退 — 打印总数、休息中、采集中各多少;有采集中的就提示「这些不能自动升级」;一只 RESTING 都没有就打「没有休息中的 Kami 可以升级」并返回。按列表里的 state 字段筛。触发:拿到 kami 列表后|来历采集中的 kami 链上不允许升级
- 断点续跑进度记录 — 用
window.__upgradeProcessed记住本轮已经处理过的 kami,续跑时跳过它们;待扫描的都处理过了,就清空进度并返回。注释写「每只处理完、不管有没有实际升级都记入」。代码实际只在单只流程没抛错时才记,抛错的 kami 下次续跑会重新处理。首轮完成、复查轮完成、全部已处理时都会把进度清成 null。触发:预扫描前筛选,每只处理成功后记入|存储window.__upgradeProcessed(内存 Set,跨调用共享)|来历被紧急锁打断后续跑,不重复扫描、不重复处理 - 只读预扫描判断要不要处理 — 逐只查等级、XP、技能点和投点,只要能升级、有未分配的点、或有非标准技能,任意一个满足就标成需要处理,查到的数据留作 prefetched 传给单只流程。
getByIndex(progress, skills),不带 harvest,省下每只多读 harvest 的开销;用computeLevelUps算 ups。触发:阶段一,对每只待扫描的 RESTING kami - 预扫描时给紧急锁让路 — 扫描过程中发现紧急锁,就调
waitForLocksRelease等锁释放(它会连普通锁一起等),等到超时就放弃整个本次升级。这时还没加锁,所以不用释放锁;超时打「等待锁释放超时,跳过升级」并返回。触发:预扫描每一只之前|参数最多等 10 分钟|来历紧急停采正在发 tx,升级必须让路 - 预扫描时保护杀手的专用加点 — 命中保护名单的 kami,预扫描时一律按「没有非标准技能」处理,专用加点不会被判成要重置。
_isProtectedKami为真时nonStandard直接给 []。但这只如果能升级或有可分配点,仍会进待处理列表,随后在单只流程里被保护名单整只跳过(每 30 分钟打一次跳过日志)。触发:预扫描每只|参数RESET_WHITELIST/window.MY_KILLER_KAMIS - 预扫描单只查询失败兜底 — 某只 kami 查询抛错时,记成 level=999、需要处理、prefetched=null:排在最后处理,但保证不漏。单只流程拿到 prefetched=null 时,会自己带 progress/skills/harvest 重新查一次。触发:预扫描单只异常|来历宁可多查一次,也不漏升
- 预扫描逐只间隔 200ms — 每查完一只 kami 停 200 毫秒再查下一只。
delay(200)。触发:预扫描循环|参数delay(200)|来历防止节点限流 - 按等级从低到高排队处理 — 只留下需要处理的 kami,按等级升序排;打印跳过了几只、待处理清单(#编号(Lv等级))。没有需要处理的就直接返回。
filter(needsUpgrade).sort(level 升序)。触发:预扫描完成后 - 阶段二前再查一次防重入 — 预扫描期间可能已经有别的 upgradekamis 实例开跑了,抢锁前再检查一次,已在运行就跳过。同入口逻辑,看
__txNormalLock是否为 helper/upgrade。触发:阶段二开始前 - 抢普通锁,失败先等锁释放 — 有紧急锁,或者抢普通锁失败时,先打出是紧急锁还是被哪个脚本/操作占着,再调
waitForLocksRelease等释放;等到超时就放弃本次。window.tryAcquireNormalLock?.('upgrade','helper')。代码实际:tryAcquireNormalLock这个函数由核心脚本提供,核心没加载时 !undefined 为真,会被当成抢锁失败,结果每 15 秒整轮重跑(含预扫描),永远拿不到锁、无法升级;和注释里「辅助脚本可独立运行」对不上。触发:阶段二开始 - 抢锁前随机错峰 3~8 秒 — 锁释放后不马上抢,先随机等 3~8 秒。
delay(3000+random*5000)。触发:等锁释放后、再次抢锁前|参数3000+Math.random()*5000 ms|来历多个等锁方错开时间,降低同时抢锁的概率 - 错峰期间又来紧急锁:5 秒后整轮重跑 — 随机等待期间又出现了紧急锁,就打日志,5 秒后重新调一次
upgradekamis()(会重新预扫描),本次返回。setTimeout(()=>upgradekamis(),5000)。触发:错峰等待后检测到紧急锁|参数5000ms - 普通锁仍被占:15 秒后整轮重跑 — 错峰后抢普通锁还是失败,就打日志,15 秒后重新调
upgradekamis(),本次返回。setTimeout(()=>upgradekamis(),15000)。触发:错峰后抢锁失败|参数15000ms - Respec 药水库存读取与本地扣减 — 开始处理前查一次 Respec 药水数量并打出来;每只用掉一瓶就在本地减 1,打「剩余 Respec Potion: n」,后面的 kami 按剩余数量判断还能不能重置。
getRespecPotionCount()查到后存成局部计数,单只流程返回usedPotion=true 时减 1。触发:阶段二拿到锁后|来历不用每只都重新查链上库存,也不会超额重置 - 每只处理前查紧急锁,发现就中断 — 逐只处理前都查一次紧急锁,有锁就立刻打「中断升级(已处理 i/n)」、置中断标记、跳出循环。
emergencyBreak=true;break。触发:逐只处理循环|来历紧急停采优先 - 单只出错只记日志、继续下一只 — 单只流程抛错(比如 API 不可用)只打「处理失败 #编号」,不中断其他 kami。逐只 try/catch。触发:单只处理异常
- 逐只间隔 2~3 秒随机抖动 — 每处理完一只 kami,随机等 2~3 秒再处理下一只。
delay(2000+random*1000)。触发:逐只处理循环|参数2000+Math.random()*1000 ms|来历让 tx 节奏更平滑 - 被紧急锁打断后 5 秒自动续跑 — 本轮被紧急锁中断时,打「等待锁释放后自动继续」,5 秒后重新调
upgradekamis(),靠进度记录跳过已处理的 kami。setTimeout5 秒后调用;finally 里先释放普通锁。续跑调用不带复查标记,续跑完成后还会再走一次复查轮。触发:逐只循环因紧急锁中断|参数5000ms|存储window.__upgradeProcessed - 首轮完成后复查一轮查漏 — 首轮全部处理完后清空进度、释放普通锁,等 2 秒让链上状态刷新,再以复查轮身份重跑一遍;复查轮跑完打「一键升级 + 技能分配 全部完成」,不再往下递归。return
upgradekamis(true);_isRecheck为 true 时结束就清空进度。触发:首轮没被中断、正常完成时|参数复查前delay(2000)|存储window.__upgradeProcessed|来历升级过程中可能又长出新的等级或技能点(比如喂了 XP 药水);复查只跑一次,避免无限循环 - finally 一定释放普通锁 — 不管是正常结束、中断还是抛错,最后都会释放普通锁;整个流程的异常只打「一键升级流程错误」。try/catch/finally 中调
window.releaseNormalLock?.('upgrade','helper')。触发:阶段二结束时|来历异常时也不会漏还锁,不会卡死其他脚本
技能体检、自动调度与命令
- 全量技能体检总表 — 只读地检查账户下所有 kami(不限状态)的技能分配,用表格列出:编号、名称(截前 15 字)、状态、等级、清算线、可用点、非标准技能数。不重置、不发任何交易,随时可以跑。
getByOperator取全部 kami → 逐只getByIndex(skills, progress)→detectNonStandardSkills→ 从kami_core_db补 LT → ctable 打表。先打出当前药水数量。返回 {results,needsReset,potionCount}。触发:手动命令|参数名称截断slice(0,15)|命令checkAllKamiSkills()|存储kami_core_db(读window.kami_core_db) - 非标准技能分三组报告 — 有非标准技能的 kami 分三组打印每只的 LT 和非标准技能:🛡️ 杀手白名单(不会被自动处理)、🔴 高清算线 LT>40%(会被重置)、🟡 低清算线 LT≤40%(暂不重置)。最后提示运行
upgradekamis()执行。用_isProtectedKami分出白名单,其余按RESPEC_LT_THRESHOLD分高低。注意「会被重置」这组里也包括采集中的 kami,而 upgradekamis 只处理 RESTING、还要先过冷却预筛,所以本轮不一定真会重置。触发:体检发现非标准技能时|参数RESPEC_LT_THRESHOLD=40|命令checkAllKamiSkills()|来历阈值和 upgradekamis 的重置逻辑保持一致;LT 高的清算风险大,药水先给它们用 - 药水够不够用提示 — 高清算线组有 kami 时,比较药水库存和这组的数量:够就提示可以全部重置;不够就提示只能重置 N 只。比较
potionCount>=highLT.length。触发:体检且高 LT 组非空时|命令checkAllKamiSkills() - 数据库没记录时 LT 按 0 — 本地精简数据库里没有这只的记录时,LT 按 0 算,归到「暂不重置」组。
dbRecord?.LT ?? 0。触发:体检每只|存储kami_core_db(读)|来历宁可漏报,也不误判成需要重置 - 体检查询节流与异常兜底 — 逐只查询间隔 100 毫秒;账户里没有 kami 就打「未找到任何Kami」;整体出错只打「检查失败」并返回空结果;全部标准时打「✅ 所有Kami的技能分配都是标准的」。
delay(100);外层 try/catch 返回 {results:[],needsReset:[]}。触发:体检过程中|参数delay(100)|命令checkAllKamiSkills()|来历防止节点限流;出错不影响页面 - 启动时探测游戏是否就绪 — 脚本一加载就自己开始探测:每 5 秒查一次四个条件(钱包地址已连接、升级 API 可用、单只查询可用、账户查询可用),四个都满足就打「游戏API就绪(N秒)」。第一次检测前也先等一个 5 秒周期,给页面注入留时间;地址兼容
value_和 value 两种字段。触发:自动:脚本加载后立即运行(IIFE)|参数POLL_MS=5000;MAX_WAIT=5 分钟|来历辅助脚本不依赖核心脚本的信号:核心是固定等 120 秒,这里按 API 实际就绪时间判断,通常能更早开始升级 - 就绪后立刻跑首轮升级 — 四个条件一满足就立即调
upgradekamis(),不用等 kami 数据全部加载完。upgradekamis 自带「kami 数据没加载就每 10 秒重试、最多 6 次」的兜底。触发:启动探测判定就绪时 - 每 30 分钟定时升级检查 — 首轮触发后建立一个 30 分钟的定时器,每次打「30分钟定时检查」并调
upgradekamis()。setInterval30*60*1000。触发:自动:启动就绪后每 30 分钟|参数30 分钟|来历调小会更频繁地跑只读预扫描(没有 tx 成本但有查询开销);调大则新攒下的等级和技能点处理得更晚 - 定时器碰到紧急锁直接跳过本次 — 30 分钟定时触发时如果有紧急锁,打「紧急锁存在,跳过本次30分钟定时检查」,不排队等,等下个周期再说。只查紧急锁;普通锁冲突交给 upgradekamis 内部处理。触发:每次 30 分钟定时触发|来历避免和紧急停采抢 nonce
- 5 分钟等不到就放弃自动触发 — 超过 5 分钟游戏还没就绪,就打「等待超时(5分钟),可手动运行
upgradekamis()」,不再重试。代码实际:超时时 30 分钟定时器也没建起来,本次页面会话里自动升级完全不会运行,只能手动跑或刷新页面。触发:启动探测超时|参数MAX_WAIT=5 分钟|命令upgradekamis()|来历5 分钟等不到多半是游戏没登录或加载失败,留给用户手动处理 - 挂载 upgradekamis 命令 — 把批量升级主流程挂到 window 上,控制台可以手动一键升级所有休息中的 kami(等级+技能)。
window.upgradekamis=upgradekamis;调用时不用传参(参数 true 是内部复查轮标记)。触发:脚本加载时挂载|命令upgradekamis() - 挂载
checkAllKamiSkills命令 — 把全量技能体检挂到 window,控制台随时可以只读体检。window.checkAllKamiSkills=checkAllKamiSkills。触发:脚本加载时挂载|命令checkAllKamiSkills() - 挂载
getRespecPotionCount命令 — 控制台查当前 Respec Potion 库存。函数定义在范围外:按钱包地址查账户背包,按RESPEC_POTION_INDEX找物品取 balance;没有背包或出错都返回 0,出错时打「获取 Respec Potion 数量失败」。触发:脚本加载时挂载|命令getRespecPotionCount() - 暴露
STANDARD_SKILLS标准加点配置 — 把标准技能 ID 集合挂到 window,方便在控制台查看和比对。window.STANDARD_SKILLS=STANDARD_SKILLS(Set)。触发:脚本加载时挂载|命令window.STANDARD_SKILLS
2.5 自动合成与步长读取
按配方优先级每 30 分钟合成一次;步长只认页面上的实时读数。
配置与配方
- 自动合成总开关 — 控制整个自动合成模块是否干活;关掉后定时器照常触发,但每轮只打一行「自动合成已禁用」就返回,手动
autoCraft()也同样被挡。autoCraftItems入口检查AUTO_CRAFT_CONFIG.enabled,false 即打日志 return。注释说 false 时「只保留手动autoCraft()」,但代码里手动调用走的也是同一函数,同样会被挡住。触发:每轮autoCraftItems入口|参数AUTO_CRAFT_CONFIG.enabled=true|命令window.AUTO_CRAFT_CONFIG.enabled=false(脚本末尾把配置对象挂到了 window,运行时可改,刷新后恢复)|来历方便 fork 用户整体关闭,不想自动消耗背包材料时用 - 步长触发阈值 80 — DOM 读到的账户步长 ≥80 才进入合成流程,没到就整轮跳过。stamina <
staminaThreshold时打「步长未达阈值(x<80),跳过合成」后 return。这一步在拿锁之前做。触发:每轮autoCraftItems检测阶段|参数staminaThreshold=80|命令window.AUTO_CRAFT_CONFIG.staminaThreshold=N|来历步长攒到接近满再动手,一次合成更多,摊到每点步长上的 gas 更低;调低会频繁小批多付 gas,调高可能顶满溢出浪费回复 - 定时检测间隔 30 分钟 — 自动合成的周期检测间隔。
startAutoCraft用setInterval(checkIntervalMs)周期调用。间隔从 start 时刻起算,所以第一个周期点落在启动后 30 分钟,也就是 5 分钟首检之后约 25 分钟。触发:startAutoCraft注册的定时器|参数checkIntervalMs=30*60*1000|来历步长回复速度固定,检测更勤意义有限;间隔太长又可能错过高步长窗口 - 保留最小步长
minStaminaKeep(预留未接线)已停用 — 本意是合成后留一部分步长不用,但当前版本主流程根本没读这个值。注释写明是预留配置,改了不影响行为。实际的收工线是主流程里写死的「剩余步长 < 10」。触发:无(不被读取)|参数minStaminaKeep=0 - 合成优先级表
CRAFT_PRIORITY(顺序即优先级 + 逐配方开关) — 用数组定义要自动合成的配方,从上到下尝试,排在前面的先消耗步长。每条配方有独立 enabled 开关,可以只停用其中一项。字段有recipeId(链上配方 ID)、name(只用于日志)、productId(产物 ID,用于库存统计)、stamina(单次步长消耗)、materials(材料清单)、enabled、maxCraft、可选的minCraft。主流程 for..of 顺序遍历,enabled=false 直接 continue。默认顺序是高阶成品在前、原料在后:Fortified XP 先于 Greater XP,Greater XP 又先于松花粉等原料。触发:被autoCraftItems锁内主循环读取|参数默认 6 条配方:#29 / #2 / #3 / #13 / #6 / #9|命令window.CRAFT_PRIORITY[i].enabled=false(脚本末尾暴露到 window,运行时可改)|来历fork 后应先确认配方符合自己需求,否则会持续消耗背包里的松果、薄荷等材料 - 配方:Fortified XP Potion(#29) — 用 Greater XP Potion、Powdered Red Amber、Essence of Thought 合成 Fortified XP 药水,优先级最高。单次消耗 75 步长,材料为 Greater XP Potion×1、Powdered Red Amber×300、Essence of Thought×1。步长上限 100,所以每轮最多合成 1 瓶。触发:自动合成主循环|参数
recipeId=29,productId=11411, stamina=75,maxCraft=10 - 配方:Greater XP Potion(#2,玻璃瓶循环容器 + 便携炉工具) — 用松花粉合成 Greater XP 药水。单次消耗 50 步长,材料为 Pine Pollen×2500、Glass Jar×1、Portable Burner(amount=0 属工具,只要求拥有、不消耗)。触发:自动合成主循环|参数
recipeId=2,productId=11402, stamina=50,maxCraft=10|来历Glass Jar 是循环容器:合成时每瓶占用 1 个,喂食后返还,不算净消耗 - 配方:Respec Potion(#3) — 用塑料瓶和薄荷碎合成技能重置药水。单次消耗 50 步长,材料为 Plastic Bottle×1、Shredded Mint×500。触发:自动合成主循环|参数
recipeId=3,productId=11403, stamina=50,maxCraft=10 - 配方:Powdered Red Amber(#13) — 把 Red Amber Crystal 磨成红琥珀粉,作为 Fortified XP 药水的原料。单次消耗 20 步长,材料为 Red Amber Crystal×1。
maxCraft虽设 50,但实际受步长约束:每轮最多 100/20=5 次。触发:自动合成主循环|参数recipeId=13,productId=1107, stamina=20,maxCraft=50 - 配方:Pine Pollen(#6,凑满 10 次才发) — 把松果合成松花粉,作为 Greater XP 药水的原料;可合成次数不足 10 次时本轮不发。链上实测配比是每次 1 松果 + 10 步长,产出 500 松花粉。
minCraft=10,要凑满一批需要 100 步长,所以实际上只有步长恰好 100、且前面的配方都没消耗步长时才会发。触发:自动合成主循环|参数recipeId=6,productId=1104, stamina=10,maxCraft=10,minCraft=10|来历拆小批等于同样的步长多笔 tx、多付固定 gas;松花粉是囤积材料,不赶时间 - 配方:Shredded Mint(#9) — 把薄荷切成薄荷碎,作为 Respec 药水的原料。单次消耗 20 步长,材料为 Mint×1。
maxCraft=50,实际受步长约束每轮最多 5 次。触发:自动合成主循环|参数recipeId=9,productId=1112, stamina=20,maxCraft=50 - 单笔合成次数上限
maxCraft— 每个配方一笔 tx 最多合成多少次。本笔数量 = min(材料上限, floor(剩余步长/单次消耗),maxCraft)。触发:主循环计算数量时|参数各配方maxCraft=10 或 50|来历防止单笔 TX 过大导致失败 - 运行时可改的合成配置与优先级 — 把合成配置对象和合成优先级清单暴露到 window,控制台可以在运行时直接修改。挂的是对象引用,改动立即影响合成模块。触发:命令|命令
AUTO_CRAFT_CONFIG;CRAFT_PRIORITY
DOM 步长读取
- 步长区域 DOM 锚定(#
MyPath→ svg → 前一个兄弟 div) — 在游戏页面里找到显示账户步长的区域。先querySelector('#MyPath'),再closest('svg'),再取previousElementSibling作为容器。任何一步拿不到都返回 null。触发:getStaminaFromDOM内部|来历选择器和游戏前端结构绑定,前端改版时需要同步更新 - 方式1:div[height] 属性取值 + 0~100 范围校验 — 优先从容器里带 height 属性的 div 读出步长数值。
parseInt(height),只有非NaN且落在 0~100 之间才采用,否则继续走方式2。触发:getStaminaFromDOM内部|参数合法范围 0~100|来历防止解析到无关属性 - 方式2:「xx/100」文本兜底 — 方式1失败时,遍历容器内所有 div,找文本形如「数字/100」的,取分子作为步长。
textContent.trim()用正则 /^\d+\/100$/ 全等匹配,取第一个命中的。触发:getStaminaFromDOM方式1未命中时 - 步长只认 DOM,读不到返回 null(禁止回退 API
stamina.sync) — 全套件(辅助+核心)唯一认可的步长来源。两种方式都失败时返回 null,调用方必须按「读取失败,本轮跳过」处理。函数纯读 DOM,没有副作用、不发 tx。页面没加载完或前端改版时安全返回 null。触发:autoCraftItems每轮检测、每笔合成成功后重读;核心脚本同样以它为准|命令getStaminaFromDOM()—— 控制台查看当前 DOM 实时步长|来历API 的stamina.sync是上次链上动作时的检查点值,不随时间回复:步长回满后 sync 还停在旧低值,误用会漏合成或发出必败 tx getStaminaFromDOM()读体力 — 从页面 DOM 读取当前体力值。挂到window.getStaminaFromDOM。触发:命令|命令getStaminaFromDOM()
合成流程
- 账户背包查询
getCraftAccountInfo— 读取当前操作员的链上账户,把背包 inventories 交给合成流程使用。连接地址兼容value_/ value 两种字段名,取不到返回 null;用explorer.accounts.getByOperator走本地 ECS 查询,零 gas。返回里也带 stamina 字段(取stamina.sync),但只为字段兼容保留,主流程绝不用它判步长。异常时打「获取账户信息失败」并返回 null。触发:autoCraftItems检测阶段 + 每笔合成成功后刷新|来历背包数据没有「随时间回复」的问题,API 读取是准确的;步长则必须走 DOM - 物品持有量查询
getItemBalance— 在背包数组里按物品 ID 查持有数量。inventories.find(item.index === itemId),查不到返回 0。触发:checkCraftMaterials调用 - 工具类材料判定(amount=0 只需拥有) — 配方里 amount=0 的材料视为工具:不随合成消耗,只要求持有 ≥1 个。缺工具时打「已停用 缺工具 名称,跳过 配方名」(测试版 1.2.15 前是「❌ 缺少工具」,会被看板计为带错),整个配方返回 0(不能合成)。触发:
checkCraftMaterials逐项核对|来历例如 Portable Burner 合成时不消耗 - 合成前补查配方表没写的工具测试线独有 —
CRAFT_PRIORITY里只有 Greater 写了便携炉,Fortified/Respec(要便携炉)和红琥珀粉/松花粉/薄荷碎(要研磨器)都没写。checkCraftMaterials开头按CRAFT_RECIPE_META补查这些工具(按编号、再按名字兜底),缺了就跳过该配方、不发合成 tx,打「已停用 [AutoCraft] 缺工具 X,跳过 Y(不发必败的合成 tx;工具在 Mina 商店有售)」(测试版 1.2.16 前写「补货见上方大字提醒」,大字提醒改为只管 Fortified 链后不再总成立)。以前缺工具照样发 tx,被链上拒绝后整轮中止,排在后面的配方也做不成。触发:每个配方检查材料前|来历0914 审查发现、用户要求直接改|版本测试版1.2.15 - 材料上限计算 + 材料不足日志 — 按库存算出材料最多支撑几次合成;任何一种材料够不上一次就判定不能合成。每种消耗类材料算 floor(库存/单次用量),为 0 时打「材料不足: 名称 (有x, 需y)」并返回 0;所有材料取最小值。如果配方只有工具、没有消耗类材料,结果是 Infinity,最终返回 0。材料不足只打日志、不报错,每轮都会打,没有去重。触发:主循环每个配方开工前|来历材料不足时静默等下一轮攒够再合成
- 发 tx 前让路紧急锁(最多等 300 秒) — 发合成 tx 前如果核心脚本正持有紧急锁(例如紧急停采),先等它释放;超时就放弃本次合成。
hasEmergencyLock()为真时调waitForEmergencyRelease('AutoCraft合成', 300000);返回 false 就打「等待紧急锁超时,放弃本次合成」并 return false,主流程会因此中止整轮。触发:doCraft每次调用|参数紧急锁等待上限 300000ms|来历不和核心的高优先级紧急操作争 nonce;调大不容易放弃,但会更久占住主流程 - 等待接口缺失时直接放弃 — 紧急锁存在、但
waitForEmergencyRelease不可用时,不冒险发 tx,直接放弃。打「紧急锁存在且waitForEmergencyRelease不可用,放弃本次合成」,return false。触发:doCraft检测到紧急锁时|来历宁可不合成,也不和紧急操作争 nonce - 发送合成 tx 并等待上链 — 调链上合成接口发一笔 tx(配方 × 数量),等上链确认后返回成功。调用
network.api.player.account.item.craft(recipeId, quantity)。返回对象有 wait 方法就 awaittx.wait();没有 wait 的环境视为已发出,直接返回 true。wait 抛异常(revert 等)会被 catch,打「合成失败」并返回 false。触发:autoCraftItems锁内主循环|来历返回 false 让主流程按「revert 即中止本轮」熔断 - 合成 tx 记入 gas 账本仅观测 — 合成 tx 发出后交给核心脚本的 gas 真值账本,按 craft 分类记账。如果
window.__kamiGasRecord存在,就调用 ('craft', [], tx)。外面包了 try/catch,记账失败不影响合成流程。触发:每笔合成 tx 发出后|来历gas 按动作分类统计,只能在发 tx 时打标签|版本1.2.4 gas挂钩(🔻SYNC→内部版) - 自动合成健康心跳仅观测 — 每次进入自动合成时写一次心跳时间戳,供健康看板判断「30 分钟合成定时器是否停摆」。
window.__kamiHealthBeats['自动合成']=Date.now()。写在函数第一行,所以即使模块被禁用、或本轮被跳过,也照样会写。触发:autoCraftItems每次调用|参数看板侧expectMin=45 分钟 - 轮前紧急锁让路(3 分钟后重试整轮) — 一进来发现紧急锁存在,就不做任何检测,3 分钟后再完整跑一轮。打「[TX锁] 紧急锁存在,延后3分钟再合成」,然后
setTimeout(autoCraftItems, 180000)。这个重试不去重,也不会被stopAutoCraft取消。触发:autoCraftItems入口|参数重试延迟 180000ms|来历避免与核心脚本的紧急停采冲突、争 nonce - 锁纪律:先查后锁 — 读 DOM 步长、读背包、判阈值这些纯读检测都不占锁,确认真有活要干才去拿普通锁。只有步骤 1~5 全部通过,才调
tryAcquireNormalLock('craft','helper')。触发:每轮autoCraftItems|来历「步长未达阈值」是最常见的轮次结果,一进来就拿锁会白白挡住核心脚本的喂食、复活等模块 - DOM 步长读取失败跳过整轮 — DOM 读不到步长时本轮不合成,也绝不回退用 API 步长。
getStaminaFromDOM()返回 null 时打「DOM 步长读取失败,本轮跳过合成(不用陈旧 API 步长)」并 return;读到了就打「DOM步长: x」。触发:每轮检测阶段|来历stamina.sync是链上检查点旧值、不随时间回复,用它会漏掉本该成功的合成,或者发出必败 tx - 背包读取失败跳过整轮 — 拿不到背包信息就本轮不合成。
getCraftAccountInfo()返回 null 时打「无法获取背包信息」并 return。触发:每轮检测阶段 - 普通锁被占让路(3 分钟后重试整轮) — 确认要发 tx 但普通锁被核心或其他模块占着,就 3 分钟后重跑整轮。
tryAcquireNormalLock('craft','helper')返回 false 时打「[TX锁] 普通锁被占用,延后3分钟再合成」,并setTimeout180000。如果锁接口不存在,可选链返回 undefined,同样会走让路分支。重试不去重,也不受stopAutoCraft取消。触发:检测通过、准备发 tx 时|参数重试延迟 180000ms|来历与核心脚本共享的 TX 双锁体系,避免并发发 tx - 每个配方开工前再查紧急锁 — 锁内循环里,每个配方开始前都检查一次紧急锁,存在就中断整轮让路。
hasEmergencyLock()为真时打「[TX锁] 检测到紧急锁,中断合成」并 break,然后走 finally 释放普通锁。触发:锁内主循环每个配方|来历杀手来袭等紧急停采优先,不与之争 nonce - 配方逐项跳过条件 — 配方被禁用、剩余步长不够一次消耗、或材料上限为 0,都直接跳过去看下一个配方。enabled=false 直接 continue;
availableStamina<recipe.stamina时静默 continue,不打日志;否则先打「检查 [配方名]...」再核对材料,材料上限为 0 时 continue(缺什么由材料检查函数打日志)。触发:锁内主循环 - 本笔数量三方取小 — 一笔合成的数量同时受材料、步长、单笔上限约束。quantity = min(材料上限, floor(剩余步长/单次消耗),
maxCraft),≤0 时 continue。触发:锁内主循环 minCraft凑批门槛 — 可合成数量不到配方设定的最小批量时,本轮不发,等材料或步长攒够一批再发。quantity < (recipe.minCraft|| 1) 时打「x 当前最多可合成xN< 凑批门槛xM,等下轮凑满再发(省 gas)」并 continue,继续尝试下一个配方。触发:锁内主循环|参数minCraft(目前仅 Pine Pollen=10)|来历tx 有固定 gas 成本,一笔大批量能摊薄固定成本,优于多笔小批量- 刻意不与 API
stamina.sync取 min — 发 tx 前的步长预算完全以 DOM 实时值为准,不拿 API 的检查点值去压低。finalQty直接等于 quantity。超扣风险交给成功后的「本地扣减 + DOM 下限」双保险兜住。触发:锁内主循环发 tx 前|来历sync 不含自然回复,步长回满后仍停在旧低值,拿它取 min 会误判步长不足、漏掉本该成功的合成 - 合成成功后刷新背包快照 — 每笔合成成功后重新读一次背包,让下一个配方按扣减后的材料计算。
totalCrafted累加本笔数量;getCraftAccountInfo()读到了就替换 inventories,读失败就沿用旧快照。触发:每笔doCraft成功后|来历合成后材料减少了,例如红琥珀粉被 Fortified 消耗 - 步长更新双保险(本地确定性扣减 + DOM 重读取低) — 连续合成时用「已知消耗的精确扣减」和「2 秒后重读的 DOM 值」两者中较低的作为剩余步长,杜绝超扣。expected = 剩余步长 − 本笔数量×单次消耗;
delay(2000)后重读 DOM,读到就取min(DOM, expected),读不到就用 expected。打「成功! 剩余步长: x(本地扣减→a / DOM→b,取低)」。触发:每笔合成成功后|参数DOM 刷新等待 2000ms|来历单用 DOM 重读有渲染滞后,读到旧高值会让下一笔超扣步长而 revert;API sync 又是陈旧值,所以取两者较低 - 合成失败立即熔断整轮 — 任何一笔合成失败,立刻中止本轮剩下的所有配方,等下个周期带着新鲜状态重试。
doCraft返回 false(包括等紧急锁超时)时打「合成失败(可能已上链 revert 扣 gas),中止本轮合成,下个周期重试」并 break。触发:doCraft返回 false|来历失败后步长、材料、nonce 任一状态都不可信,继续硬试下一配方只会再烧 gas - 剩余步长 <10 提前收工 — 一笔成功后剩余步长低于 10,本轮直接结束。打「步长不足10,停止合成」并 break。只在成功分支之后检查。触发:每笔合成成功后|参数收工线 10(写死)|来历所有配方单次消耗都 ≥10,剩下的做不了任何一笔
- 相邻两笔合成间隔 1 秒 — 两笔合成 tx 之间固定等 1 秒。循环尾 await
delay(1000)。触发:每笔合成之后|参数1000ms|来历避免发 tx 过快 - 本轮合成汇总日志 + finally 释放普通锁 — 每轮结束打一行汇总;不管中途是否出错,普通锁一定会释放。合成过的打「合成完成! 共合成N次, 剩余步长x」,没合成的打「本次无可合成物品」;finally 里调
releaseNormalLock('craft','helper')。触发:锁内主循环结束|来历防止异常导致锁泄漏,挡住核心脚本 - 控制台手动触发一轮合成
autoCraft()— 立即手动跑一轮完整的自动合成检测与合成。window.autoCraft就是autoCraftItems,走的守卫和锁完全一样。模块被禁用时同样不干活。触发:命令|命令autoCraft() autoCraft()手动合成一轮 — 在控制台手动执行一轮自动合成。window.autoCraft指向前文自动合成板块的autoCraftItems。触发:命令|命令autoCraft()
材料缺口大字提醒
- Fortified XP Potion 材料链缺口盘点(不看步长,每轮都查)测试线独有 — 每轮合成检测读完背包后、步长门槛判断前,检查 Fortified XP Potion 能不能做一批;做不了就往下逐层细分:缺 Greater XP Potion → 查它的松花粉、玻璃罐、便携炉;缺松花粉 → 查松果、研磨器;缺红琥珀粉 → 查红琥珀晶体、研磨器。某材料库存够就不往下查(Greater 有库存时不管松果)。缺中间产物时按单次产出量折算合成次数(不少于该配方
minCraft,松花粉一次至少 10 次),最多 4 层、防环;查到底缺的记为补货项,同一项取最大需求,工具只要求有 ≥1。步长不够不算缺货。Respec、薄荷碎等其它配方缺货不提醒(照常自动合成)。原流程只有步长 ≥80 才查材料(0914 日志 106 次检测里 90 次没查)。触发:autoCraftItems每轮|函数:computeCraftSupplyShortage/reportCraftSupplyShortage|来历用户 0914 要求缺材料时大字提醒;0915 要求只提醒合成 Fortified 所需物品、往下细分|版本测试版1.2.14;测试版1.2.16 改为只看 Fortified 链 - 提醒目标与停用配方
CRAFT_ALERT_ROOTS/CRAFT_ALSO_BY_CORE测试线独有 —CRAFT_ALERT_ROOTS=[29] 决定提醒只看哪些配方,要加别的链就往里加 recipeId;目标配方被停用时不提醒,手动showCraftSupply()打一行 ℹ️。链上中间配方被停用时:核心也会合成的(CRAFT_ALSO_BY_CORE:Greater 2、松花粉 6,核心 1.2.58 只合成这两样)照常往下查,并注明「辅助已停用此配方,只靠核心合成」;其它(如红琥珀粉 13,核心不合成)没有脚本会做,这一格标 ⛔「配方已停用,没有脚本会合成」,Fortified 算做不了,标题点名让用户把 enabled 改回 true(这项不算补货,不进补货清单,也不受静音影响)。核心增删合成要同步CRAFT_ALSO_BY_CORE|参数CRAFT_ALERT_ROOTS=[29];CRAFT_ALSO_BY_CORE={2,6}|来历0915 审查——初版下钻不看 enabled,停用红琥珀粉时显示「材料够」而 Fortified 永远做不出来|版本测试版1.2.16 - 红底大字补货提醒 + 材料链树测试线独有 — 做不了时打一行红底白字 18px 标题「🛒 [合成材料] Fortified XP Potion 做不了——请补:X N 个、…」(有停用配方时追加「Y 配方已停用(没有脚本会合成),把 CRAFT_PRIORITY 里它的 enabled 改回 true」),再打一段橙色材料链树:根节点「Fortified XP Potion(按合成 1 次核对)」,每层 ✅ 够 / → 要先合成几次(起批不足注明「一次至少合成 10 次,凑不满不合成」)/ 🛒 缺几个 + 去哪补 / ⛔ 配方已停用,用 ├─ └─ │ 缩进。同样的缺货 10 分钟内只打一行「缺货同上」;缺货全部静音且没有停用配方时只打一行 🔕;材料够时打「Fortified XP Potion 材料够做一批」。所有输出带
[合成材料]标签,健康看板整条跳过。触发:盘点有结果时|参数去重窗口 10 分钟|版本测试版1.2.14;测试版1.2.16 改为材料链树 - 补货去处提示测试线独有 — 研磨器、便携炉:Mina 商店有售(买一次一直用);Glass Jar:商店不卖,市场购买或拾荒,喂 Greater/Fortified XP 药水也会返还空瓶;松果、红琥珀晶体、Essence of Thought:商店不卖,市场购买或拾荒。Plastic Bottle 的提示保留,默认只提醒 Fortified 链时用不到。依据游戏 listings/items/recipes 表|参数
CRAFT_SUPPLY_HINT|版本测试版1.2.14 - 配方产出量与工具补充表
CRAFT_RECIPE_META测试线独有 — 补CRAFT_PRIORITY里没写的单次产出量与工具:Fortified/Greater/Respec 每次产 1、工具便携炉;红琥珀粉/松花粉/薄荷碎每次产 500、工具研磨器。与游戏配方表一致(松花粉链上实测)。既用来算缺口,也是checkCraftMaterials的工具门禁(测试版 1.2.15 起),改 tool 字段会直接影响能不能合成。改配方要与核心RECIPE_DEFS、辅助CRAFT_PRIORITY同步|版本测试版1.2.14 showCraftSupply()手动盘点测试线独有 — 立即读背包盘点一次:连已静音的项一起列出(标「已静音」)、不受 10 分钟去重限制、材料够也打整棵材料链树;提醒目标被停用时打一行 ℹ️。读不到背包打「读不到背包,稍后再试」。命令:showCraftSupply()|版本测试版1.2.14muteCraftAlert/unmuteCraftAlert静音补货提醒测试线独有 — 某项不打算补就静音它:按物品名(不分大小写)或物品编号,存 localStorage,刷新后仍有效;静音项不进标题,树里标「已静音」。unmuteCraftAlert()取消全部,unmuteCraftAlert('Red Amber Crystal')取消一项。不想要 Fortified 的提醒,就把CRAFT_PRIORITY里 Fortified 的enabled改成 false(它也就不再自动合成)。命令:muteCraftAlert('Red Amber Crystal')/unmuteCraftAlert()|存储kami_craft_alert_mute|版本测试版1.2.14
定时任务
- 启动自动合成(5 分钟首检 + 30 分钟周期) — 注册自动合成调度:启动 5 分钟后做第一次检测,之后按周期检测。脚本加载时会自动调用一次。打「自动合成模块启动,5分钟后首次检测」;
setTimeout5 分钟后调autoCraftItems;setInterval(checkIntervalMs)周期调用。触发:脚本加载时自动调用(挂载段末尾startAutoCraft());也可控制台手动调用|参数首检延迟 5*60*1000ms;周期checkIntervalMs=30 分钟|命令startAutoCraft()—— 启动/重启周期任务|来历首检等页面加载完、数据稳定,也避开启动窗口复活等启动期任务;调短可能在数据未稳时误判,调长会浪费启动后第一段高步长窗口 - 重复启动清旧定时器(部分幂等) — 再次调用
startAutoCraft时先清掉旧的周期定时器,防止出现多个并行周期。autoCraftTimer存在就clearInterval。注释写「重复调用幂等」,但代码只清了setInterval:5 分钟首检的setTimeout没被跟踪,所以每调一次 start 都会多排一次首检。触发:startAutoCraft调用 - 定时器 skip 模式(紧急锁时跳过本次) — 周期定时器触发时如果紧急锁存在,本次整次跳过,不排队、不补跑,直接等下个周期。
setInterval回调里hasEmergencyLock()为真时打「紧急锁存在,跳过本次定时合成」并 return。和autoCraftItems内部「3 分钟后重试」不同,这一层直接放弃本次。5 分钟首检那次没有这层判断。触发:每 30 分钟定时器触发|来历避免与核心脚本的紧急停采争 nonce - 停止自动合成
stopAutoCraft— 停止自动合成的周期任务,仅本次会话有效,刷新后恢复自动启动。clearInterval并把autoCraftTimer置 null,打「自动合成模块已停止」。代码实际上不会取消已排好的 5 分钟首检setTimeout,也不会取消锁忙时的 3 分钟重试setTimeout,这些待执行的轮次仍会跑。想彻底停,要配合AUTO_CRAFT_CONFIG.enabled=false。触发:命令|命令stopAutoCraft() startAutoCraft()/stopAutoCraft()— 在控制台启动或停止自动合成模块。挂到 window 上,实现在前文自动合成板块。触发:命令|命令startAutoCraft();stopAutoCraft()- 加载即自动启动合成 — 脚本加载到末尾时自动调用
startAutoCraft(),启动合成模块。触发:脚本加载时一次
2.6 启动窗口复活
刷新后核心脚本要等约 2 分钟才就绪,辅助脚本在这段空档里抢先复活死亡 kami。
- 启动空档抢先复活 — 网页刷新后,核心脚本主流程就绪前有约 120~150 秒空档。辅助脚本加载快,一进游戏就先批量复活死亡的 kami,让它们尽早恢复采集。完成一次有效检查就停(「无死亡」、「丝带为 0」、「全在冷却内」都算完成),之后交给核心死亡监控(3 分钟一轮)和主循环接管。触发:脚本加载后自动轮询|来历核心要等启动流程走完才跑主循环,而且主循环里停采和部署的优先级高于复活
- 游戏未就绪自动重试 — 游戏接口还没准备好时返回 false,交给下一次轮询重试。连接地址取不到、
getByOperator不是函数、账户查询抛异常、kamis 不是数组或为空,都 return false。注意:一只 kami 都没有的账户也会一直重试,直到 8 次上限。触发:helperReviveOnce开头 - 筛出死亡 kami,无死亡即完成 — 从账户 kami 名单里筛出状态为 DEAD 的;一只都没有就打日志并结束轮询。state 统一转大写后比较 'DEAD';为空时打「启动检查:无死亡 kami」并 return true。触发:
helperReviveOnce - 复活丝带不足熔断 + 红字告警 — 有 kami 死了但背包里没有复活丝带时,大号红字提示去 Mina 商店购买,不发任何 tx。在背包里找物品 #11001 的 balance,≤0 时用 %c 红色粗体 14px 打告警,并 return true(算完成)。触发:有死亡 kami 时|参数
HELPER_REVIVE_RIBBON=11001(Red Ribbon Gummy)|来历返回 true 停止轮询,避免每 20 秒重复轰炸告警 - 跨脚本共享防重发表
__reviveSentAt(15 分钟) — 辅助复活、核心死亡监控、核心主循环三条路径共用一张「kamiId→ 发送时间」表,同一只 kami 15 分钟内只发一次复活 tx。window.__reviveSentAt不存在就初始化为 Map;建待复活名单时,距上次登记 ≤15 分钟的跳过。发 tx 前先登记。只有发送当场抛异常才撤销登记;revert、确认超时都保持登记。触发:helperReviveOnce构建待复活名单和发送时|参数HELPER_REVIVE_COOLDOWN_MS=15*60*1000(必须与核心一致)|存储window.__reviveSentAt(内存 Map,跨脚本全局变量)|来历与核心值不一致会导致三路防重发互认失效;确认超时仍保持登记,是为了防止慢链场景重发 kamiId三级解析 — 为每只死亡 kami 找到发 tx 需要的kamiId,找不到就跳过这只。优先从window.kami_core_db按 index 找kamiId,其次用账户数据里的k.id,最后调explorer.kamis.getByIndex(index)兜底(异常静默)。三者都拿不到就 continue。触发:构建待复活名单- 按丝带库存与单轮上限截断待复活名单 — 一轮最多复活 min(丝带库存, 30) 只,多出来的留给下轮。每加入一只就判断
todo.length≥min(ribbons, 30),满了 break。触发:构建待复活名单|参数HELPER_REVIVE_MAX_PER_ROUND=30(与核心REVIVE_MAX_PER_ROUND同值)|来历一次性复活几百只会独占 TX 锁十几分钟,期间喂食、停采、部署全部排队(0815 实盘 1105 只死亡雪崩教训)|版本1.2.6 复活单轮限流(🔻SYNC;同核心 1.2.21) - 单轮限流说明日志(三行)仅观测 — 触发单轮上限时,向用户解释为什么只救一部分、剩下的谁来接手。条件是死亡数 > 名单数且名单数 ≥30,打蓝字「死亡 N 只 → 本轮先救 30 只」、「为什么限流…」、绿字「剩余约 M 只:核心死亡监控会自动接手…」。只有被 30 上限截断时才打,被丝带库存截断时不打;剩余数里还包含冷却中和解析不到
kamiId的。触发:名单被单轮上限截断时|来历让用户放心:丝带不浪费、不重复发 tx、无需人工干预|版本1.2.6 复活单轮限流(🔻SYNC→内部版) - 全部在冷却内时不重发 — 死亡 kami 都已被其他路径在 15 分钟内发过复活时,只打日志,不发 tx,并结束轮询。todo 为空时打「N 只死亡均在 15 分钟复活冷却内(其他触发路径已处理),不重发」并 return true。代码实际上
kamiId全部解析失败也会走这条,打出同样的文案。触发:待复活名单为空 - 轮前紧急锁让路 — 准备复活时紧急锁存在,本次整体让路,下次轮询再试。打「[TX锁] 紧急锁存在,辅助复活让路(稍后核心接管)」并 return false。触发:发复活 tx 前
- 普通锁 revive 让路 / 锁体系未就绪时直发 — 拿不到与核心共享的 revive 普通锁就整体让路;核心锁接口还不存在时不拿锁直接发。
tryAcquireNormalLock是函数时申请 ('revive','helper'):失败打「普通锁被占用,辅助复活让路」并 return false;成功则 locked=true。接口不存在就跳过拿锁。finally 里只有 locked 为真才释放。触发:发复活 tx 前|来历拿不到锁说明核心正在发 tx,稍后由核心接管;启动空档核心不发 tx,所以不拿锁直发也安全 - 批量复活开始日志仅观测 — 开始发复活 tx 前,用红字列出本轮要复活的全部 kami 编号和丝带库存。打「启动窗口批量复活 N 只:#a, #b…(丝带库存 x)」,红色粗体。触发:拿锁成功后
- 每笔复活前查紧急锁,杀手来袭立即中断 — 逐只复活时,每发一笔前都检查紧急锁;发现紧急停采就马上停下让路,剩下的下轮自动复活。
hasEmergencyLock()为真时用橙色粗体打「检测到紧急停采(杀手来袭),立即让路——本轮已复活 x 只,剩余 y 只延后…」并 break。计数__hRevDone在发送前就加 1,所以「已复活」数里也包含发送失败的。触发:锁内逐只复活循环|来历旧码只在循环开始前查一次,215 只的批量复活把紧急停采干等了 353 秒(0815 实盘 1105 只死亡雪崩根因)|版本1.2.5 复活让路紧急停采(🔻SYNC;同核心 1.2.20) - 复活/冷却观察日志(发送前)仅观测 — 发复活 tx 前读一次该 kami 的
harvest.time.last,记录距上次动作多少秒、是否还在 180 秒冷却窗内,供事后统计复活是否受冷却影响。explorer.kamis.getByIndex(index,{harvest:true})走本地只读查询,经_obsCooldownAge算出 age 和 remain(180−age)。打「[复活/冷却观察] #x DEAD age=Ns remain=Ms(⚠️冷却窗内/已过冷却)→ 发送复活tx前」。读取失败静默跳过,不影响复活。触发:每笔复活 tx 发送前|参数冷却窗 T=180s(_obsCooldownAge内写死)|来历升级/复活是否受 cooldown 待实盘定论;事后 grep「🔬 [」按 age<180 与 ≥180 两组统计成败率|版本1.1.17 升级/复活冷却观察日志(🔻SYNC→内部版) - 发送复活 tx(使用 Red Ribbon Gummy) — 对每只死亡 kami 发一笔使用复活丝带的 tx。发送前先登记
__reviveSentAt,再调network.api.player.pet.item.use(kamiId, 11001)。触发:锁内逐只复活循环 - 复活 tx 记入 gas 账本仅观测 — 复活 tx 按 revive 分类、带上
kamiId记入 gas 真值账本。window.__kamiGasRecord存在时调用 ('revive', [kamiId], tx),外面包 try/catch,不影响流程。触发:每笔复活 tx 发出后|版本1.2.4 gas挂钩(🔻SYNC→内部版) - 45 秒确认超时赛跑(成功/超时/revert 三态) — 等复活 tx 上链确认,最多等 45 秒,并分别报告成功、超时、失败。tx 有 wait 时,
Promise.race让tx.wait()和 45 秒计时赛跑:ok 打绿字「复活成功」;timeout 打「tx已发出但确认超时(慢链),15分钟内不重发」;revert 打红字「tx执行失败(revert)」。没有 wait 方法则打「复活tx已发出」。revert 和超时都不撤销防重发登记。触发:每笔复活 tx 发出后|参数确认超时 45000ms|来历慢链时不无限等待;已登记防重发,所以超时也不会重复发 - 复活/冷却观察日志(结果)仅观测 — 把复活结果和发送前记录的冷却 age 对上,方便统计冷却窗内复活的成败率。发送成功分支打「#x DEAD age=Ns → 复活结果=成功/超时未知/失败/已发出(无wait,成败未知)」;发送异常分支打「复活结果=失败(tx未成功发出)」。只有发送前那条观察读到了数据才打。触发:每笔复活 tx 结束后|版本1.1.17 升级/复活冷却观察日志(🔻SYNC→内部版)
- 发送异常撤销登记,单只失败不影响后续 — 复活 tx 当场发不出去(接口抛异常)时,撤销防重发登记,让其他路径可以重试,然后继续处理下一只。catch 里
__reviveSentAt.delete(kamiId),打红字「#x 发送失败: 原因」,不 break。触发:use()抛异常时|存储window.__reviveSentAt|来历tx 根本没发出,保持登记会让这只 kami 白等 15 分钟 - 相邻两笔复活间隔 1.2 秒 — 两笔复活 tx 之间固定等 1.2 秒。循环尾 await
delay(1200)。成功、失败都会等。触发:每笔复活之后|参数1200ms - 启动轮询
bootHelperRevive(20 秒 × 最多 8 次) — 脚本加载后自动每 20 秒调一次helperReviveOnce,覆盖约 160 秒的启动窗口;完成一次有效检查或满 8 次后自动停止。立即执行函数里setInterval20 秒,tries++;helperReviveOnce返回 true 或 tries ≥8 时clearInterval。异常 catch 后打「[辅助复活] 异常: …」。回调是 async,setInterval不会等上一轮跑完,上一轮没结束时下一次 tick 会并发进入,靠普通锁(拿不到即让路)和__reviveSentAt挡重复。触发:脚本加载自动执行(一次性启动任务)|参数间隔 20*1000ms,上限 8 次|来历之后复活完全交给核心死亡监控(3 分钟一轮)和主循环两条路径
2.7 地块适配分析
kamiAnalyze:按 body / hand 把 kami 分成四类地形,找出主流类和少数派,给出转移清单。
- 地形属性枚举
TERRAIN_TYPES— 定义游戏内全部四种地形属性,也是分类顺序和 majority 判定时平票的先后顺序。['normal','eerie','scrap','insect']。触发:被分类与打印函数遍历|参数TERRAIN_TYPES=['normal','eerie','scrap','insect']|来历游戏新增地形时需同步扩充 - 房间地形属性查询
getRoomAffinity— 查指定房间的 affinity 地形属性,返回小写数组。explorer.nodes.all()里找roomIndex相同的节点,affinity 是数组就逐项toLowerCase;没有就返回 null。触发:命令 / 内部复用|命令getRoomAffinity(roomIndex) - 房间名查询
getRoomName— 查指定房间的名称。nodes.all()里按roomIndex找节点,返回 name,没有就返回 null。触发:命令 /getCurrentRoomInfo调用|命令getRoomName(roomIndex) - 当前所在房间信息
getCurrentRoomInfo— 打包返回账户当前所在房间的编号、名称、地形属性。钱包地址value_/value 双兜底,getByOperator取roomIndex,读不到返回 null;名称缺失时回退为「Room N」。代码实际上没有 awaitgetByOperator,而同文件其他地方都 await 了它;如果该接口返回 Promise,这里的roomIndex会恒读不到,函数恒返回 null(未实测)。触发:命令|命令getCurrentRoomInfo() - 单地块适配判定
isKamiSuitable— 判断一只 kami(body+hand)是否适合在某种地形采集。三个参数都转小写,body/hand 为空按空串处理。normal 地块要求 body 和 hand 都是 normal;其他地块只要 body 等于该地形,或 body=normal 且 hand 等于该地形。terrain 本身没有空值保护,传 null 会抛错。触发:命令 /isKamiSuitableForRoom调用|命令isKamiSuitable(body, hand, terrain)|来历NORMAL 地块要求「纯 normal」个体 - 多属性房间适配判定
isKamiSuitableForRoom— 判断 kami 是否适合某个房间,支持双属性房间(如 eerie+scrap)。affinity 为 null 或空数组时视为不限地形,返回 true;否则只要任一地形匹配就适合(some 语义)。触发:命令|命令isKamiSuitableForRoom(body, hand, affinities) - kami 归类规则
getKamiSuitableTerrain(body 优先于 hand) — 给出一只 kami 最适合的地块类型:body 不是 normal 就归 body 类;body 是 normal 就归 hand 类;两者都是 normal 就归 NORMAL。例如 scrap/eerie 归 SCRAP,normal/scrap 也归 SCRAP。没有挂到 window,只在内部用。触发:classifyKamisByAffinity内部调用|来历归为 X 类的 kami 在 X 地块采集收益最优,多账户分工时应把同类集中到主营该地块的账户 - 以实时持有列表为基准(剔除幽灵 kami) — 分类只遍历账户当前真实持有的 kami,已经转走、但 db 里还留着记录的不会混进结果。钱包地址取不到直接抛「无法获取钱包地址」;
getByOperator取account.kamis作为唯一基准,这是全流程唯一一次链上 API 调用。total 等于实时持有数,包含 db 缺失的那些,所以四类百分比加起来可能不到 100%。触发:classifyKamisByAffinity|来历db 里有记录但已转走的「幽灵」kami 不能出现在转移清单里 - db-first 零 API 读属性 — body/hand/level/LT/name/
kamiId这些字段都从精简数据库kami_core_db读,不逐只查链。kami_core_db不是数组时降级为空数组;建成 index→记录 的 Map,O(1) 查找。触发:classifyKamisByAffinity|存储window.kami_core_db(跨脚本全局只读)|来历body/hand 是永不变化的静态属性,分类可以 0 API 瞬时完成 - 数据库缺失检测
missingInDb— 找出「账户实时持有、但 db 里没有」的 kami,不计入分类,单独列出提醒补库。筛出不在dbByIndex里的,记录 index/name/state,分类循环里遇到直接 continue,不中断整体分析。触发:classifyKamisByAffinity|来历提示用户运行核心脚本的syncKamiDb()增量补库 - 杀手 kami 保护(不列入转移、不被停采) — 命中核心脚本杀手清单的 kami,即使属于少数派,也会从 minority 里剔除,放进
excludedKillers。window.MY_KILLER_KAMIS是 Set 时才用它;核心脚本没加载、不是 Set 时降级为空集,分类照常进行,只是不做剔除。触发:classifyKamisByAffinity拼 minority 时|存储window.MY_KILLER_KAMIS(核心脚本维护的全局 Set)|来历杀手 kami 不能因为转移规划被停采 - 脏数据防御:归类结果不在四类之内跳过 — db 里 body/hand 是异常值、归不进四类的 kami 直接跳过,不报错。!
byType[terrain] 时 continue。归入的每只 kami 记录 index/kamiId(db 优先,回退k.id)/name/level/body/hand/LT/state(大写)/terrain。触发:classifyKamisByAffinity - 主流类 majority 判定 — 四类中数量最多的那一类定为 majority,不要求过半。按
TERRAIN_TYPES顺序用严格大于比较;数量平票时先出现的类胜出,顺序为 normal > eerie > scrap > insect。四类全空时 majority=null。触发:classifyKamisByAffinity - 少数派 minority 合集 — majority 之外三类的全部 kami(已剔除杀手),作为跨账户转移和停采的完整清单。遍历非 majority 的三类,杀手进
excludedKillers,其余进minorityKamis。返回 {total,byType, majority,majorityCount,minorityKamis,excludedKillers,missingInDb}。触发:classifyKamisByAffinity|命令__classifyKamisByAffinity()—— 返回原始分类数据(调试用)|来历为多账户「同类集中」的转移规划提供完整清单;分类与「当前站在哪个地块」完全解耦 - 控制台地块适配报告
kamiAnalyze()— 打印全账户 kami 的四类分布、主流类、少数派清单和转移建议。纯只读,不发 tx、不占锁、不写存储,随时可以安全执行。analyzeKamiTerrainFit内部 try/catch 调分类函数,失败只打一行「[kamiAnalyze] 分析失败: 原因」就返回。analyzeKamiTerrainFit和kamiAnalyze是同一函数的两个别名。触发:命令|命令kamiAnalyze()/analyzeKamiTerrainFit() - 报告头部统计仅观测 — 报告开头显示账户实时持有数和数据库条数。打「Kami 类型分布(按 body+hand 属性)」标题,以及「账户实时持有 N 只,数据库 M 条」。触发:
kamiAnalyze() - 数据库缺失提醒(前 5 只 +
syncKamiDb建议)仅观测 — 有 kami 不在数据库里时,用橙字提醒并建议先补库。最多列前 5 个 index,超出用「...」收尾;第二行打「建议先运行:syncKamiDb()(增量补全,几秒钟)」。触发:kamiAnalyze()且missingInDb非空|参数最多列 5 只|命令syncKamiDb()(核心脚本提供)|来历避免刷屏 - 分类规则速览仅观测 — 在报告里用 9 行文字说明 body/hand 的归类规则,并附例子。整段用 \n 拼接后一次 log 输出。触发:
kamiAnalyze()|来历让用户一眼明白为什么 scrap/scrap 和 normal/scrap 都归 SCRAP 类 - 四类分布四色清单(全量 index、每行 25 个)仅观测 — 逐类打印数量、占比和全部 kami 编号,不截断;少数派三类用专属颜色显示。标题格式「🏷️ X 类 (N 只, p%)」。majority 用绿色粗体加「← MAJORITY」,编号列表不上色;其他三类标题和列表都用专属色:normal 灰 #9e9e9e / eerie 紫 #c678dd / scrap 橙 #e5a13a / insect 绿 #4ec94e。编号每行 25 个,用 \n 拼成一次 log。触发:
kamiAnalyze()|参数PER_LINE=25;TERRAIN_COLORS四色|来历与核心脚本可转移清单同色系,转移时一眼区分不易看混;majority 不参与转移所以不上色;一次 log 避免时间戳重复;全量列出方便照清单去游戏内逐个查找 - 无法确定主流类时红字告警仅观测 — 四类都为空、定不出 majority 时,红字提示并结束报告。打「无法确定 majority(4 类都为空?)」和分隔线后 return。触发:
kamiAnalyze()且 majority=null - 少数派状态概览与转移指引仅观测 — 按状态统计少数派数量,并告诉用户每种状态该怎么处理。绿色大号字打「主流类型 = X(N 只)」;青字打「少数派合计 N 只(已排除 M 只杀手保护)」;RESTING 数量提示「可直接转移」;HARVESTING 数量用橙字提示「需要先停采 → 调用
stopMinorityForTransfer()」;其他状态(DEAD、升级中等)有的话用灰字提示「跳过」。触发:kamiAnalyze()|命令stopMinorityForTransfer()(核心脚本提供) - 少数派明细(按目标地块分组、带状态、每行 20 个)仅观测 — 按目标地块分组列出全部少数派的编号和状态,方便分批转移,可直接复制到游戏内查找。跳过 majority 类;每类再用
MY_KILLER_KAMIS剔除杀手(与minorityKamis口径一致),空类跳过;标题「→ N 只 → X 地块账户:」,下面每行 20 个「#index(状态)」。触发:kamiAnalyze()且少数派非空|参数PER_LINE=20(单项约 20 字符,一行约 400 字符)|来历保证控制台可读 - 杀手保护跳过清单仅观测 — 列出因杀手保护被跳过的 kami 编号。
excludedKillers非空时用金色 #d4a017 打「已保护跳过 N 只杀手 kami(即使是 minority 也不动): #a, #b」,最后打结束分隔线。触发:kamiAnalyze() - 少数派数据接口
__getMinorityKamis(跨脚本) — 给核心脚本的stopMinorityForTransfer()用的数据接口:核心据此筛出 HARVESTING 的少数派批量停采,为跨账户转移做准备。直接返回classifyKamisByAffinity()的完整结果,挂在window.__getMinorityKamis上。触发:被核心脚本stopMinorityForTransfer()调用|命令__getMinorityKamis()|存储window.__getMinorityKamis(跨脚本全局函数) - 属性分类模块加载提示仅观测 — 模块加载时在控制台提示可用命令。打三行:「Kami 属性分类模块已加载(不依赖地块、从 db 读、瞬时完成)」、「分析 4 类分布 →
kamiAnalyze()」、「一键停采 minority 中正在采集的 kami →stopMinorityForTransfer()[核心脚本提供]」。触发:脚本加载
2.8 代码健康看板
showHealth:用"期望表"对照实际运行,回答"该发生的发生了没有、发生得对不对"。每 30 分钟自检一次,有问题自动打出看板。
看板结构与心跳层
- 代码健康看板 v3 四层结构仅观测 — 用「期望表」对照实际运行,回答日志的盲区:长时间静默到底是没活可干,还是模块卡死。四层检测:心跳层(该跑没跑)、事件层(跑出坏事)、闭环层(开了头没收尾)、状态层(数据不对)。按《代码健康监控指标目录》覆盖约 140 项。零新增链上查询。数据源:
__kamiHealthBeats埋点、__kamiLogBuffer最近 6000 行日志反查、window/localStorage/DOM 直读、performance.now()页面运行时长。🔴=资产或整条链路级,🟡=关注级,⚪=条件型,永不标红。触发:自动每 30 分钟 + 命令|参数日志回看 6000 行;统计窗口 60 分钟|命令showHealth()|来历日志只记录「发生了什么」,静默是它的盲区|版本v3 - 心跳判定 + 刷新宽限期仅观测 — 周期性模块静默超过期望间隔就判红,否则判绿。beat 型优先读
__kamiHealthBeats埋点时间,没有再用日志最后命中时间(取自全部 6000 行,不限 60 分钟窗口)。静默时长 = min(距上次, 页面运行时长),所以刚刷新的页面在运行时长超过期望间隔前不会判红。触发:每次看板汇总|来历防止刷新页面后误报|版本v3 - beat 型条目补日志正则仅观测 — 埋点型心跳条目也配上日志正则,让它们的错误日志计入执行结果层的带错统计。触发:每次看板汇总|来历修 v2 的盲区:埋点型模块报错看板统计不到|版本v3
- 硬心跳:核心主循环仅观测 — 读核心埋点「主循环」,超过 25 分钟没跳判红,提示「核心
runAutomation停摆:部署/停采/喂食全部失效」。执行统计正则覆盖 [部署池] / [扫描统计] / [stop 分流] / [DOM预检] / Kami 处理异常。触发:每次看板汇总|参数expectMin=25 分钟|版本v3 - 硬心跳:账户信息轮仅观测 — 日志里 30 分钟没出现「🪶 账户信息」或「获取账户/地块信息失败」就判红,提示主循环在轮内早退(心跳在跳但没跑完整轮)。sig 型,靠日志特征反查。触发:每次看板汇总|参数
expectMin=30 分钟|版本v3 - 硬心跳:死亡监控仅观测 — 日志里 12 分钟没出现 [死亡监控] 就判红,提示「死亡无人发现 → 不会自动复活」。sig 型。触发:每次看板汇总|参数
expectMin=12 分钟|版本v3 - 硬心跳:杀手位置轮询仅观测 — 日志里 10 分钟没出现「⏱️ 下次检测」「🔍 我的位置」[同房间警报] [邻居预警] 就判红,提示杀手逼近无人预警(监控脚本没运行,或没升级到 ≥1.1.7)。sig 型。触发:每次看板汇总|参数
expectMin=10 分钟|版本v3 - 硬心跳:LT 显示刷新仅观测 — 读辅助埋点「LT显示」,超过 15 分钟判红,提示卡片清算线徽标停更(只影响显示层)。beat 型,统计正则 [LT兜底]。触发:每次看板汇总|参数
expectMin=15 分钟|版本v3 - 硬心跳:升级巡检仅观测 — 读埋点「升级巡检」,超过 45 分钟判红,提示 30 分钟升级定时器停摆。beat 型;统计正则覆盖自动升级、定时升级、升级 tx 发送失败、技能分配笔数、技能重置摘要、Respec Potion 等。触发:每次看板汇总|参数
expectMin=45 分钟|版本v3 - 硬心跳:自动合成仅观测 — 读埋点「自动合成」,超过 45 分钟判红,提示 30 分钟合成定时器停摆。beat 型,统计正则 [
AutoCraft]。触发:每次看板汇总|参数expectMin=45 分钟|版本v3 - 硬心跳:精确 LT 重算仅观测 — 日志里 75 分钟没出现 [精确LT] 就判红,提示每小时清算线重算停摆(自愈新增的 kami 会一直留着旧公式初值)。sig 型。触发:每次看板汇总|参数
expectMin=75 分钟|版本v3 - 硬心跳:防断连仅观测 — 日志里 35 分钟没出现 [防断连] 就判红,提示 idle 模拟点击停摆,长时间挂机断连风险上升。sig 型。触发:每次看板汇总|参数
expectMin=35 分钟|版本v3 - 条件型模块最后活动仅观测 — 11 个「没活可干时本来就静默」的模块只报最后活动时间,永不标红:批量部署、停采、喂食、复活、XP 药水、拾荒、Gas 统计、DB 增量、杀手扫描、数据库构建、买松果(测试版 1.2.17 加,匹配
[买松果])。它们的正则同时用作执行结果层的统计口径;完整看板里以「⚪ 条件型模块最后活动(无活动≠故障)」一行输出。触发:每次看板汇总|版本v3
事件层
- 事件层:出现即报仅观测 — 约 45 种坏事特征在 60 分钟回看窗口内出现就报,显示「标签 ×次数 —— 最新: 原文」。原文截取 160 字;与心跳层互补:心跳回答该跑没跑,事件回答跑出了什么坏事。触发:每次看板汇总|参数窗口 60 分钟|版本v3
- 🔴 事件:死亡与复活仅观测 — 三类红色事件:发现 kami 死亡(含批量复活、启动窗口批量复活日志);复活 tx 失败或 revert(核心/辅助复活发送失败、触发复活异常);复活丝带断货(没有丝带、丝带不足或没货、Ribbon 不足)。触发:每次看板汇总|版本v3
- 🔴 事件:食物断供与熔断仅观测 — 三类红色事件:救援食物断供(所有 HP 恢复食物库存为 0)、[喂食/熔断]、合成熔断(「可能已上链 revert 扣 gas」)。触发:每次看板汇总|版本v3
- 🔴 事件:tx 执行与停采仅观测 — 四类红色事件:批量 tx 上链但整批执行失败;kami 疑似卡链上;停采后仍有残留(「个未能停采」「两轮重试后仍有」);杀手停采联动断裂(「未找到
emergencyStopHarvest」)。触发:每次看板汇总|版本v3 - 🔴 事件:环境、页面、跨脚本仅观测 — 红色事件:gas 余额告警、白屏保护触发、连续 10 次检测不到 Kami 列表、DOM 规模严重不足、STARVING 异常爆发、部署整批预检失败、辅助/核心跨脚本断链、技能重置失败、进游戏超时自刷、数据库构建失败([构建失败] 或 [主流程异常])。触发:每次看板汇总|版本v3
- 🟡 事件:杀手监控与扫描仅观测 — 黄色事件:杀手邻居预警、监控对象活跃击杀(「自上次检查后清算了」)、API 杀手监控被停止、监控自身定位失败、杀手扫描失败或超时(含
localStorage写入失败)、哨兵发现超强新敌。触发:每次看板汇总|版本v3 - 🟡 事件:锁、黑名单、部署仅观测 — 黄色事件:kami 被拉黑(部署或停采黑名单)、地块不匹配跳过、TX 锁超时强制释放、等 60 秒仍拿不到普通锁跳轮、自动部署仍暂停中、小账户提示(凑批不适用)。触发:每次看板汇总|版本v3
- 🟡 事件:页面自愈与 DOM仅观测 — 黄色事件:钱包 deadzone 自愈、错误界面自愈、页面检测触发刷新、找不到 Party 按钮、Feed 监控被意外启用、步长 DOM 读取失败。触发:每次看板汇总|版本v3
- 🟡 事件:升级、XP、数据库仅观测 — 黄色事件:Respec 药水不足、Respec 药水数量查询失败、升级等锁超时放弃、XP 喂食失败、获取账户持有列表失败(退化为不过滤)、DB 写回
localStorage失败、数据库备份失败。触发:每次看板汇总|版本v3 - 「紧急停采触发」移出事件层已停用 — 原先把「紧急停采触发」当红色事件,现已删除。真正的故障仍由闭环层「紧急停采闭环」(开始后 10 分钟无终态才报)和「警报→停采联动」覆盖,这两条都保留。来历:用户 0710 拍板:触发紧急停采是代码在正确响应(业务事件),不是故障;看板只管「代码没跑对」|版本1.1.23
- 「杀手同房间警报」移出事件层已停用 — 原先把「杀手同房间警报」当红色事件,现已删除。代码层面的检查由闭环层「警报→停采联动」(5 分钟内没跟上停采才报红)承担,该规则保留。来历:用户 0710 拍板:杀手在场是业务或环境事件,不是代码故障|版本1.1.22
闭环层
- 闭环层:开始→终态配对仅观测 — 10 组「开始 → 终态」配对:最近一次「开始」已超过窗口时长,但之后没有终态,就判为流程中途挂死。从新到旧扫日志,分别记下最近一次开始和最近一次终态的时间,终态为空或早于开始即报。只统计 60 分钟窗口内的日志行,更早的开始不会被判。触发:每次看板汇总|版本v3
- 闭环:紧急停采仅观测 — 「[紧急停采] 开始」后 10 分钟内没有「完成 / 没有需要停采的kami / 预检后无需停采」就报红:开始后无终态,流程中途崩溃。触发:每次看板汇总|参数
winMin=10|版本v3 - 闭环:一键停采仅观测 — 「[一键停采] 开始停采」后 10 分钟内没有「完成 / 没有 HARVESTING 状态 / 异常 / 当前地块未知」就报红。触发:每次看板汇总|参数
winMin=10|版本v3 - 闭环:转移停采仅观测 — 「[转移停采] 准备停采」后 10 分钟内没有「完成 / 异常 / 辅助脚本未加载 / 无法识别 majority」就报红。触发:每次看板汇总|参数
winMin=10|版本v3 - 闭环:部署计划→成交仅观测 — 「笔(API)] 计划」后 10 分钟内没有成交、API 失败、全部预检失败、或让路回报,就报红:部署链路断了。让路回报包括:紧急锁存在跳过 DOM 兜底部署、检测到紧急锁中断部署、等待紧急锁释放超时。触发:每次看板汇总|参数
winMin=10|版本v3 - 闭环:死亡→复活联动仅观测 — 日志出现「个 Kami 死亡」后 5 分钟内没有「立即触发批量复活 / 批量复活 N 只 / 触发复活异常」就报红:发现死亡但复活没被触发。触发:每次看板汇总|参数
winMin=5|版本v3 - 闭环:警报→停采联动仅观测 — [同房间警报] 或 [邻居预警] 后 5 分钟内没有「⚡ 触发紧急停采 / 未找到
emergencyStopHarvest/ [紧急停采] 开始」就报红。「未找到emergencyStopHarvest」算作终态,这种情况由事件层「杀手停采联动断裂」另外报红。触发:每次看板汇总|参数winMin=5|版本v3 - 闭环:杀手扫描仅观测 — 「自动全网扫描」或「开始扫描(预计」后 10 分钟内没有「扫描完成 / 失败 / 游戏 API 未就绪」就报黄:扫描启动后挂死(正常 20~60 秒)。触发:每次看板汇总|参数
winMin=10|版本v3 - 闭环:升级复查轮仅观测 — 「再检查一遍是否有遗漏」后 20 分钟内没有「一键升级 + 技能分配 全部完成」或「所有 RESTING Kami 均已」就报黄。触发:每次看板汇总|参数
winMin=20|版本v3 - 闭环:技能重置仅观测 — 「正在重置 #」后 10 分钟内没有「技能重置成功」或「❌ 重置技能失败」就报黄:重置 tx 卡死,药水可能已经扣了。触发:每次看板汇总|参数
winMin=10|版本v3 - 闭环:XP 药水合成仅观测 — 「开始合成 Greater XP Potion」后 10 分钟内没有「成功合成」或「❌ 合成失败」就报黄。触发:每次看板汇总|参数
winMin=10|版本v3
状态层
- 状态层:检查函数逐个隔离仅观测 — 状态层的 9 个检查函数各自 try/catch,单个函数抛错直接跳过,不影响其他检查和整张看板。每个函数接收 ctx {now,
pageAgeMin, prev, beats},返回问题数组。触发:每次看板汇总|版本v3 - 状态:紧急锁僵尸仅观测 —
window.__txEmergencyLock持有超过 10 分钟没自愈就报红:全部 tx 被卡住。触发:每次看板汇总|参数10 分钟|版本v3 - 状态:普通锁僵尸仅观测 —
window.__txNormalLock持有超过 8 分钟就报红,显示持锁的脚本名和操作名。触发:每次看板汇总|参数8 分钟|版本v3 - 状态:两次自检快照仅观测 — 保存一份自检快照(时间、日志缓冲长度、紧急停采旗、操作旗),给卡死类检查做两点比较。距上次快照超过 15 分钟才刷新,所以通常对比的是上一次 30 分钟自检的状态。触发:每次看板汇总|参数15 分钟|版本v3
- 状态:停采互斥旗卡死仅观测 —
__emergencyStopRunning连续两次自检(间隔 ≥15 分钟)都是 true 就报红:停采互斥旗卡死,三个停采命令全被锁住。触发:每次看板汇总|参数≥15 分钟两点|版本v3 - 状态:操作标志卡死仅观测 —
__kamiOperationInProgress连续两次自检都是 true 就报红:操作标志卡死,会拖延 45 分钟强制刷新。触发:每次看板汇总|参数≥15 分钟两点|版本v3 - 状态:日志缓冲零增长仅观测 — 两次自检之间
__kamiLogBuffer长度完全没变就报红:log 管道停滞,或页面被系统挂起。只有核心在场(stopCurrentRoom存在)时才检查。触发:每次看板汇总|参数≥15 分钟两点|版本v3 - 状态:停采模式内外不一致仅观测 — 内存
window.__kamiMode与localStoragekami_mode不一致时报黄:切换模式后没刷新页面,新停采线没生效。两边都有值才比较。触发:每次看板汇总|存储kami_mode(读)|版本v3 - 状态:杀手检测旗残留仅观测 —
__killerDetected为 true,且__lastKillerTime缺失或已超过 25 分钟,就报黄:安全期解除逻辑没跑,停采线被长期锁在安全档。触发:每次看板汇总|参数25 分钟|版本v3 - 状态:杀手监控名单为空仅观测 — 杀手监控脚本在场(
startKillerMonitor存在),但__killerWatchList为空时报红:监控空转零保护,请往KILLER_KAMI_INDEXES填编号。监控脚本不在场时,整个监控检查函数直接跳过。触发:每次看板汇总|版本v3 - 状态:杀手映射为空仅观测 — 页面运行超过 6 分钟后,
__killerPlayerMap加__killerSelfOwned总数仍为 0,报红:名单里的杀手全部反查失败,监控实质失明。触发:每次看板汇总|参数页面 >6 分钟|版本v3 - 状态:自家杀手未注册仅观测 —
__killerSelfOwned里有杀手不在MY_KILLER_KAMIS中时报黄:核心或辅助可能误部署、误洗点正在作战的杀手。只报第一只(break)。触发:每次看板汇总|版本v3 - 状态:核心紧急停采接口缺失仅观测 — 页面运行超过 5 分钟、监控脚本在场,但
window.emergencyStopHarvest不存在,报红:下次杀手警报只能告警,无法自动停采。触发:每次看板汇总|参数页面 >5 分钟|版本v3 - 状态:杀手活跃度快照陈旧仅观测 — 名单非空,且
kami_killer_activity里所有快照都超过 15 分钟没刷新,报黄:轮询死了,或查询全部失败。JSON 解析异常静默跳过。触发:每次看板汇总|参数900 秒|存储kami_killer_activity(读)|版本v3 - 状态:数据库脚本混装旧版仅观测 — 精简数据库脚本在运行(
rebuildKamiCoreDb存在),页面超过 5 分钟,但共享缓冲里没有它的日志,报黄:请升级到 ≥1.1.7/1.1.12,否则构建失败看板看不见。触发:每次看板汇总|参数页面 >5 分钟|版本v3 - 状态:数据库构建失败面包屑仅观测 —
kami_db_last_fail比kami_core_db_meta.builtAt更新、且在 7 天内时报黄:精简数据库上次运行失败(显示阶段和几小时前),当前库可能是旧快照。面包屑由精简数据库脚本写入,刷新后仍在;这项放在库校验前面,库缺失时也照报。触发:每次看板汇总|参数7 天|存储kami_db_last_fail;kami_core_db_meta(读)|版本v3 - 状态:
kami_core_db解析失败仅观测 —localStorage里的kami_core_db无法 JSON 解析时报红:数据地基损坏,请用rebuildKamiCoreDb重建;并终止本函数后续检查。解析结果不是数组时,改用内存window.kami_core_db兜底;空库直接返回,交给不变量检查报。触发:每次看板汇总|命令rebuildKamiCoreDb()|存储kami_core_db(读)|版本v3 - 状态:数据库降级记录比例仅观测 — harmony 为 null 的降级记录超过 20% 报红(构建期接口大面积故障,建议重建);有但不到 20% 报黄。触发:每次看板汇总|参数20%|版本v3
- 状态:缺 LT 的记录仅观测 — 缺 LT 的记录数多于降级记录数时报黄:这些 kami 没有精确停采线保护。触发:每次看板汇总|版本v3
- 状态:高清算线 kami 建议升级仅观测 — 清算线超过 65% 的 kami 报黄,列出前 5 只的编号和 LT,建议优先升级。触发:每次看板汇总|参数LT>65%|版本v3
- 状态:杀手档案缺桶仅观测 —
kami_top_predators里有空桶时报黄:该手型清算线会系统性偏低,建议重扫。触发:每次看板汇总|命令scanTopPredators()|存储kami_top_predators(读)|版本v3 - 状态:档案存在但生效内置默认仅观测 —
localStorage有杀手档案,但页面运行超过 10 分钟后生效来源仍是「内置默认」,报黄:档案解析疑似失败。触发:每次看板汇总|参数页面 >10 分钟|存储kami_top_predators(读)|版本v3 - 状态:清算线公式定点自检仅观测 — 用 5 组固定输入跑
computePreciseLT,与解析期望值对比(误差 ≤0.05),任一不过就报红:公式、亲和表或 CDF 代码疑似被改动,全库清算线不可信。5 组向量(vio 均 41,猎物 harm 均 41):SCRAP 攻 ATS0.4/ATR0.5 打 NORMAL 体=70;NORMAL 攻打 NORMAL 体=24;EERIE 攻打 SCRAP 体=30;EERIE 攻打 INSECT 体=10;NORMAL 攻 ATS1 打 SCRAP 体=100。只报第一组失败,与威胁档案无关,无宽限期。触发:每次看板汇总|参数容差 0.05|版本v3 - 状态:LT 超出值域仅观测 — 数据库里 LT 小于 0 或大于 100 的记录报红:写库逻辑有问题。页面运行 ≤10 分钟时不检查(等启动重算跑完)。触发:每次看板汇总|参数宽限 10 分钟|版本v3
- 状态:库存 LT 与现算值不一致仅观测 — 数据库存的 LT 与当前档案现算值相差超过 0.05 的记录,过半报红,否则报黄,并给出一个样例。少量不一致属正常(新增 kami 等整点重算);大量或过半说明
refreshPreciseLT没在跑。页面运行 ≤10 分钟不检查。触发:每次看板汇总|参数容差 0.05;宽限 10 分钟|命令refreshPreciseLT()|版本v3 - 状态:LTHP 与 LT 不自洽仅观测 — LTHP 与 LT/100×maxhp 相差超过 0.5 的记录报黄:疑似半截写入,可跑
refreshPreciseLT()重算。触发:每次看板汇总|参数容差 0.5|命令refreshPreciseLT()|版本v3 - 状态:保守包络方向校验仅观测 — 正常情况下,旧公式(满配假想杀手 V41)是精确值的上界。精确 LT 超过旧公式值 0.05 以上的只数报黄,提示用
showTopPredators()/compareLT()核查威胁档案。只在生效档案里没有 ATS>0.4 杀手时才检查(有超常杀手时突破包络是正常的)。触发:每次看板汇总|参数容差 0.05|命令showTopPredators();compareLT()|版本v3 - 状态:辅助 LT 显示埋点缺失仅观测 — 页面运行超过 20 分钟,
__kamiHealthBeats里仍没有「LT显示」埋点,报红:辅助脚本自身的定时器层疑似半死。触发:每次看板汇总|参数页面 >20 分钟|版本v3 - 状态:XP 药水总控未启动仅观测 — 页面超过 15 分钟、核心在场且有主循环埋点,但没有「XP流程」埋点,报黄:启动序列断链(需要核心 ≥1.1.7/3.3.13 才有埋点)。触发:每次看板汇总|参数页面 >15 分钟|版本v3
- 状态:自动拾荒未运行仅观测 — 页面超过 15 分钟、核心在场且有主循环埋点,但没有「拾荒」埋点,报黄:本页自动拾荒没运行(需要核心 ≥1.1.7/3.3.13 埋点)。触发:每次看板汇总|参数页面 >15 分钟|版本v3
- 状态:黑名单非空提示仅观测 — 停采黑名单或部署黑名单里有 kami 时报黄,显示各自只数,说明 30 分钟自动解封,并给出查详情的命令。读
window.__stopBlockedKamis和window.__blockedKamiIds的 size。触发:每次看板汇总|命令showStopBlockedKamis();showBlockedKamis()|版本v3 - 状态:复活防重发表类型异常仅观测 —
window.__reviveSentAt存在但不是 Map 时报黄:复活三路防重发互认失效。触发:每次看板汇总|版本v3 - 状态:XP 喂食记录损坏仅观测 —
kami_xp_potion_fed无法 JSON 解析时报黄:会重复喂药,可用clearXPPotionFed()重置。触发:每次看板汇总|命令clearXPPotionFed()|存储kami_xp_potion_fed(读)|版本v3 - 状态:错误重载计数未复位仅观测 —
kami_reload_count≥1,且页面已运行超过 10 分钟,报红:陷在启动失败循环里。触发:每次看板汇总|参数页面 >10 分钟|存储kami_reload_count(读)|版本v3 - 状态:LT 徽标样式节点丢失仅观测 — 数据库非空,但页面上找不到 id 为
__aux_lt_style__的样式节点,报黄:清算线徽标全部隐形。DOM 直读。触发:每次看板汇总|版本v3 - 状态:gas 配对表陈旧仅观测 —
kami_gas_pending里超过 24 小时没被消费的条目报黄:消耗统计链路断裂。解析异常静默跳过。触发:每次看板汇总|参数24 小时|存储kami_gas_pending(读)|版本v3 - 状态:日志时间戳解析失败仅观测 — 最近 10 行日志(至少 5 行)的时间戳全部解析失败时报黄:时区或格式变了,所有依赖日志时间的指标已失明。触发:每次看板汇总|参数最近 10 行,至少 5 行|版本v3
- 日志时间戳解析仅观测 — 解析日志行里的 [YYYY-MM-DD HH:mm:ss],按脚本配置的时区偏移换算成绝对时间;解析不了返回 null。用
__TZ_OFFSET_MS拼出 ±HH:MM 后缀,再交给 new Date;四个脚本统一用这个格式。触发:被看板汇总调用|版本v3
执行结果层与白名单
- 执行结果层:带错日志判定仅观测 — 60 分钟窗口内按模块统计日志总数、带错数、最新一条错误原文(截 140 字)。带错关键词:❌、失败、超时、revert、异常、不足、mismatch、
CALL_EXCEPTION。触发:每次看板汇总|参数窗口 60 分钟|版本v3 - 白名单:「失败计数=0」统计行仅观测 — 判带错之前,先剥掉「真失败 0」「失败0个」「失败 0」这类零计数片段再判断,纯统计收尾行不计带错。正则用 (?!\d) 防止误剥「失败 02」这类非零计数。触发:每次看板汇总|来历这类行只是计数字段名带「失败」二字,并不是真出错,之前会被误计带错|版本v1.1.16
- 白名单:条件不满足的诊断行仅观测 — 剥离零计数片段后,整行命中「需 ≥ / 缺 N / 步长不足 / 库存不足 / 材料不足(测试版 1.2.19 加) / 等恢复或喝体力药 / 无匹配食物 / 档位不匹配」的,不计带错。只影响带错统计,不影响事件层(比如「复活丝带断货」照报)。触发:每次看板汇总|来历缺材料、缺体力、步长不足是正常说明(条件满足了会继续干),但常带 ❌ 或「不足」被误计带错|版本v1.1.19
- 白名单三批:
pendingVerify/ gas 不作停成凭据仅观测 — 白名单追加两个模式:pendingVerify、「gas 不作停成凭据」,这类观察行不计带错。触发:每次看板汇总|来历核心 1.1.17 起,gas 判为full_exec时会打「gas 不作停成凭据 →pendingVerify(本批不计成功/不计失败)」观察行,含「失败」二字被误计带错|版本1.1.20 - 规则:模块带错比例仅观测 — 某模块近 1 小时带错 ≥3 条、且占总数一半以上,报红;有带错但没到这个程度,报黄。两者都附最新一条错误原文。触发:每次看板汇总|参数≥3 条且 ≥50%|版本v3
- 规则:批量部署有动作但成功 0 只仅观测 — 近 1 小时批量部署模块有日志,但从「[批量部署/第 N 笔(API)] 成功 X 个」累加出的成功数为 0,报红:请排查预检、nonce、tile 获取。触发:每次看板汇总|版本v3
- nonce 撞号:去重计数 + 抓号段测试线独有仅观测 — 从「account sequence mismatch, expected X, got Y」抓出号段,按 X/Y 数对去重计数;报告里列前 3 组「期望X/实到Y」,提示:实到号若落在停采发送的 nonce 区间内,就是 raw 停采占了号、MUD 队列计数器没跟上。注释说门槛从 3 降到 1。代码实际:1~2 次标 🔵,≥3 次才标 🟡,而
showHealth只挑 🔴/🟡 展示、也只拿它们判断「有问题」,所以 1~2 次撞号目前既不显示,也不触发看板。触发:每次看板汇总|参数≥3 次 🟡;1~2 次 🔵|来历0913 实盘定案:一次撞号游戏会打两行日志(EXECUTION FAILED + NONCE ERROR detected),旧码记成 2 次。之前误以为 NONCE ERROR 是误标(外层只是 -32000 参数错),其实错误体深处的 sequence mismatch 才是真相:看错误类别不能只看最外层 code|版本测试版1.2.13 - nonce 撞号:无号段的其他形态测试线独有仅观测 — 没有给出 expected/got 号段、但有 nonce 语义的报错也计为撞号:sequence mismatch、nonce too low/high、invalid nonce、nonce has already been used、replacement underpriced。这类形态每行计 1 次,不去重。触发:每次看板汇总|版本测试版1.2.13
- nonce 正则收紧(不再误数自家日志)仅观测 — 撞号只按真实报错短语匹配,不再用宽泛的 /nonce/i,我方「nonce统一/统一nonce」这类诊断日志不再被误计为冲突。注释写收紧为 NONCE ERROR;代码现在实际匹配 account sequence mismatch 等真实报错短语(见上两条)。触发:每次看板汇总|来历0710 取证:mud 通道真实 nonce 冲突为 0,看板显示的「nonce 冲突 3 次」全是自身日志被误匹配|版本1.1.24
- 规则:TXQueue 非 nonce 失败测试线独有仅观测 — [TXQueue] 行里含 EXECUTION FAILED 或 TX failed、但与 nonce 无关的,累计 ≥5 次报黄,提示看原文定性(例如 status 0 = 链上 revert)。已被 nonce 规则命中的行不重复计入。触发:每次看板汇总|参数≥5 次|版本测试版1.2.13
- 防自回声过滤仅观测 — 扫日志时跳过看板自己的输出:含「代码健康看板」表头的整块,以及带 [健康] 标记的全绿行和自检异常行。
log()把多行看板作为一条缓冲记录写入,所以整块都能靠表头关键词跳过;故意不用裸 🩺 过滤。触发:每次看板汇总|来历否则看板里回显的「最新: 原文」会被事件、闭环、心跳正则再次命中,产生自回声误报;辅助复活的正常日志也用 🩺,不能按 emoji 过滤|版本v3 - 看板跳过合成缺货提醒行测试线独有 — 带
[合成材料]标签的日志行整行不进看板统计:缺货是业务提醒不是代码故障,而明细里的 XP Potion / Respec Potion / 拾荒 会误命中 XP药水、升级巡检、拾荒 模块正则,最坏把「升级定时器没跑」的红色报警盖成绿色(0914 审查用真实看板代码复现)。触发:每次看板自检|版本测试版1.2.14 - 看板跳过核心启动命令清单的
//说明行测试线独有 — 日志前缀后以//开头的行整条不进看板统计。核心启动时会打一份命令清单,其中的说明文字含「revert白烧」「查看停采黑名单(停采失败…)」「清除喂食失败冷却记录」等字样,既命中错误词又含「停采/喂食」模块关键词 → 每轮看板都误报「停采/喂食/XP药水 近1小时 N 条带错」。真实日志行不会以//开头(都带 emoji 或[标签])。触发:每次看板汇总|来历0921 夜实测一页 35 行说明里 3 行中招,四号每轮都报|版本测试版1.2.19 - 看板跳过核心合成诊断卡片 + 拾荒规则收紧测试线独有 — 含「═══ 合成诊断:」的核心诊断卡片整条不进看板统计(它是条件不满足的说明;玻璃罐标签里的「喂食」会误归喂食模块)。拾荒模块匹配从裸「拾荒|Scavenge」收紧为「自动拾荒|跳过拾荒|Scavenge」:核心真实拾荒日志都带这些字样,裸「拾荒」会误命中命令清单、gas 报告和合成诊断里的说明文字(0914 日志核对:原来被计入拾荒的多是这些说明行)。判断拾荒是否在跑仍靠心跳埋点,不受影响。触发:每次看板自检|版本测试版1.2.15
不变量、展示与周期自检
- 不变量:
kami_core_db为空仅观测 — 内存数据库为空时,列入 🔴 重点排查区:请运行精简数据库脚本构建。misc 类问题在看板里归到 🔴 区输出。触发:每次看板汇总|版本v3 - 不变量:清算线中位数 >70%仅观测 — 数据库 LT 中位数超过 70% 时,列入 🔴 区:疑似还是旧公式刻度,精确化可能没生效,请跑
refreshPreciseLT()核实。触发:每次看板汇总|参数中位数 >70%|命令refreshPreciseLT()|版本v3 - 不变量:杀手档案长期未更新仅观测 —
kami_top_predators扫描时间超过 8 天,列入 🔴 区,建议手动scanTopPredators()。文案写「周扫调度疑似失效」,但实际档案有效期是 6 小时(启动时判定),8 天是很宽松的兜底线。触发:每次看板汇总|参数8 天|命令scanTopPredators()|存储kami_top_predators(读)|版本v3 - 不变量:页面长时间未刷新仅观测 — 页面连续运行超过 65 分钟,列入 🔴 区:核心的 45 分钟定时刷新疑似失效。用
performance.now()计算页面运行分钟数。触发:每次看板汇总|参数65 分钟|版本v3 showHealth完整看板 — 随时打印完整看板。结构依次为:🔴 重点排查(心跳超期 + 红色规则、事件、闭环、状态 + 不变量)→ 🟡 关注 → 📊 近 60 分钟执行统计 → 🟢 正常心跳 → ⚪ 条件型最后活动 → 没问题时加「✅ 未发现问题」。心跳超期行格式:「期望≤N分钟一次,实际 X —— 提示」。触发:命令|命令showHealth()|版本v3showHealth(true)全绿一行模式仅观测 — 只看问题的模式下,如果没有任何问题,只打一行「🩺 [健康] N 个硬心跳正常、近1小时无报错无事件、闭环/状态检查全过」,附页面运行分钟数。「有问题」= 心跳超期、不变量、或任意 🔴/🟡 规则/事件/闭环/状态。触发:周期自检调用|命令showHealth(true)|版本v3- 执行统计行与相对时间格式仅观测 — 📊 行逐模块显示「N条(错M)」,附近 1 小时部署成功只数、精确 LT 上次更新条数。时间统一显示为「本次会话内从未 / 刚刚 / N分钟前 / N小时前」。精确 LT 更新条数从日志「[精确LT]…更新 N 条」里提取最新一次;不到 1.5 分钟显示「刚刚」,90 分钟以上按小时显示。触发:每次看板输出|版本v3
- 周期自检仅观测 — 页面加载后延迟 10 分钟做第一次自检,之后每 30 分钟一次。每次先写「健康自检」埋点,再跑
showHealth(true);抛错时落日志「⚠️ [健康] 自检异常」。注释说自检埋点「可被自身监控」,但本段的心跳注册表里没有登记「健康自检」条目。触发:自动:首次 10 分钟后,之后每 30 分钟|参数首检延迟 10 分钟;间隔 30 分钟|来历修 v2「看板自己挂了也没有痕迹」的盲区;首检延迟是为了等各模块跑起来|版本v3
2.9 物品池自动买松果
测试版 1.2.17 新增,1.2.18 起默认开:MUSU 够多、松果不多、价格合适时,自动在物品池(Item Pool)用 MUSU 买 Pine Cone,每 7 天最多买一次。原则是「宁可不买,绝不多买」:任何读数不确定就跳过这一轮。
- 买入规则测试线独有 — 同时满足才买:①自己 MUSU ≥30 万;②松果 ≤3000(超过 3000 就不买,所以买完一般约 4000);③买 1000 个的平均价 ≤200 MUSU/个。每次买 1000 个。触发:开启后每 5 分钟一次|参数
PINE_BUY_CONFIG:lot=1000、maxAvgPrice=200、minMusu=300000、maxHold=3000|来历用户 0919 提 - 价格上限由链上强制测试线独有 — 物品池的 swap 是「付定额 MUSU、按池子现价换松果」,到手少于最低数量链上直接撤销。脚本下的单:付款 = min(1000×200, 精确成本×1.005),最低到手 = 1000。所以最多付 20 万、至少拿 1000 个,平均价一定 ≤200,不取决于脚本算得准不准;多给的 0.5% 只是容忍报价到上链之间别人先买了一点。精确成本按合约公式反算,0919 探针实测到个位(少付 1 MUSU 就撤销)。链上接口:
PoolSystem.swap(1, 1004, 付款, 最低到手),前端走api.player.pool.swap(MUD 队列,和合成同一通道)|参数slippageBps=50 - 余额读链上最新值,和页面取保守值测试线独有 — 池子储备、手续费、本号 MUSU 和松果都用 eth_call 直接读组件合约(
safeGet,不花 gas),再和页面数据比:松果取两者大的、MUSU 取两者小的。背包条目的链上 id 由脚本自己算(keccak256("inventory.instance", 账户 id, 物品编号),页面没有全局 ethers,脚本自带 keccak,已对 ethers 逐字节核对):松果用到 0 时链上会删掉背包条目、页面也可能没有,不能靠页面条目;自算 id 每轮先和页面 MUSU 条目的 id 对一遍。页面刚刷新读到占位账户、链上读不到、自算 id 对不上、链上 MUSU 读成 0 而页面有(组件合约可能换代)都整轮跳过。读账户、链上读数、模拟各等最多 15 秒|参数readTimeoutMs=15000 - 发单前先模拟测试线独有 — 用 eth_call 在当前链上状态模拟这笔 swap(不签名、不上链、不花钱):撤销或到手不足 1000 就不发 tx。价格在报价后变了导致的撤销是正常情况,日志打「模拟下单通不过(链上会撤销…)」,不算错误;节点出错才打 ❌
- 拿锁后全部重查测试线独有 — 发单段先拿浏览器跨标签锁(
navigator.locks,按钱包地址命名,同一账户开两个标签页也只有一边能进;浏览器不支持就不买),再拿共享普通锁 (pool,helper),两把都拿到后把余额、模拟、冷却、次数、开关、钱包地址全部再查一遍,发单前最后一刻再同步重读当前钱包、前端冻结、紧急停采(不用评估时的快照)才发单;拿不到锁就等下一轮,不排队|来历0919 双异源审查(codex + kimi):第一次检查到发单之间余额/开关可能已变,普通锁是页面内存锁、跨标签不通 - 节奏:每 7 天最多买一次,到期没买成每天查一次测试线独有 — 买满一整批(1000 个;下单结果不明、可能已买到的也算)后 7 天内自动轮不再买。7 天到期(或从没买过)后检查一次:因为价格、持有量、MUSU 不满足而没买,算「今天查过了」,24 小时后再查;页面没就绪、链上读失败、锁被占、模拟被撤销这类临时原因不算,下个整点节拍再试。节奏闸只读
localStorage、不读链,挡住时安静返回,每页每个钱包只打一行「本号已开启自动买松果:下次检查约在 N 天后(原因)」。页面每 45 分钟被核心刷新一次,所以「今天查过没有」存在localStorage,刷新后照样记得。小额验证单(buyPineNow({ lot: 10 })这类不满一批的)不占这 7 天;「是不是整批」在下单时写进记录(full),之后改PINE_BUY_CONFIG.lot或刷新都不影响判定。锁内复查时因价格/持有量/MUSU 不买,同样算今天查过。页内定时节拍每小时一次|参数buyIntervalMs=7 天;dailyCheckMs=24 小时;checkIntervalMs=60 分钟|存储kami_pine_buy_check:<钱包地址>(最近一次查过没买成的时间和原因)|来历用户 0919:「改为一个星期买一次」「一个星期没买到,每天检查一次即可」(一个号每天最多消化约 140 个松果,1000 个够用一周左右)|版本测试版1.2.18 - 防连点、次数上限、在途单测试线独有 — 任何一单之后 10 分钟内不再下单(防连点,也是下单失败后的重试间隔);24 小时最多下单 5 次;同一页面上一笔单还没有结果(可能卡在交易队列里)时不下新单;结果不明的单按「可能已买到」算进 7 天间隔,所以同号双开也不会叠单(除非卡在队列里超过 7 天)。下单记录在发单之前写进
localStorage并读回核对,写不进去就不发;读不了记录就不买(不当成没有记录)|参数cooldownMs=10 分钟;maxBuysPer24h=5|存储kami_pine_buy_hist:<钱包地址>(最近 8 天的全留,更早的只留最近 30 条,保证 7 天闸看得到那笔整批)|来历codex 审查指出超时后队列里的旧单可能稍后成交 - 下单与到账核对测试线独有 — 发单最多等 120 秒:超时打「下单超时」,按「可能已买」处理。上链撤销打 ❌。之后每 6 秒读一次链上松果余额、最多 90 秒,松果增加至少 1000 个(本单最少到手数)才算成交,打「✅ [买松果] 到账:松果 A → B(+N),付 M MUSU(均价 x)」;别处来的零星松果不会被当成这笔成交,没到 1000 按结果不明处理;下单报错也先按链上余额核对是否其实已成交。每笔 tx 按动作
pool记入 gas 账本(核心 1.2.60 的showGasReport()分动作明细里暂时不单列这一类,只计入总额)|参数sendTimeoutMs=120000 - 日志与看板测试线独有 — 「不买」的原因类别(价格、持有量、MUSU、冷却…)变了才打一行,同一原因 30 分钟最多打一次;手动命令每次都打。日志都带
[买松果],看板把它列为条件型模块;常态不买的文案不带看板错误词,真出错的(链上读失败、下单超时、核对超时、接口缺失)带 ❌ 或「超时」,会计入带错 - 开关与命令测试线独有 — 1.2.18 起默认开(用户 0919 单号试买 10 个成功后定):没关过的号都自动买;开关按钱包地址存在该浏览器的
localStorage(kami_pine_buy_on:<钱包地址>,只有'0'算关;读不了按关处理),同一浏览器换号不会串,刷新后仍记得。定时节拍一直在跑(刷新后 4 分钟首检,之后每小时),关了的号每轮只读一下开关就返回,第一次检查时打一行本号开没开。命令:startAutoBuyPine()重新开(10 秒后首检);stopAutoBuyPine()关(先记在本页内存里,再写localStorage并读回确认;写不进去时本页照样停,但会提示刷新后可能还开着;正在进行的一轮发单前也会看到);showPineBuy()只读报价与此刻会不会买(池子、现价、精确成本、链上/页面余额、模拟结果、近 24 小时下单,结论算上冷却和次数);buyPineNow()立即按规则跑一轮(不看开关,也不受 7 天 / 每天一次限制;10 分钟防连点和 24 小时上限照旧);buyPineNow({ lot: 10 })只买 10 个,用来小额验证下单通路。手动参数只能比默认规则更严(lot1~1000、maxAvgPrice≤200、minMusu≥30 万、maxHold≤3000),要放宽请改window.PINE_BUY_CONFIG(本页有效,刷新恢复)|版本测试版1.2.17 - 已知限制测试线独有 — ①定额付款只限了「最少到手」,限不了「最多到手」:模拟之后若有人往池里大量卖松果,这一单会按更低的价拿到多于 1000 个(花费仍 ≤20 万、均价只会更低),持有量可能超过 4000。swap 接口本身不提供输出上限。②「在途单」标记只在本页内存里,但结果不明的单算进 7 天间隔(1.2.18),同号双开要卡在交易队列超过 7 天才可能叠单
三、精简数据库
精简数据库脚本扫描账户全部 kami,每只压成 17 个字段存进 localStorage,供其他脚本零 API 读取。首次安装或数据异常时启用一次,建完建议停用。
3.1 定位与使用建议
- 篡改猴注入范围与运行时机 — 脚本只在
kamigotchi.io的所有子域页面注入,页面空闲(document-idle)后运行,不申请任何特权 API。@match https://*.kamigotchi.io/*、@run-at document-idle、@grant none;测试版的 @name 里带着版本号「Kamigotchi精简数据库-测试版-1.2.6」,@version v1.2.6。触发:自动(打开游戏页面) - 精简数据库定位:17 字段库供全套件零 API 读取 — 扫描当前账户名下全部 kami,每只压成 17 个字段,存进
localStorage.kami_core_db并挂到window.kami_core_db,其他脚本直接读、不用再调接口。核心脚本拿它做部署决策、停采线计算、XP 药水喂食目标筛选;辅助脚本拿它做升级/技能管理、kamiAnalyze地块适配分析、findKillerCandidates杀手候选扫描。触发:自动(main 末尾构建)/命令rebuildKamiCoreDb()|存储kami_core_db|来历让其他脚本零 API 成本读取 kami 关键数据 - 使用建议:构建一次后停用,日常增补交给核心脚本 — 首次安装时启用,构建成功后建议到篡改猴面板停用,免得每次刷新都全量重扫;新 kami 的日常增补由核心脚本
syncKamiDb()增量自愈;数据异常或换号时重新启用本脚本,或调rebuildKamiCoreDb()全量重建。头部说明框和构建完成处的注释都这么写;控制台出现「🎉 构建完成」就算成功。触发:用户操作|命令rebuildKamiCoreDb()|来历避免每次刷新都全量重扫 - 新手提示:构建耗时与故障处理步骤 — 告诉新手一次完整构建大约 1~2 分钟(先固定等 40 秒,最后 10 秒有橙色倒计时,再展开 Party、切换眼睛图标),完成后以表格列出全库。出现「❌ [构建失败]」或「[主流程异常]」时先刷新页面重试;还不行就等页面加载完,在控制台执行
rebuildKamiCoreDb()。触发:用户阅读说明|命令rebuildKamiCoreDb() - 构建时的清算线只是初值,辅助脚本会重算覆盖 — 本脚本按「官方精确公式 + 内置默认最强杀手档案」写入 LT/LTHP;辅助脚本启动后,会按每 6 小时全网扫描出的最新档案重算并覆盖(
refreshPreciseLT)。两边公式口径相同,构建值和最终生效值只随档案新旧略有差异;辅助脚本会在启动时、扫描后、每小时重算覆盖。触发:自动(辅助脚本侧覆盖)|存储kami_core_db|版本v1.1.8 起
3.2 日志与版本检查
- 统一日志
log():带时间前缀、分两行输出仅观测 — 本脚本的日志统一带「[精简数据脚本][YYYY-MM-DD HH:mm:ss]」前缀:先打一行前缀,第二行原样透传正文。时间取当前时刻加时区偏移后转 ISO,截到秒;注释写的是「加 8 小时偏移」,代码实际按TZ_OFFSET_HOURS算出的偏移(默认跟随浏览器本地时区)。触发:全程被各板块调用|来历分两行打印能让对象/数组在控制台保持可展开,不被模板字符串串行化;统一时区方便多设备、跨时区核对运行记录 - 日志时区设置
TZ_OFFSET_HOURS仅观测 — 可以配置日志时间戳按哪个时区显示。'auto' 按 -getTimezoneOffset()跟随浏览器本地时区;也能写死小时数:8=UTC+8、-5=UTC-5、5.5=UTC+5:30。触发:脚本载入时求值一次|参数TZ_OFFSET_HOURS='auto'|来历跨时区多设备核对日志时不乱|版本v1.1.3 起 - 刻意不包装/劫持 console — 本脚本不改写 console,只自己调
console.log。日志函数只是在外层加前缀,原生 console 保持不动。触发:设计约定|来历控制台里显示的 source link 是篡改猴注入机制生成的虚拟 URL,脚本内部改不了,包装 console 没收益还有兼容风险 - 跨脚本共享日志缓冲
window.__kamiLogBuffer仅观测 — 每条log()除了打到控制台,还以「前缀+正文」单行形式推进全局数组window.__kamiLogBuffer,供辅助脚本的健康看板做特征反查。数组不存在就先初始化;写入包在 try/catch 里,出错静默;控制台照旧两行输出。代码观察:本脚本不给缓冲设容量上限;正文用args.join(' ')拼接,对象会变成 [object Object],%c 样式串也会原样进缓冲。触发:每次调用log()|来历供辅助健康看板按日志特征反查 - 关键步骤补一条
log(),确保进缓冲仅观测 — 裸console.log/console.error不进共享缓冲,所以关键节点会额外用log()补记一条。补记的节点:眼睛切到 eye-half、点击 Party 加载按钮、构建失败、主流程异常。其余裸 console 输出(倒计时、眼睛状态变更、找不到按钮、DOM 超时警告、DOM/API 数量不一致警告、console.table)不进缓冲。触发:对应步骤发生时|来历注释:裸 console 不进缓冲,看板读不到 - 启动提示顺序约束(首次 log 必须晚于时区常量) — 启动提示的
log()调用必须放在时区常量定义之后。log()引用 const__TZ_OFFSET_MS,const 不提升、有暂时性死区,所以首次调用必须写在它下面。触发:脚本载入|来历曾把启动提示放在时区常量前面,脚本一启动就崩(v1.1.9 修复)|版本v1.1.9 - 版本标签防腐:版本号、线名、构建时间三常量测试线独有 — 版本号、线名、构建时间收成
SCRIPT_VERSION/SCRIPT_LINE/SCRIPT_BUILT三个常量,启动横幅和版本检查的SELF_VERSION都引用它们,不再各改各的。SCRIPT_BUILT由发布器打包时注入真实发布时间(同 @x-release-date,版本没变就沿用旧日期),本地未发布保持占位「(本地未发布)」,日志里看到这个就说明不是从GitHub装的。注释写命令 banner 也引用常量;代码实际上本脚本的命令清单只在头部注释框里,那里的「测试版 v1.2.6」和 @name/@version 仍是硬编码。触发:脚本载入|参数SCRIPT_VERSION='1.2.6';SCRIPT_LINE='测试版';SCRIPT_BUILT='(本地未发布)'(发布器替换,勿手改)|来历0913 实盘日志里辅助脚本 @version 已是 1.2.10,启动 log 却打 v1.2.8(精简库、监控也各差一版),导致两次误判「beta 没装全」「监控没更新」;日志撒谎比没有日志更糟|版本测试版1.2.5 - 醒目启动横幅仅观测 — 脚本一载入就打一条 16px 粗体白字、绿底圆角的启动横幅,写明线名、版本、构建时间,并提示「等待网页加载完成」。
log()配 %c 样式(背景 #2e7d32)。代码观察:横幅在防重复执行守卫之前执行,重复注入也会再打一次。触发:脚本载入即执行|版本1.1.12 - 启动版本检查:8 秒后对比
GitHub最新版本仅观测 — 启动 8 秒后拉取GitHub仓库 beta 线的 kamigotchi-database-beta.meta.js,解析最新 @version 和 @x-release-date,与本机版本比较后打提示。URL 带 ?_=时间戳,并设 cache:'no-store' 防缓存;用正则抓 @version、@x-release-date(抓不到日期显示「未知」);cmpVer逐段按数字比较,返回 -1/0/1。本脚本没有GitHub分发的内部版可以整块跳过。触发:自动(脚本载入后setTimeout8000ms,一次)|参数延迟 8000ms|来历延迟 8 秒是为了避开启动时的拥挤|版本1.1.11 - 记录本机首次运行该版本的时间仅观测 — 本机第一次运行某个版本时,把当时时间(zh-CN 本地格式)写进
localStorage,当作「篡改猴安装/更新时间」显示在版本检查提示里。key = 'kami_ver_seen_' + '精简数据库' + '_' + 版本号;已有值就沿用;localStorage异常时显示「未知」。触发:脚本载入(版本检查初始化时)|存储kami_ver_seen_精简数据库_1.2.6|来历脚本读不到篡改猴的安装时间,只能取首次见到该版本的时刻来近似|版本1.1.11 - 版本比较三态提示仅观测 — 跟
GitHub版本一样时打 ✅ 已是最新(附发布日期与本机更新时间);本机落后时打橙色加粗 ⚠️,指引到篡改猴「实用工具→检查用户脚本更新」;本机更新时打 ℹ️「本地开发版」。按cmpVer结果 0 / <0 / >0 分支输出。触发:版本检查拿到远端版本后|版本1.1.11 - 远端版本解析失败时静默跳过仅观测 — 从拉回的文本里抓不到 @version 时,只打一条 ℹ️「无法解析
GitHub最新版本,跳过」就结束。代码观察:非 200 响应(比如返回错误页)也走这条分支,而且不受 24 小时节流,每次都会打。触发:fetch 成功但解析不到版本|版本1.1.11 - CSP 拒绝外联的提示 24 小时只报一次仅观测 — fetch 失败(绝大多数是游戏页 CSP 拒绝外联
GitHub)时,24 小时内只提示一次「属正常,不影响篡改猴自动更新」,并附手动检查更新的路径。读localStorage里上次提示的时间戳:距今 ≥86400000ms 才打并刷新时间戳,否则静默;localStorage本身异常时退化成每次都打。触发:版本检查 fetch 抛错时|参数节流窗口 86400000ms(24h)|存储kami_vercheck_csp_note_精简数据库|来历游戏 SPA 运行时注入 CSP meta 的 connect-src 白名单,raw 外联在游戏页永久失败;旧版每次加载页面都打一行,刷屏|版本1.1.13
3.3 页面准备与接口就绪
构建前先等首屏、点开 Party、把眼睛切到 half,再等链上接口就绪。
- sleep 异步等待 — 返回一个 ms 毫秒后 resolve 的 Promise,全脚本各种等待都复用它。new Promise(r =>
setTimeout(r, ms))。触发:全程被调用 - jitter 随机抖动错峰 — 在 [min,max] 区间取随机整数毫秒,给等待和请求加随机偏移。用在启动等待(0~2000ms)、构建前错峰(300~1800ms)、详情请求前错峰(20~150ms)、退避抖动(0~400 / 0~250ms)等处。触发:全程被调用|来历多账户/多标签页不会在同一时刻集中发请求,降低被限流的概率
- 启动固定等待 + 最后 10 秒橙色倒计时 — main 开头先固定等约 40 秒让游戏首屏加载完,最后 10 秒每秒打一条橙色加粗的「页面加载中... 剩余 N 秒」,结束时提示准备打开 Party。总时长按整秒向下取整,逐秒 sleep;main 传入 40000+
jitter(0,2000),实际等 40~42 秒;等待期间完全不碰 DOM;倒计时用裸console.log,不进共享缓冲。触发:main()开头调用一次|参数totalMs=40000(默认,main 实传 40000+0~2000);countdownSec=10|来历页面没加载完就开扫会找不到按钮/接口;不碰 DOM 是为了避免在半加载状态下误操作;倒计时方便肉眼确认脚本还活着 - 按真人时序派发五个鼠标事件的模拟点击 — 向目标元素依次派发 mouseover → mousedown → mouseup → click → mouseout,模拟真人点击。以
delayMs为基准:+50ms 悬停、+150ms 按下、+200ms 抬起、+300ms click 后立刻 mouseout(relatedTarget=body,把「鼠标」移出);所有事件 bubbles:true;element 为空直接 return 不抛错;事件都用setTimeout异步派发,函数不等点击完成就返回。触发:点击 Party 加载按钮、眼睛按钮时调用|参数delayMs=0(默认;加载按钮传 500,眼睛按钮传 300);间隔 50/150/200/300ms|来历游戏前端(React 类框架)校验完整事件序列,只派发单个 click 常常不生效;冒泡是为了让框架挂在上层的事件委托也能收到 - 读取眼睛图标状态
getEyeState— 读 Party 面板上眼睛按钮图标的当前状态:open / half / closed,找不到返回 null。只在 #party 容器内查 button img[src*="eye-"],取第一个命中,看 src 里含 eye-open / eye-half / eye-closed;都不含也返回 null。触发:waitForEyeHalf每轮轮询调用|来历查询严格限定在 #party 内,避免误抓页面其他位置的眼睛图标 - 循环点击眼睛按钮直到切到 eye-half — 轮询眼睛状态,已是 half 就返回 true 进入构建;否则点一下眼睛按钮,等状态落定后再查,直到切到 half。从图标
closest('button')反查所属按钮,simulateClick(按钮, 300) 后等 1 秒;眼睛状态按固定顺序循环,多点几次必经 half。实际节奏:点击后等 1 秒再加轮询间隔 2 秒,每轮约 3 秒。触发:startSequence点击加载按钮 3 秒后调用|参数checkInterval=2000;stateDelay=1000|来历eye-half 下每只 kami 以完整卡片渲染,DOM 里才有全部卡片;其他状态列表折叠或精简,DOM 扫不全 - 眼睛状态变化轨迹日志仅观测 — 状态每变一次就打「仅观测 状态变更: 旧 → 新」(首次显示「初始」),切到 half 时打成功提示,并补一条 log 进共享缓冲。用
lastState去重,同状态不重复打;状态变更、点击尝试、找不到按钮都是裸 console,只有「[启动序列] 眼睛已切到 eye-half」走log()。触发:waitForEyeHalf轮询中|来历便于回看切换轨迹 - 找不到眼睛按钮时只等待、不乱点 — 眼睛按钮还没渲染出来时不做任何点击,打一条「找不到眼睛按钮,等待中」后继续轮询。
eyeBtn为空走 else 分支,只打日志,然后照常sleep(checkInterval)。触发:waitForEyeHalf轮询中|来历按钮可能还没渲染出来 - 眼睛切换 3 分钟超时:刷新页面自救 + 失败面包屑 — 超过 3 分钟还没切到 half,就写失败面包屑并
location.reload()刷新页面,返回 false。超时判定放在本轮点击尝试之后;面包屑 {at, stage:'眼睛切换超时(已刷新自救)'} 写进localStorage,写失败静默。触发:waitForEyeHalf累计耗时 ≥3 分钟|参数baseWaitTime=maxWaitTime=3*60*1000|存储kami_db_last_fail|来历防止页面卡死后脚本空转挂机;面包屑写在localStorage里能跨刷新存活,供事后排查 - 等待并点击 Party 加载按钮 — 每 2 秒查一次 Party 加载按钮,出现后模拟点击,固定等 3 秒让眼睛按钮渲染出来,再进入眼睛切换流程。选择器 #
party_buttonbutton;点击用simulateClick(按钮, 500),并用 log 补记进缓冲;只点一次,之后一律交给waitForEyeHalf,不回头重点。代码观察:点击前不判断 Party 是否已展开(该按钮是开关)。触发:main()固定等待结束后调用|参数clickInterval=2000;点击后sleep(3000)|来历把 Party 面板和 kami 列表加载出来,供渲染检测与数量对照 - 加载按钮 5 分钟超时:刷新自救 + 失败面包屑 — 5 分钟内一直没找到 Party 加载按钮,就写失败面包屑并刷新页面,返回 false。面包屑 {at, stage:'加载按钮超时(已刷新自救)'},写失败静默。触发:
startSequence累计 ≥5 分钟未见按钮|参数maxWaitTime=5*60*1000|存储kami_db_last_fail|来历防页面卡死空转;面包屑跨刷新存活 - 眼睛流程失败不直接终止主流程 — 眼睛切换失败时
startSequence返回 false,main 不检查返回值,照常往下走。注释写「失败不阻塞后续 API 构建」;代码实际上失败分支里已经location.reload()了,main 会继续跑waitForKamiList等步骤,直到页面刷新把流程打断。触发:waitForEyeHalf返回 false 时 - Party 卡片渲染就绪检测(非致命) — 轮询 Party 列表固定层级下的 kami 图片,检测到至少 1 张就打「页面已渲染 Kami 卡片(DOM 检测到 N 张)」返回 true;超时只告警、照常走 API 构建。选择器写死 #party > div > div:nth-of-
type(3)> div:nth-of-type(2)> div:nth-of-type(2)img[src*="/kami/"];每 1~1.5 秒随机间隔查一次;超时用console.warn(不进缓冲)后返回 false。构建结果完全以链上 API 为准。触发:main()中startSequence之后|参数maxWaitMs=60000;轮询 1000+0~500ms|来历写死层级只统计 Party 列表内的卡片,避免误计页面其他位置的 kami 图片;随机间隔避免固定节奏查询;页面改版选择器失配时会白等这 60 秒 - 等待
window.network/ explorer 接口就绪 — 每 150ms 检查一次游戏前端暴露的 network 对象:operator 地址、按 operator 查账户、按 index 查 kami 详情,这些齐备才进入构建。explorer 读的是前端本地同步的链上数据副本,不发交易、不耗 gas。条件:window.network、network.network.connectedAddress.value_、explorer.accounts.getByOperator、explorer.kamis.getByIndex同时存在。30 秒超时后抛「network/explorer 未就绪」,由 main 捕获并终止本轮。注释写「四项齐备才放行」;代码实际超时后只检查window.network和 explorer 是否存在,缺connectedAddress或查询函数时不抛错、直接放行,随后在buildCoreDb内报错,记为构建失败。触发:main()中卡片渲染检测之后|参数maxWaitMs=30000;轮询 150ms|来历后续所有数据都从 explorer 读;轮询密一点,保证接口一就绪立刻进入构建
3.4 清算线计算
建库时按官方公式 + 内置最强杀手档案算清算线初值,辅助脚本之后会按最新档案重算覆盖。
- 内置天敌档案
DB_TOP_PREDATORS(四手型最强杀手包络) — 内置一份全网最强杀手的默认威胁档案,按手型分四桶,计算清算线时取全部档案里最坏的对位。取值方向故意保守:个别杀手事后易主或消失,只会让线偏高、不会偏低。EERIE 那档是维度包络(vio/atr 取 0707 实测、ats 取 0717 实测 0.30+0.01 垫),不是真实个体。必须与辅助脚本TOP_PREDATORS_DEFAULT同值同序,两处同步改。触发:清算线计算时被读取|参数EERIE vio36/ats0.31/atr0.50;SCRAP vio41/ats0.30/atr0.50;INSECT vio36/ats0.26/atr0.50;NORMAL vio34/ats0.40/atr0.50|来历0717 某账户 15 只死亡归因:主凶是一只专杀 NORMAL body 的 NORMAL 手杀手,实测 ATS 0.38 而档案只有 0.30(全体低估 8pp),于是 NORMAL 调到 0.40(+0.02 垫),EERIE 调到 0.31|版本1.2.3 - 标准正态分布 CDF 近似
normCdf— 计算标准正态累积分布,供清算线公式里的 Φ(ln(攻Vio/守Harm)) 使用。用 Abramowitz–Stegun 多项式近似 erf,误差约 1e-7,负数按对称处理。触发:computePreciseLT调用 - 亲和克制规则
ltAffinity— 按合约规则算攻方手型对守方 body 的亲和修正值。EERIE 克 SCRAP、SCRAP 克 INSECT、INSECT 克 EERIE:克制 +0.5、被克 -0.5;攻守双方都是 NORMAL 时 special +0.2;只有一方是 NORMAL 为 0;同系或无关系为 0。触发:computePreciseLT调用|参数克制 ±0.5;双 NORMAL +0.2|来历与合约一致;special 只在双 NORMAL 时生效 - 单对精确清算线
computePreciseLT— 给定一个攻方档案和一个守方参数,按官方公式算清算线百分比(0~100)。清算线 = Φ(ln(vio/harm))×0.4×(1+亲和+攻ATR−守DTR)+(攻ATS−守DTS),截断到 [0,1] 后 ×100。攻方 vio 或守方 harm 非正直接返回 0;hand/body 统一转大写再匹配;atr/ats/dtr/dts 缺省按 0。触发:computeLiquidationLine对每个档案调用|来历与辅助脚本computePreciseLT/computePreciseLTForRecord是同一公式的两份副本,改动必须两处同步 - 对全部档案取最坏对位,输出 LT 与 LTHP — 拿守方参数对
DB_TOP_PREDATORS四个档案逐一计算,取最大值作为这只 kami 的清算线,同时给出百分比 LT 和 HP 绝对值 LTHP。harmony 或 maxhp 缺失/非正时返回 {LT:null, LTHP:null};ratio 当守方 DTR、shift 当守方 DTS,为空按 0;LT 保留两位小数,LTHP = LT/100×maxhp 保留两位小数。触发:buildCoreDb为每只参数齐备的 kami 调用一次|来历对已知最强杀手取最坏情况,保守设线
3.5 构建数据库
- 构建前覆盖式备份旧库 — 重建开始前,把
localStorage.kami_core_db原样复制到kami_core_db_old,并解析后挂到window.kami_core_db_old。旧库存在:复制原始字符串落盘,再JSON.parse挂 window(解析失败只影响 window,不影响已落盘的备份),打「已覆盖写入」;旧库不存在:打「未发现现有数据库,跳过」。只保留最近一份,不做多版本。整体 try/catch,失败时console.warn并 log,不阻断构建。触发:buildCoreDb第一步|命令window.kami_core_db_old|存储kami_core_db(读),kami_core_db_old(写)|来历重建若因断网/接口异常产出残缺库,可从kami_core_db_old手动复制回主 key 恢复;只留一份是为了避免localStorage无限膨胀 - 固定并发池,结果按输入顺序回填 — 以固定并发数执行一批异步任务,结果按输入顺序写回数组。启动 concurrency 个
workerLoop,通过共享游标nextIndex自增领取任务(单线程事件循环下天然无竞争);单个任务抛错时该位置写 {error: 信息} 占位,其他任务不受影响,池本身永不 reject。代码观察:buildCoreDb的 worker 内部已吞掉全部异常,一般走不到占位;万一出现,{error} 对象会原样写进数据库。触发:buildCoreDb拉取详情时调用|参数concurrency 由调用方传入(构建时 CONC=10) - 打印当前 operator 地址仅观测 — 构建开始时读出当前 operator 地址(浏览器里代表登录账户执行操作的钱包),并打「准备构建精简数据库,账户的 operator address 是:…」。String(
connectedAddress.value_|| '')。触发:buildCoreDb开头 - 账户 kami 列表拉取:3 次重试 + 线性退避抖动 — 用
getByOperator拉账户及名下 kami 概要列表,拿不到 kamis 就重试,最多 3 次。返回对象带 kamis 才算成功;异常静默吞掉;每次失败后等 300+次数×300+0~400ms(约 600/900/1200ms)。代码观察:第 3 次失败后也会多等一次再退出。触发:buildCoreDb备份之后|参数重试 3 次;退避 300+tries×300+jitter(0,400)ms|来历错峰并容忍接口瞬时失败 - 列表全失败按空列表继续(会写出空库) — 3 次都拿不到列表时按空数组继续构建,并打「API 返回 N 只 Kami(以 API 为准)」。空列表会把主 key 覆盖成空库,此时旧库只剩
kami_core_db_old,需要手动找回。kami 数量一律以 API 为准。触发:账户列表重试耗尽|存储kami_core_db - DOM 卡片数与 API 数量对照告警仅观测 — 统计 Party 列表里的卡片图片数,和 API 返回数量不一致时
console.warn提示「以 API 为准,DOM 仅用于等渲染」。只有 DOM 数 >0 且不相等时才提示;用裸console.warn,不进缓冲;整段 try/catch 静默;不影响构建结果。触发:拿到账户列表之后|来历DOM 可能没渲染全,只作参考 - 详情并发拉取 + 请求前随机错峰 — 用
runPool以并发 10 拉取每只 kami 的详情,每个请求发出前先随机等 20~150ms。CONC=10 写死在函数内。触发:buildCoreDb列表之后|参数CONC=10;请求前jitter(20,150)ms|来历并发调大更快,但请求更集中、易触发限流;调小更温和但耗时线性变长 - 单只详情 3 次尝试 + 指数退避 — 用
getByIndex拉单只 kami 的全量详情,失败或返回空就按指数退避重试,最多 3 次。opts 全开:stats/traits/bonus/harvest/progress/time;异常静默;每次失败后等 250×2^(t-1)+0~250ms(约 250/500/1000ms);第 3 次失败后同样会多等一次。触发:runPool每个任务内|参数尝试 3 次;退避基数 250ms 指数翻倍 +jitter(0,250) - 详情拿不到时写降级记录 — 3 次尝试都失败时,不丢这只 kami,而是写一条降级记录:能从列表概要拿到的字段照填,统计类字段全部 null。index/
imgNumber/kamiId/harvestId/name/level 取自账户列表概要;harmony/maxhp/body/hand/ratio/shift/LT/LTHP/vioBase/harmBase/powBase全部置 null;消费方需识别 null 并跳过。触发:单只详情 3 次尝试均失败|存储kami_core_db|来历保证库里仍有这只 kami 的定位信息,统计字段绝不用残缺数据填 - 立绘编号
imgNumber提取 — 从 image URL 里提取立绘编号,供核心脚本把库记录和页面卡片图片对上号。正则 kami/(\d+).gif 取第一组(字符串),失败为 null;降级记录从列表概要的 image 提取。触发:组装每条记录时|来历核心脚本靠它匹配 DOM 卡片 - body/hand 亲和属性统一转小写 — 把
traits.body.affinity和traits.hand.affinity转成小写存库,取不到为 null。入库是小写;算清算线时computePreciseLT内部再转大写匹配。触发:组装每条记录时|来历body 决定与各手型杀手的克制关系;hand 用于地块分析和克制关系 - 防御阈值加成缺省按 0 — ratio/shift 取自
bonuses.defense.threshold,接口没返回时按 0(无加成)处理,并参与清算线计算。?? 0。代码观察:正常记录缺省是 0,降级记录里是 null,两者口径不同。触发:组装每条记录时|来历ratio 在公式里抵扣攻方 ATR,shift 抵扣攻方 ATS - 出生原始属性
vioBase/harmBase/powBase入库 — 把 violence/harmony/power 三项的出生基础值(base)存进库,取不到为 null。stats.violence.base、stats.harmony.base、stats.power.base。触发:组装每条记录时|存储kami_core_db|来历出生属性终生不变,入库一次就能长期复用,辅助脚本findKillerCandidates()不用再发 API 就能全库筛选杀手候选、评估抗清算与采集潜力 - 清算线参数不齐不硬算 — harmony、maxhp、body 三项都齐备才计算清算线,否则 LT/LTHP 保持 null。判定 harmony != null && maxhp != null && body 非空;
computeLiquidationLine内部还有 harmony/maxhp 必须为正的二次守卫。触发:组装每条记录时|来历绝不用残缺参数硬算出错误的清算线 - 17 字段记录组装 — 每只 kami 输出 17 个字段:index、
imgNumber、kamiId、harvestId、name、level、harmony、maxhp、body、hand、ratio、shift、LT、LTHP、vioBase、harmBase、powBase。index 是链上全局序号(查询与增量同步的主键);kamiId用于发交易;harvestId取harvest.id,不在采集时为 null;level 取progress.level;harmony 和 maxhp 取 total(含等级/技能加成);LT 为 0-100 两位小数;LTHP 为对应 HP 绝对值。触发:buildCoreDb每只 kami|存储kami_core_db|来历★核心脚本主用:部署/停采/喂药发交易与停采线;☆辅助脚本主用:升级、地块分析、杀手筛选 - 落盘
localStorage并挂到window.kami_core_db— 整库 JSON 序列化写进localStorage.kami_core_db,同时挂到window.kami_core_db供其他脚本直接读。先setItem后挂 window;setItem抛错(比如存储配额超限)会跳进构建失败分支,window.kami_core_db不会更新。触发:详情全部拉完后|命令window.kami_core_db|存储kami_core_db - 构建元信息 + 成功后清除失败面包屑 — 构建成功后写一份元信息 {
builtAt时间, count 条数, degraded 降级数},供辅助健康看板判断库龄、规模和快照新旧,并删除之前的失败面包屑。degraded 按 harmony==null 统计;代码观察:详情拿到了但缺 harmony 的记录也会被算进降级数。整段 try/catch 静默。触发:落盘成功后|存储kami_core_db_meta(写),kami_db_last_fail(删除)|来历供辅助健康看板判断快照新旧 - 构建完成提示 + base 字段填充率仅观测 — 打「🎉 构建完成」,然后统计
vioBase/harmBase/powBase三项都非 null 的只数,打「base 字段填充: X/总数 只(用于findKillerCandidates杀手候选筛选)」。两条都走log(),会进缓冲。触发:落盘成功后|来历提醒用户杀手候选筛选的数据覆盖面 console.table表格输出全库仅观测 — 构建完成后用console.table把整个库按表格打到控制台,供人工核对。裸 console,不进缓冲。触发:构建完成后|命令console.table(kami_core_db)- 构建失败捕获 + 失败面包屑 — 构建过程中任何未捕获异常都只打「❌ [构建失败]」,不影响页面运行,并写失败面包屑。
console.error之后再 log 一条进缓冲;面包屑 {at, stage:'构建失败: '+错误信息前 80 字},写入失败静默。触发:buildCoreDb内抛异常(含手动重建时 network 未就绪)|参数错误信息截断 80 字|存储kami_db_last_fail|来历console.error不进共享缓冲,所以要补 log;面包屑跨刷新存活,供看板排查 - 控制台查看当前库与备份库 — 可以在控制台直接查看构建出的库和构建前的旧库,用来对照新旧。
window.kami_core_db是当前库,可用console.table(kami_core_db)表格化浏览;window.kami_core_db_old是构建前备份(本页本次构建时才挂,刷新后需从localStorage.kami_core_db_old读)。触发:命令|命令window.kami_core_db;console.table(kami_core_db);window.kami_core_db_old|存储kami_core_db,kami_core_db_old|来历误建可回退:把kami_core_db_old手动复制回主 key 即可恢复
3.6 主流程、防重复与备份
- 数据库存储 key 约定 — 精简库主 key 固定为
kami_core_db,构建前旧库备份 key 固定为kami_core_db_old,全脚本只在这一处定义。这两个名字是整个脚本套件读库的约定入口,核心/辅助脚本都按它读取。触发:脚本载入时求值|参数CORE_DB_KEY='kami_core_db';CORE_DB_OLD_KEY='kami_core_db_old'|存储kami_core_db,kami_core_db_old|来历改名会让核心/辅助脚本全部读不到数据库 - 主流程串联 — 按顺序执行:固定等待 → 展开 Party/切眼睛 → 等卡片渲染(非致命)→ 打「检查
window.network是否加载完毕」→ 等链上 API(致命)→ 构建数据库。fixedWaitWithCountdown(40000+jitter(0,2000)) →startSequence()→waitForKamiList(60000)→waitForReady(30000)→ sleep(jitter(300,1800)) →buildCoreDb()。触发:脚本载入后自动执行一次|参数40000+0~2000ms;60000;30000;300~1800ms - 构建前再随机错峰 300~1800ms — API 就绪后不马上构建,先再随机等 300~1800ms。sleep(
jitter(300,1800)),注释原话「再错峰一丢丢」。触发:waitForReady通过后|参数jitter(300,1800)ms|来历多账户/多标签页错峰,降低限流 - 主流程异常捕获 + 失败面包屑 — 主流程任何异常(包括 API 未就绪抛错)都只打「❌ [主流程异常]」,不影响页面,并写失败面包屑。
console.error之后再 log 进缓冲;面包屑 stage='主流程异常: '+错误信息前 80 字。触发:main 内抛异常|参数错误信息截断 80 字|存储kami_db_last_fail|来历console.error不进缓冲;面包屑跨刷新供看板排查 - 无论成败都清除「构建中」标记 — main 的 finally 里把
window.__KAMI_CORE_DB_BUILDING__置 false。成功或异常都会执行。触发:main 结束时|来历保证下次载入或手动重建不会被残留标记卡住 - 失败面包屑
kami_db_last_fail跨脚本约定 — 各类失败都在localStorage写一条 {at 时间戳, stage 失败阶段} 面包屑,刷新后仍在,供辅助健康看板查看;构建成功时自动清除。四种 stage:'眼睛切换超时(已刷新自救)'、'加载按钮超时(已刷新自救)'、'构建失败: …'、'主流程异常: …';后写覆盖前写;写入一律 try/catch 静默。触发:各失败点自动写;构建成功时删除|存储kami_db_last_fail|来历刷新自救会清空控制台,面包屑能跨刷新留下失败现场 - 防重复执行全局标记
__KAMI_CORE_DB_BUILDING__— 用 window 全局标记保证同一页面同一时刻只跑一份自动构建流程;标记已为 true 时,本份脚本静默退出。已为 true 就 return:不启动 main,也不挂rebuildKamiCoreDb;否则置 true 后启动。标记挂在 window 上,刷新自动清零,不会永久锁死;套件内其他脚本也可读它,判断库是否正在重建。代码观察:启动横幅和版本检查在守卫之前已执行,重复注入时仍会各跑一次。触发:脚本载入(IIFE 末尾)|来历防止重复注入/重复触发时两份流程并发写库、互相覆盖 - 自动运行一次构建 — 脚本载入并通过防重复守卫后,自动调用
main()完整跑一轮构建流程。main()不 await,直接触发。触发:脚本载入 - 控制台命令
rebuildKamiCoreDb()— 在控制台手动全量重建精简数据库,同样会先备份旧库、写元信息、打表格。window.rebuildKamiCoreDb直接等于buildCoreDb:跳过固定等待、Party 展开、眼睛切换和就绪检测,要求window.network已就绪,否则内部报错走构建失败分支并写面包屑。代码观察:它不检查也不设置__KAMI_CORE_DB_BUILDING__,自动构建进行中手动调用会两份并发写库。触发:命令|命令rebuildKamiCoreDb()// 控制台直接调用,返回 Promise|存储kami_core_db,kami_core_db_old,kami_core_db_meta,kami_db_last_fail|来历数据明显异常或更换登录账户后用来全量重建
四、轻量杀手监控
轻量杀手监控盯一份杀手 kami 名单,纯 API 轮询、不读页面;杀手出现在你的房间或隔壁时告警,并联动核心紧急停采。
4.1 名单与配置
- 油猴运行范围与注入方式 — 脚本只在
kamigotchi.io各子域页面运行,页面空闲后注入,不申请任何 GM 特权。@match https://*.kamigotchi.io/*,@run-at document-idle,@grant none(直接跑在页面上下文,才能读window.network与核心脚本挂出的全局函数);整体包在 use strict 的 IIFE 里。触发:打开游戏页面时由篡改猴自动注入|版本v1.2.14 - 杀手 kami 名单(预填默认名单) — 一个写死在源码里的杀手 kami 编号数组,是本脚本唯一必填的配置:名单上有谁就盯谁。v1.1.8 起预填了默认名单,装上就有基础保护。共 42 只:长期敌情观察 34 只;曾是自家杀手、已送人的 3 只(对方可能拿来攻击);2026-07-08 用
scanTopPredators实测的全网 EERIE/SCRAP/INSECT/NORMAL 四种手型最强杀手各 1 只;以及清算我方 #1129 的真凶 #1232。维护方法:扫描结果提示「未在监控名单」时把编号追加进来,保存后刷新页面生效。名单为空也不报错,只是空转、没有保护。名单里混进自己的 kami 不用手动剔除,建映射时会自动识别。触发:脚本加载时读取;建映射、每轮核对杀手是否换主、隔壁核对都会遍历它|参数KILLER_KAMI_INDEXES=42 项|命令addKiller(index)/removeKiller(index)/listKillers()|来历#1232 当晚不在名单里,漏防导致 #1129 被清算;补录后映射自动反查到了正确的主人账户(它和另一个杀手账户名字只差数字 0/1)|版本v1.1.8 起预填 - 名单活引用暴露给辅助脚本 — 把名单数组的活引用挂到
window.__killerWatchList,辅助脚本scanTopPredators用它核对「最强杀手是否已在监控名单」。挂的是数组本身,不是拷贝,所以addKiller/removeKiller改完立刻能看到。触发:脚本加载即挂出;辅助脚本扫描时读取 - 轮询间隔带随机抖动 — 每轮检测的间隔是 2 分钟基础时间加上 0~60 秒随机数,也就是每 120~180 秒查一次。
getRandomInterval返回KILLER_CHECK_BASE加 [0,KILLER_CHECK_RANDOM) 内的随机毫秒数,每轮单独抽一次。调小间隔发现得更快但请求更多;抖动设 0 会变成固定节奏(不建议)。触发:每轮检测结束后,由scheduleNextCheck调用|参数KILLER_CHECK_BASE=120s;KILLER_CHECK_RANDOM=60s|来历避免固定节奏形成请求指纹,也让多开的页面请求错开 - 邻居预警开关 — 开启时,杀手到了相邻地块也会告警并触发紧急停采(提前一格反应);关闭则只有同房间才告警。关闭后位置判断直接短路,不调用
isNeighborRoom,省掉房间坐标查询;自家杀手的「部署在隔壁」日志也会变成普通的「远程作战中」。触发:每轮检测判断每个杀手玩家和每只自家杀手的位置时|参数NEIGHBOR_WARNING=true|来历赶在杀手进场前先把 kami 收回来 - 沉寂杀手 24 小时活跃度门槛(常量) — 杀手主人超过 24 小时没有任何链上动作,就判定为「不玩了」,同房间和隔壁两个分支都不再为它停采。门槛用的是账户的
time.last(移动、部署、清算都会刷新它),同房间和隔壁两个分支共用;实际判定逻辑见轮询主函数里的对应条目。触发:位置检测中,同房间或隔壁命中时先检查|参数KILLER_INACTIVE_HOURS=24|来历清算必须主人本人在场操作。数据上,真不玩的杀手都是几十天没动作(37/95/102 天),在玩的是几分钟到 2 小时前刚动过,两者之间差距巨大。本方案替代了 v1.1.9~1.1.10 的「沉寂 10 天只降低警报频率、仍然停采」,降频用的状态变量也一并删掉了|版本1.1.11 沉寂杀手24h活跃度门槛 - 隔壁杀手静默分钟数(常量与规则定案)测试线独有 — 隔壁的杀手主人连续 10 分钟既没清算也没移动,并且名下 kami 都不在我方节点 → 判定不在杀人时段,不停采。只作用于隔壁分支。清算信号是
stats.kills(全图累计清算数);移动信号优先用账户 MOVE 计数,读不到退回time.action,再读不到就只看清算。静默计时存在localStorage,刷新页面不清零。以下情况一律照常停采:kami 在我方节点采集、查不清、击杀数读不到、前端冻结。触发:位置检测中隔壁分支命中、且主人过了 24 小时门槛时|参数NEIGHBOR_QUIET_MINUTES=10|存储kami_killer_activity(killsSince/moveCount/moveSince)|来历0913 实盘:隔壁一个杀手一直在它自己的房间采集,击杀数全天没涨、全天没移动,7 小时触发了 176 次紧急停采。用户 0913 定案:只看 move 和 liquidate,不看部署/采集/停采|版本测试版1.2.11 隔壁规则 → 1.2.12 击杀静默 → 1.2.13 只看移动+清算(1.2.14 移动改 MOVE 计数) - Feed 监控参数(历史保留)已停用 — 给已停用的 Feed 监控模块提供节奏和阈值:巡检/保活/重试周期、清算计数滑动窗口、触发条数、退出安全模式的冷却时间。只有手动执行
startFeedMonitor()后才会用到这些参数;默认不启用。触发:仅手动启动 Feed 监控后|参数FEED_CHECK_INTERVAL=3min;LIQUIDATE_WINDOW_MS=5min;LIQUIDATE_COUNT_TRIGGER=2;SAFE_COOLDOWN_MS=15min|命令startFeedMonitor()/stopFeedMonitor() - 跨脚本安全模式全局标记已停用 — 在 window 上初始化三个全局标记:
__killerDetected(是否处于安全模式)、__lastKillerTime(最近一次清算消息的时间)、__liquidatedTimestamps(窗口内清算时间戳列表),供核心脚本读取。用「已有值 || 默认值」的方式初始化,脚本重载时不会把已有状态清零。只有 Feed 模块会写这几个标记,所以 Feed 停用后,核心脚本「检测到杀手自动切安全停采线」这条路径默认不会生效。触发:脚本加载时初始化
4.2 主人映射与换主核对
启动时把每只杀手 kami 反查到主人账户,之后按"主人位置"轮询,API 调用大幅减少;杀手换账户时每轮自动核对重建。
主人映射
- 杀手名字附
accountId短标仅观测 — 所有显示杀手账户名的日志和警报,统一写成「名字(id前7位…)」,形近名、同名的不同账户一眼能分开。id 不是字符串或不足 7 位时显示「(无id)」,名字缺失显示「?」。只改显示文本,不影响任何判定、轮询和告警触发。触发:映射、换主、警报、安全、失败、listKillers等所有带杀手名的日志|来历2026-07-08 #1129 命案的根因之一是名字撞脸:真凶和整晚被监控的挂机杀手账户名只差数字 0/1,肉眼看错导致审计误判,连补录都补错了账户。名字不可靠,一切以accountId为准|版本v1.1.10 起(1.1.10 名字防撞脸id标注) - 杀手 kami → 主人玩家映射(省 API 核心设计) — 启动时把名单里每只 kami 反查到它的主人账户,按
playerId归组成 {playerName, kamis[]}。之后每轮只查「玩家在哪」,一个玩家通常带多只杀手,查一次就全覆盖。反查链:kamis.getByIndex(带 harvest) →entities.get(entity).OwnsKamiID →accounts.getByID。每次重建先清空映射和自家杀手列表再重算,归组时查重。完成后打「✅ 映射建立完成!N 个外部杀手 kami → M 个 player」,有失败再打失败数。触发:startKillerMonitor启动时;每轮开头发现映射缺失时兜底;手动rebuildKillerMap();换主核对发现变化时|命令rebuildKillerMap()|来历单轮 API 调用从「名单长度×3」降到「玩家数+自家杀手数」 - 省 API 效果日志仅观测 — 映射建完后打一行「📈 优化效果: 每次检测 名单数×3 次API → 玩家数+自家杀手数 次API」。触发:每次建映射成功落地时
- 映射结果挂到 window 供调试仅观测 — 把敌方映射挂到
window.__killerPlayerMap,自家杀手清单挂到window.__killerSelfOwned,方便在控制台直接查看。只在映射成功落地时更新;身份未就绪提前返回时不更新这两个变量。触发:建映射成功结束时 - 单只反查归类共用函数测试线独有 — 建映射和补查共用同一个
__mapOneKiller做反查和归类,返回 true=并入敌方映射,null=自家杀手,false=查询失败。整段逻辑从原来的循环体原样搬出,没有重写。查询异常只打「✗ Kami N 查询失败」并返回 false,不往外抛;并入映射和自家列表都先查重。触发:buildKillerPlayerMap遍历名单时;每轮补查时|来历两处各写一份,口径必然会漂移|版本测试版1.2.8 R3 - 自家杀手自检(id 或名字任一匹配) — kami 的主人 id 或名字和自己一致,就判为自家杀手,移入单独的自家列表(不进敌方映射),日志「🛡️ Kami N 是自己名下 → 移入自家杀手单独追踪」。id 和名字双重比对,防某个字段缺失时漏判;两边都必须是真值才参与比较,空串、0、undefined 一律不参与,避免「两个空值相等」的误判。触发:每只 kami 反查归类时|来历自家杀手部署在外面作战时,账户位置代表不了 kami 位置,必须单独用
harvest.node.index跟踪|版本1.2.5(真值判断) - 自家身份读取带退避重试 — 建映射前先读自己的
accountId和账户名;读不到最多试 5 次,间隔 0/2/4/6/8 秒(累计约 20 秒),拿到任意一个有效身份就停。钱包地址先读connectedAddress.value_,读不到再读 .value,再用accounts.getByOperator查账户。每次等待前打「⏳ 账户数据尚未就绪,Ns 后重试(第 i/5 次)」,异常打「获取自己accountId失败」,成功打「👤 当前账户: 名字 (id=…)」。触发:每次buildKillerPlayerMap开头|参数重试间隔 [0,2000,4000,6000,8000]ms|来历页面刚加载时账户数据经常还没准备好,直接放弃会让整轮映射失去自检依据;有上限的重试比无限等待安全。用户 0911 定案|版本1.2.6 账户未就绪重试 - 空串身份视为没拿到 — 自己的账户 id 和名字必须是非空字符串才采信,空串一律按「没拿到」处理,宁可全部按外部杀手处理。把 ?? 空值合并改为显式判断「字符串且长度>0」。触发:读取自家身份时|来历0911 实盘 bug:账户未就绪时自己的名字是空串,查不到名字的敌方主人也是空串,两个空串相等,某玩家名下 3 只敌方杀手被误判成自家移出监控,监控玩家数从 23 掉到 21,整个会话对该玩家失明,同时自己的位置读成了 deadzone 房间 0|版本1.2.5 自家杀手误判修复
- 客户端空账户 id「0」过滤测试线独有 — 账户 id 形如 '0' 或 '0x000…'(全零)时,按没拿到处理。用正则 ^(0x)?0+
$(不分大小写)排除。触发:读取自家身份时|来历客户端的NullAccountid 是字符串 '0'(07-24、09-11 日志里的「当前账户: (unknown) (id=0)」就是它),能通过非空守卫,导致自家杀手被并成敌方且不再自愈|版本测试版1.2.11 - 身份未就绪时映射不落地 — 5 次重试后仍认不出自己,就不置「映射已建立」标记,直接返回,下一轮轮询开头自动重建。打橙色「⚠️ 未能识别当前账户 → 本轮映射不落地,下一轮自动重建;期间全部 kami 按外部杀手处理(保守)」,并同步
__killerMonitorState.mappingBuilt=false。代码实际:此时映射表里已经全员按敌方归组,启动或兜底建映射后本轮会带着这张表继续检测,自家杀手可能被当成敌方轮询;只有换主核对路径有快照回滚。触发:buildKillerPlayerMap身份闸门处|来历连自己是谁都没查到时,自家/敌方分类不可信,而且常伴随位置读成 deadzone;宁可多建一次,也不带病跑完一整个会话|版本1.2.5 身份未就绪不落地映射 - 首查失败登记待补查测试线独有 — 建映射时反查失败的 kami 不再从数据结构里消失,而是登记进待补查集合(失败次数记 1),并打橙色日志列出编号,提示每轮轮询开头自动重试。失败的编号进
__killerPendingIds,元数据 {fails:1,nextAt:0};自家杀手(返回 null)既不算成功也不算失败。触发:buildKillerPlayerMap遍历名单时|来历1.2.8 之前,失败的 kami 连缓存都不进,而映射标记完全不看失败数照样置真,此后整个会话不再重建。如果它是主人在名单里的唯一一只,这个玩家就从威胁扫描中彻底消失,和 0708 #1129「真凶不在名单」命案一模一样(用户 0912 复现)|版本测试版1.2.8 R3 首查失败永久失明 - 通过身份闸门后才缓存身份测试线独有 — 只有通过身份闸门后,才把自己的 id 和名字缓存起来,给补查和换主核对复用。缓存赋值放在「身份未就绪 return」之后;提前返回的路径绝不会把缓存写成 null。触发:
buildKillerPlayerMap身份闸门通过后|来历放在闸门之前会缓存成 null,补查就会把自家杀手判成敌方,然后轮询自己的账户位置,走到哪都误触发同房间警报加紧急停采(1.2.5 同类事故的镜像)|版本测试版1.2.8 R3 - 自家杀手注册
MY_KILLER_KAMIS— 自家杀手编号同步加入全局 Setwindow.MY_KILLER_KAMIS。核心脚本(部署/XP 喂食)和辅助脚本(升级/技能重置)据此跳过这些 kami,不把作战中的杀手当普通采集 kami 处理。集合已被其他脚本建好就复用,不是 Set 才新建;加入前查重,并打「🛡️ N 只自家杀手单独追踪:#…」和「📌 同步 K 只到MY_KILLER_KAMIS」。代码实际:只加不删,自家杀手送人后,刷新页面前仍留在集合里。触发:建映射成功落地时|存储window.MY_KILLER_KAMIS(内存,跨脚本共享)
补查与换主核对
- 映射缺失兜底重建 — 映射还没建立、或者映射表为空时,本轮先打「⚠️ 杀手映射尚未建立,先建立映射...」并建映射,再继续检测。触发:每轮开头|来历防止在映射就绪前空跑
- 补查闸门:身份没缓存就不补查测试线独有 — 有待补查的 kami、但自家身份缓存为空时,本轮不补查,打「已停用 [补查] 身份未缓存,本轮跳过 N 只待补查」。id 和名字缓存都为空才跳过。触发:每轮开头,待补查集合非空时|来历宁可继续缺这几只,也不能在没有自检依据时把自家杀手并成敌方,否则会轮询自己的位置,走到哪都误触发紧急停采|版本测试版1.2.8 R3
- 首查失败的 kami 每轮补查测试线独有 — 到期的待补查 kami 在每轮开头重新反查,成功就并入映射、移出队列,并打绿色「✅ [补查] N 只已补回监控名单,剩余待补 M 只」。前 5 次失败每轮都重试(水合竞态通常一两轮就自愈),之后退到 10 分钟一次;这个退避只为减少日志噪音,不为省成本。每轮不限数量(42 只全查也只是 126 次本地读)。代码实际:重建映射不清空补查队列,已经在重建中成功的,会在下一轮再补查一次才移出(有查重,不会重复登记)。触发:每轮开头,身份已缓存且有到期待补查项时|参数前 5 次每轮重试;之后
nextAt=+10min|来历补查是三次同步本地 ECS 读,没有网络开销也不花 gas,所以刻意不照搬核心 1.1.22 那套 3~30 分钟的链上复读退避(那是给索引器分钟级滞后设计的)。照抄代码形状而不看语义,是本项目栽过六次的坑|版本测试版1.2.8 R3 首查失败永久失明 - 杀手换账户每轮核对测试线独有 — 每轮位置检测前逐只读名单 kami 当前的主人,和映射不一致就整表重建,日志「🔄 [杀手换主] N 只杀手 kami 已换账户:#编号 旧主人 → 新主人 → 重建映射」。取主人的方式和建映射完全一样(
kamis.getByIndex→entities.get.OwnsKamiID),重建直接复用buildKillerPlayerMap,不另写一套归类逻辑。待补查的和还没归属的 kami 跳过;读到空主人也跳过,不改映射。都是本地 ECS 读,无网络无 gas。触发:每轮开头,映射已建立时|来历映射原先只在启动或页面刷新(约 45~50 分钟一次)时建立,期间换主就会一直盯旧账户。07-08~09-13 日志共 35 次换主、涉及 16 只 kami,常常整批换,或在大号小号间来回换。配合隔壁静默规则尤其要紧:旧主人正好在隔壁挂机时,会被判成「人不在」而不停采(用户 0913 提)|版本测试版1.2.11 杀手换账户每轮核对 - 换主核对:读失败的 kami 记为归属不明测试线独有 — 本轮读不到当前主人的 kami,不改映射,但会记下来;隔壁静默核对时把它当作「可能属于这个隔壁玩家」一起核对。读取异常时加入本轮的
__ownerReadFailed集合,这个集合每轮重新创建。触发:换主核对读取异常时|来历归属不明就按可能是它的来处理,往照常停采的方向收紧|版本测试版1.2.11 - 换主核对:自家杀手只按 id 比较测试线独有 — 自家杀手是否换了主人,只比
accountId;身份缓存里没有 id(只有名字)时不判定。缓存无 id 时直接判为「未变化」。触发:换主核对遍历到自家杀手时|来历避免把自家杀手误判成换主,触发不必要的重建|版本测试版1.2.11 - 换主重建失败回滚旧映射测试线独有 — 重建前先给旧映射和自家列表拍快照;如果重建时账户身份读不到(映射没落地),就回滚到旧映射、恢复「已建立」标记,打「⚠️ [杀手换主] 重建映射时账户身份未就绪 → 已回滚到旧映射,下一轮再试」。回滚同时更新
__killerMonitorState.mappingBuilt和window.__killerPlayerMap。触发:换主核对发现变化并重建、而重建没落地时|来历重建失败会留下一张「全员敌方」的表,本轮继续用会轮询到自己,判成同房间,误触发紧急停采(复核 0913 离线复现)|版本测试版1.2.11
4.3 位置轮询与停采判定
每 2~3 分钟查一轮:杀手主人在同房间 → 警报 + 停采;在隔壁 → 预警 + 停采;主人 24 小时没动作、或隔壁 10 分钟既没杀人也没移动 → 不停。
每轮流程
- 轮询起点时间戳(健康看板判活)仅观测 — 每轮开头把当前时间写进
window.__killerMonitorState.lastRoundAt,辅助脚本健康看板据此判断监控还活着。触发:每轮checkKillerPositions开头|存储window.__killerMonitorState.lastRoundAt - 自己位置取不到就整轮跳过 — 自己的房间号是 null 时,打「⚠️ 无法获取自己位置,跳过本次检测」,直接结束本轮,不拿错误的基准硬判。只对 null 生效;调度链照常排下一轮。触发:每轮取完自己位置后
- 轮次开头位置日志仅观测 — 打「🔍 我的位置:【房间名】(房间N),检测 M 个杀手玩家...」。触发:每轮取到自己位置后
- 按玩家聚合查位置 — 每轮只遍历杀手玩家,每人查一次
accounts.getByID拿账户所在房间,名下全部杀手就都覆盖了,不逐只查 kami。每个玩家先做活跃度追踪,再取杀手房间信息,然后判断同房间和隔壁。触发:每轮检测|来历一个玩家往往带多只杀手,这是省 API 的核心 - 每轮至多触发一次停采、命中后不提前结束 — 命中威胁后用本轮标记保证同一轮只调一次紧急停采;之后再命中只打「(本轮已触发过紧急停采,不重复触发)」,并继续查完整个名单。同房间和隔壁两个分支共用
stopTriggeredThisRound标记,命中后 continue 而不是 return。触发:同房间或邻居分支命中时|来历早年命中就 return,排在命中者后面的玩家整轮零监控,活跃度快照也不更新,循环后面的自家杀手追踪也执行不到|版本1.1.9 起 - 单个玩家查询失败不中断 — 查某个杀手玩家时出任何异常,只打「⚠️ 查询玩家 某某 失败: 原因」,继续查下一个。每个玩家单独包 try/catch。触发:每轮遍历玩家时
- 本轮收尾语与 API 计数仅观测 — 本轮触发过停采打「✅ 检测完成,本轮已触发紧急停采 (本次API请求: N)」,否则打「✅ 检测完成,暂时安全 (本次API请求: N)」。代码实际:API 计数只统计玩家账户查询、隔壁核对的 kami 查询、自家杀手查询,不含换主核对、补查、房间信息查询。触发:每轮检测结束|来历命中威胁后不再提前 return,所以收尾语要按是否触发过停采分开写,避免「暂时安全」误导
同房间与沉寂杀手
time.last读不到按活跃处理 — 杀手主人的time.last缺失或不是正数时,「未动作小时数」记为 0(=活跃),照常停采。用位置轮询已经拿到的账户对象自带字段,不额外查询。触发:同房间或隔壁判定前|来历拿不准就照常停采,绝不因为数据缺失漏防|版本1.1.11 沉寂杀手24h活跃度门槛- 同房间沉寂杀手跳过停采 — 杀手主人就在我房间,但超过 24 小时没有链上动作 → 判定「不玩了」,只打一行灰色「⚪ [沉寂杀手/跳过] 某某 在【房间】(你房间),但主人
X.Xh未动作 → 判不玩,跳过停采」,不告警不停采,继续查其他玩家。门槛是KILLER_INACTIVE_HOURS;活跃度快照在跳过前已经更新。触发:同房间命中时|参数KILLER_INACTIVE_HOURS=24|来历真不玩的杀手几十天没动作,在玩的几分钟到 2 小时前刚动过;本方案替代了 v1.1.9~1.1.10「沉寂降频仍停采」|版本1.1.11 沉寂杀手24h活跃度门槛 - 同房间警报与紧急停采 — 活跃杀手的主人在我房间 → 打红底大横幅「🚨🚨🚨 [同房间警报] 杀手 某某 在【房间】!」,列出杀手位置、我的位置、名下被监控的杀手、活跃度,并触发紧急停采(每轮最多一次)。同房间只看 24 小时门槛,不受隔壁静默规则影响;行为和 v1.1.10 之前一致。触发:同房间命中且主人 24 小时内有动作
- 隔壁沉寂杀手跳过停采 — 杀手主人在隔壁,但超过 24 小时没有链上动作 → 打「⚪ [沉寂杀手/跳过] 某某 在隔壁【房间】,但主人
X.Xh未动作 → 判不玩,跳过停采」,继续查其他玩家。门槛和判定跟同房间分支完全一样。触发:隔壁命中时(先于静默规则)|参数KILLER_INACTIVE_HOURS=24|版本1.1.11 沉寂杀手24h活跃度门槛
隔壁预警与静默放行
- 邻居预警与提前停采 — 活跃杀手在隔壁、且没被静默规则放行 → 打橙底横幅「⚠️⚠️ [邻居预警] 杀手 某某 在隔壁【房间】!」,列出杀手位置、我的位置、名下监控杀手、活跃度,并触发紧急停采(每轮最多一次)。触发:隔壁命中,没被 24 小时门槛或静默规则放行时|参数
NEIGHBOR_WARNING=true|来历提前一格反应,赶在杀手进场前把 kami 收回来 - 隔壁静默:击杀数读不到或未静默就照常停采测试线独有 — 击杀静默分钟为 null(读不到,或击杀数为 0)或者不足 10 分钟时,不进入静默判断,直接走邻居预警停采,也不打静默日志。移动信号为 null(MOVE 计数和
time.action都读不到)时,只按清算判断(等同 1.2.12 的行为)。触发:隔壁分支、过了 24 小时门槛后|参数NEIGHBOR_QUIET_MINUTES=10|版本测试版1.2.12 击杀静默 → 1.2.13 移动+清算 - 隔壁静默:击杀静默但刚移动过仍停采测试线独有 — 击杀数已经 ≥10 分钟没涨,但移动信号不到 10 分钟 → 判定人在线,打「⚠️ [隔壁静默/仍停采] …击杀数 N 分钟没涨,但 X前刚移动过 → 人在线,照常停采」。触发:隔壁分支击杀已静默时|参数
NEIGHBOR_QUIET_MINUTES=10|来历用户 0913 定案,只看移动和清算两个动作|版本测试版1.2.13 - 隔壁静默:前端冻结时不启用测试线独有 — 核心脚本写的
window.__frontendFrozen为 true 时,击杀和移动读数可能都是旧值,「静默」会被高估 → 不启用静默规则,打「…但前端数据冻结中(读数可能是旧值)→ 照常停采」。只读核心脚本传感器写的全局变量;严格等于 true 才算冻结。触发:隔壁分支击杀静默、移动也静默或读不到时|存储window.__frontendFrozen(读,核心脚本写)|版本测试版1.2.12 - 隔壁静默:核对范围组装测试线独有 — 要核对的 kami =「它名下已登记的监控 kami」+「名单里还没归属任何主人的 kami」(待补查的、
addKiller后还没归属的、本轮换主读失败的)+「账户对象如果带 kamis 列表,它名下的全部 kami」。账户列表为空时日志注明「列表为空」;账户对象不带列表时注明「只核对了名单内的」;列表非空、但有元素读不出正整数编号 → 直接判拿不准,照常停采,不再发起核对。触发:隔壁分支、击杀和移动都静默且前端未冻结时|来历归属不明就按可能是它的算;没登记进名单的 kami 挂在我方节点也要能看到(getByID返回的账户是否带 kamis 没有文档依据,所以缺席时如实说明)|版本测试版1.2.13 - 隔壁静默:核对通过则不停采,否则照停测试线独有 — 核对到的 kami 都不在我方节点 → 打「⚪ [隔壁静默/不停采] 某某 在隔壁…击杀数 N 分钟没涨(累计 K)、M 分钟没移动,核对到的 kami 都不在我方节点(核对明细)→ 判不在杀人时段,不停采」并跳过;否则打「⚠️ [隔壁静默/仍停采] …但 具体原因 → 照常停采」。核对调用外面再包一层 try,异常按拿不准处理;核对里查 kami 的次数计入本轮 API 请求数。明细按状态统计,例如「在节点75采集×1,RESTING×2」。触发:隔壁分支、击杀和移动都静默且前端未冻结时|参数
NEIGHBOR_QUIET_MINUTES=10|来历0913 实盘:隔壁杀手 7 小时触发 176 次紧急停采,而它的 kami 一直在自己房间采集|版本测试版1.2.13(1.2.14 移动信号改 MOVE 计数) - 隔壁核对:状态白名单测试线独有 — 逐只查 kami 状态:去空白转大写后,HARVESTING 才去看节点;只有 RESTING/DEAD/LISTED/721_EXTERNAL 算不在我方节点;其他任何值(空、对象、数字、未知状态)都算查不清 → 照常停采。状态统计进核对明细;编号先去重再逐只查。触发:隔壁静默核对时,对每只 kami|参数
OFF_NODE=[RESTING, DEAD, LISTED, 721_EXTERNAL]|来历只有 HARVESTING 才看节点:RESTING/DEAD 状态下的harvest.node是上一次采集的旧节点(0913 探针:#6245 在 RESTING,节点仍指房间 10),代表不了现在的位置。复核 0913 要求全部往「照停」方向收紧|版本测试版1.2.11 隔壁规则;1.2.12 - 隔壁核对:编号与节点号严格正整数测试线独有 — kami 编号、我方房间号、采集节点号都必须能转成正整数,否则算查不清 → 照常停采。接受正整数 number、大于 0 的
BigInt、去空白后是纯数字的字符串;0(deadzone)、布尔、空白、数组等一律不行。我方房间号认不出直接返回「我方房间号无法识别」;在采集但节点号无效返回「在采集但读不到有效节点号」。触发:隔壁静默核对开始时和每只 HARVESTING kami 上|来历节点号读harvest.node.index:核心脚本生产代码在用,0913 探针 10 只 HARVESTING 样本 10/10 都读到了|版本测试版1.2.11 - 隔壁核对:节点号与房间号交叉校验测试线独有 — 采集中的 kami 如果同时读得到
harvest.node.roomIndex,而且和node.index不相等 → 查不清,照常停采。roomIndex为 undefined 或 null 时跳过这项校验。触发:隔壁静默核对,每只 HARVESTING kami|来历0913 探针里node.index与node.roomIndex全部相等,对不上说明数据异常|版本测试版1.2.11 - 隔壁核对:kami 正在我方节点采集就必停测试线独有 — 核对到任意一只杀手 kami 正在我方房间的节点采集 → 返回「正在我方节点采集,人走一步进房即可清算」→ 照常停采。触发:隔壁静默核对,节点号等于我方房间号时|来历清算需要:杀手 kami 已在同节点采集、账户在同房间、冷却已过。kami 已经挂在我方节点时,主人从隔壁走一步回来就能立刻清算;挂着等待期间
time.last不会刷新,所以「很久没动」说明不了它不在|版本测试版1.2.11 - 隔壁核对:查询失败或异常一律按查不清测试线独有 — 单只 kami 查询抛异常、名单为空、或函数整体异常,都返回 away:false(查不清)→ 照常停采。遇到第一个拿不准的就立即返回,不再查剩下的;返回值带已发起的查询次数 calls。触发:隔壁静默核对全过程|来历fail-closed:拿不准就照停。停采这一侧宁可多发,不能漏停|版本测试版1.2.11
远处杀手与自家杀手
- 远处杀手安全日志仅观测 — 杀手不在同房间也不在隔壁时,打「✅ 某某(id) 在【房间】(房间N),安全(活跃度标注)」。触发:每轮每个不构成威胁的杀手玩家
- 自家杀手部署位置追踪仅观测 — 逐只查自家杀手本体:不在 HARVESTING 或没有节点号,记「自家杀手 #N: 状态(未部署)」;和我同一地块,记「ℹ️ 与我同地块…(不攻击自家 kami,无威胁)」;在隔壁或其他地块,记「🗡️ 部署在…远程作战中」。这一段永远不告警、不停采。开头打「🛡️ 检测 N 只自家杀手的实际部署位置...」;单只查询失败只记日志继续;查询次数计入本轮 API 数。触发:每轮玩家循环结束后,自家杀手列表非空时|来历游戏没有友军误伤,自己的杀手不会攻击自家 kami;杀手部署在外时人未必跟着,账户位置不可靠
- 自家杀手节点号字段修正测试线独有仅观测 — 自家杀手的部署地块改读
harvest.node.index;值为空或不是有限数字时按未部署处理。触发:每轮自家杀手追踪时|来历原来读 harvest 顶层的roomIndex,但官方客户端 Harvest 顶层没有这个字段(v1.2.3 写代码时可能有,后来客户端改了),导致自家杀手在采集时永远被记成「未部署」;改成核心脚本 7 处同款的读法,0913 探针 10/10 读到|版本测试版1.2.12
位置工具
- 房间名与坐标缓存 — 按房间号缓存 {index, name, location},同一个房间只查一次 API,之后直接读缓存。查询失败时返回兜底对象(名字用「Room N」,坐标为 null),不写缓存,下次还会重查;坐标为 null 时邻居判断自动按「不相邻」处理。触发:每轮检测里取自己房间、杀手房间、自家杀手部署房间,以及邻居判断时|来历房间是静态数据,缓存能进一步省请求
- 同层四邻判定 — 判断两个房间在地图上是否相邻:同一层、上下左右紧挨着才算,斜角和跨层都不算。满足 |dx|+|dy|=1 且 dz=0 算相邻。同一个房间返回 false(交给同房间分支处理);任一房间缺坐标返回 false(宁可漏判邻居,也不拿残缺数据误报)。触发:
NEIGHBOR_WARNING开启时,每轮对每个杀手玩家和每只自家杀手的房间调用 - 读取自己所在房间 — 通过钱包地址查到自己的账户,取
roomIndex,作为每轮比对的基准。任一环节抛异常就返回 null,调用方整轮跳过。代码实际:只读connectedAddress.value_,没有 .value 回退;账户存在但roomIndex是 undefined 或 0(deadzone)时不会返回 null,这一轮也不会跳过。触发:每轮checkKillerPositions取位置时
4.4 杀手活跃度追踪
- 活跃度标注(最近动作/累计击杀)仅观测 — 每轮给每个杀手主人生成一段标注,例如「最近动作: 31分钟前|累计击杀: 84」,附在安全日志和警报日志里,用来区分长期挂机和正在活跃。直接复用位置轮询已经拿到的账户对象,不额外查询。
time.last不是数字时显示「未知」;stats.kills是数字时才附上击杀数。触发:每轮检测时,对每个杀手玩家调用一次|命令listKillers()|存储kami_killer_activity|版本v1.1.4 起 - 杀人增量深红横幅仅观测 — 本轮读到的累计击杀数比上次存档多时,打一条深红底横幅:「🩸🩸 [杀手活跃] 某某 自上次检查后清算了 N 只 kami!(累计 K)」。本次和上次都是数字、且本次更大才触发。不管杀手在哪个房间都广播;只提醒,不影响停采。触发:每轮活跃度追踪时|存储
kami_killer_activity.kills|来历两次轮询之间击杀数上涨,是最可靠的活跃信号(不管是人操作还是脚本操作)|版本v1.1.4 起 - 房间变化标注仅观测 — 上次存档的房间号和本次账户房间号不同时,在标注里加「刚移动: 房间A→B」。两个房间号都必须是数字才比较。注释说明:两次轮询之间走过去又走回来看不出房间变化,要靠
time.last变新才能发现。触发:每轮活跃度追踪时|存储kami_killer_activity.room|版本v1.1.4 起 - 击杀静默计时(
killsSince)测试线独有 — 记录「当前击杀数第一次被看到的时刻」,算出击杀数已经多少分钟没涨,给隔壁静默规则用,并在标注里附「击杀静默 N 分钟」。只有同时满足以下条件才沿用旧计时:击杀数和上次一样、两次都 >0、killsSince是数字且不晚于当前时间(防时钟异常);首次观测、上次是零、击杀数变了,都从现在重新计时。击杀数读不到或为 0 时记 null,隔壁静默规则不启用、照常停采。代码实际:击杀数为 0 的杀手永远不会被判为静默。触发:每轮活跃度追踪时|存储kami_killer_activity.killsSince|来历计时存进localStorage,页面自动刷新后不清零|版本测试版1.2.12 击杀静默 - 移动信号:账户 MOVE 计数(主用)测试线独有 — 读杀手主人账户的 MOVE 计数,算出计数已经多少分钟没涨,作为「多久没移动」。通过
explorer.data.get(accId,'MOVE')读取,必须是大于 0 的有限数字才采信,读取包在 try 里,异常就忽略。计数和上次一样且moveSince可信才沿用旧计时,否则从现在算起(首次观测等于 0 分钟,也就是当作刚移动过,照常停采)。标注附「移动计数 N(M 分钟没涨)」,计数涨了再附「刚移动 K 步」。触发:每轮活跃度追踪时|存储kami_killer_activity.moveCount/moveSince|来历用户 0913 指出合成也会刷新time.action;合约LibRoom.logMove每移动一次计数 +1,合成和道具都不碰它,所以计数涨了就是真的走了|版本测试版1.2.14 - 移动信号回退:
time.action→ 只看清算测试线独有 — MOVE 计数读不到时,退回用账户time.action算距今多少分钟;time.action也读不到就记 null,隔壁规则只看清算。回退时清空moveSince,等计数恢复可读后重新计时。标注写「最近动作(time.action): X前(MOVE 计数读不到)」。time.action会被合成、账户道具刷新,所以这个回退偏保守,更容易照常停采。触发:每轮活跃度追踪、MOVE 计数读不到时|存储kami_killer_activity.moveSince(清空)|来历合约里只有AccountMove/AccountUseItem/Craft/KamiCastItem四个系统会写time.action;部署、停采、收取、喂食、清算只写time.last|版本测试版1.2.14 - 活跃度快照持久化 — 每个杀手主人的快照 {name, room, last, kills, at,
killsSince,moveCount,moveSince} 存进独立的localStorage键,页面自动刷新后击杀基线和静默计时都不会丢。读写都包 try/catch:JSON 损坏或存储不可用时读回空对象,写失败静默忽略,不影响主流程。触发:每轮活跃度追踪结束时写入;listKillers()读取|命令listKillers()|存储kami_killer_activity|来历用独立键,不和其他数据冲突|版本v1.1.4 起 - 秒数转人话仅观测 — 把秒数格式化成「25秒前 / 31分钟前 / 5.1小时前 / 82.1天前」。<90 秒显示秒,<5400 秒显示分钟,<48 小时显示一位小数的小时,更长显示一位小数的天;负数或非数字显示「未知」。触发:活跃度标注、隔壁静默日志、
listKillers调用
4.5 联动紧急停采
- 联动核心脚本紧急停采 — 检测到威胁时调用核心脚本挂出的
window.emergencyStopHarvest(),触发一轮紧急停采扫描(只停触及停采线/危险线的 kami,不是全部收回),日志「⚡ 触发紧急停采!原因: …」。调用后不等结果,发出即返回 true。原因文本带杀手名、id 短标和房间。触发:同房间警报或邻居预警分支命中,且本轮还没触发过时 - 调用者自报身份 — 调用紧急停采前,先把
window.__kamiCallSource设为 'killer_monitor',让核心脚本的wrapManual知道这次是杀手监控自动触发的,不是用户手动操作。套件内约定的全局变量。触发:每次触发紧急停采前|存储window.__kamiCallSource(跨脚本约定) - 核心脚本缺席时降级为只告警 — 找不到
emergencyStopHarvest时,只打「❌ 未找到emergencyStopHarvest函数,核心脚本可能未加载」并返回 false;本脚本可以单独运行,但只有告警,没有自动停采。代码实际:调用方不看返回值,本轮仍会标记「已触发过停采」,收尾照样打「本轮已触发紧急停采」。触发:触发紧急停采而核心脚本没加载时
4.6 Feed 监控(已停用)
曾经靠监听聊天窗 Feed 频道的清算消息感知全图击杀风向,现在默认关闭,代码只作保留。
- Feed 清算消息监控(整体)已停用 — 监听游戏 Chat→Feed 频道里的 liquidated 消息,感知「全图正在发生击杀」的整体风向,不需要事先知道杀手是谁;结果只体现为全局标记
__killerDetected,本模块自己不触发紧急停采。现在已经不再监控 Feed,代码只是保留,默认不启动,也不建议启用;杀手保护由位置轮询承担。核心脚本「检测到杀手自动切安全停采线」依赖这个标记,所以该路径默认不生效。触发:只能手动startFeedMonitor()启动|参数见 Feed 监控配置|命令startFeedMonitor()/stopFeedMonitor() - 窗口计数进入安全模式已停用 — 周期巡检时清掉滑出 5 分钟窗口的清算时间戳;窗口内达到 2 条就置
__killerDetected=true,记下时间,打红字「🚨 [Feed监控] 检测到杀手活动!切换到安全模式」。已经在安全模式里就不重复置位、不重复打日志。触发:Feed 监控运行时,每 3 分钟巡检一次|参数LIQUIDATE_WINDOW_MS=5min;LIQUIDATE_COUNT_TRIGGER=2 - 冷却期满退出安全模式已停用 — 安全模式中,距最后一次击杀满 15 分钟才退出,打绿字「✅ [Feed监控] 安全冷却结束,恢复贪婪模式」;没满就打「⏱️ 安全模式中,N 分钟后恢复」。冷却恢复只由巡检定时器驱动。触发:Feed 监控巡检时|参数
SAFE_COOLDOWN_MS=15min|版本1.1.12 饥饿模式改名贪婪模式(仅文案) - 收到清算消息的处理已停用 — 每条 liquidated 消息记一个时间戳,并刷新「最近一次杀手活动」时间(冷却重新计时),清掉过期记录,打「🔪 检测到击杀消息 (5分钟内第 N 条)」;达到阈值且还没进安全模式就立即置位。刻意不解析击杀者和受害者名字,只统计条数。代码实际:日志里的「5分钟内」「≥2」是写死的文字,不跟随常量变化。触发:历史补扫或
MutationObserver捕捉到清算消息时|参数LIQUIDATE_WINDOW_MS=5min;LIQUIDATE_COUNT_TRIGGER=2|来历hover 取名会在页面上残留 tooltip,干扰界面 - 自动打开 Chat 并切到 Feed 标签已停用 — 检查 #chat 是否可见;不可见就点 #chat-button 里的按钮并等 1.5 秒;再找 #feed 区域,点文字为 Feed 的按钮并等 0.8 秒(按钮 disabled 说明已经在 Feed 页)。找不到按钮、窗口没打开、找不到 #feed 都打日志并返回 false;用 Promise 版 delay 等 DOM 渲染。触发:
startFeedMonitor时,以及 Chat 窗口保活发现被关掉时|参数点击后等待 1500ms / 800ms - 启动失败 3 分钟后自动重试已停用 — 打不开 Feed、找不到 #feed、或找不到 #feed 内容区(第二个子元素)时不放弃,打「3分钟后重试」,之后自动再启动一次。已在运行时再次启动只提示、不重入。代码实际:重试定时器没有保存句柄,
stopFeedMonitor取消不了还没到点的重试;运行标记要等 observer 挂好才置真。触发:startFeedMonitor失败时|参数FEED_CHECK_INTERVAL=3min - 历史清算消息补扫已停用 — 启动时先扫一遍 Feed 内容区里已有的 span,文本含 liquidat、且内部带 img 的才算击杀消息,逐条补记时间戳。补扫到的条数打「📊 扫描到 N 条历史击杀消息」。触发:
startFeedMonitor成功打开 Feed 后|来历要求带 img,避免把纯文本误计成击杀 MutationObserver实时捕捉已停用 — 在 Feed 内容区挂MutationObserver(childList+subtree),新增的元素节点文本含 liquidat 就打「🆕 检测到新的 liquidated 消息」并计数。只处理元素节点,跳过文本和注释节点。代码实际:实时路径只看文本,不像历史补扫那样要求带 img。触发:Feed 监控运行期间,DOM 有新增节点时- 定时巡检与 Chat 窗口保活已停用 — 两条
setInterval:一条每 3 分钟跑一次状态巡检(进入/退出安全模式);另一条每 3 分钟检查 Chat 窗口,被关掉就打「🔄 Chat 窗口已关闭,重新打开...」并自动重开。触发:Feed 监控启动成功后周期运行|参数FEED_CHECK_INTERVAL=3min|来历MutationObserver依赖 Chat 窗口存在才收得到新消息 - Feed 专用自身位置读取(未调用)已停用 — 一个更容错的「取自己房间号」函数(可选链、地址为空返回 null),当前主流程没有调用,只作备用保留。触发:无调用方
startFeedMonitor()/stopFeedMonitor()Feed 监控启停已停用 — 手动启动或停止已停用的 Feed 清算消息监控。停止时断开 observer、清掉巡检和保活两条定时器、复位运行标记,打「🛑 [Feed监控] 已停止」。不建议启用。停止取消不了启动失败后还没到点的 3 分钟重试。触发:命令|命令startFeedMonitor()/stopFeedMonitor()
4.7 启动、日志与命令
- 统一日志前缀与时区仅观测 — 所有
log()输出都加「[杀手监控][YYYY-MM-DD HH:MM:SS]」前缀,在套件多个脚本的混合输出里好区分。TZ_OFFSET_HOURS='auto' 时跟随浏览器本地时区,也可以写死数字(8=UTC+8、-5、5.5=UTC+5:30);时区偏移只在脚本加载时算一次。触发:脚本内每次调用log()|参数TZ_OFFSET_HOURS='auto'|版本v1.1.3 起(时区设置) - 彩色样式前缀插入仅观测 — 带 %c 样式的日志,会把前缀插到 %c 之后,让颜色和背景作用于整行,包括前缀。首参是以 %c 开头的字符串、且至少有 2 个参数时走样式分支;否则前缀作为普通参数输出,不会因为缺样式参数而报错。触发:每次调用
log() - 日志写入跨脚本共享缓冲仅观测 — 每条 log 同时往
window.__kamiLogBuffer推一份带前缀的纯文本,供核心脚本saveKamiLogs导出存档、辅助脚本健康看板反查特征。缓冲不存在就新建,整段包在 try 里。代码实际:log()只去掉首参开头的 %c,后面的 CSS 样式参数会被当成文本一起拼进缓冲(clog 才会剥掉 CSS)。触发:每次调用log()|存储window.__kamiLogBuffer(内存,跨脚本共享) - clog:控制台直出也入日志仅观测 — 原本直接
console.log的内容(启动命令清单等)改走 clog:控制台照常彩色输出,同时写一份纯文本副本进共享日志缓冲。__plainArgs数首参里有几个 %c,就跳过后面几个 CSS 字符串参数;对象用 JSON 序列化(失败退 String);多行文本拆开逐行入库,每行带 [杀手监控][时间] 前缀;全程 try 包住,不影响主流程。本文件里只有启动横幅用到它。触发:启动横幅输出时|存储window.__kamiLogBuffer|来历直接console.log的内容在控制台看得到,但保存的日志文件里一行都没有,事后没法复盘。用户 0911 定案:全部走日志 - ctable:表格输出也入日志仅观测 —
console.table输出表格(失败退回console.log),同时往日志缓冲写「(表格 N 行)」和每一行的 JSON。传入的不是数组就包成单元素数组;每行 JSON 化失败时退 String。代码实际:本文件范围内只有定义、没有调用,是预留的。触发:本文件内未被调用|存储window.__kamiLogBuffer|来历同上,用户 0911 要求所有输出都进日志 - 版本标签防腐三常量测试线独有 — 版本号、线名、构建时间收成三个常量(
SCRIPT_VERSION='1.2.14'、SCRIPT_LINE='测试版'、SCRIPT_BUILT),启动日志、命令横幅、版本检查都引用它们,不会再各改各的。SCRIPT_BUILT由发布器打包时注入真实发布时间(版本没变就沿用旧日期);本地没发布时保持「(本地未发布)」占位,所以日志里看到这几个字,说明这份不是从GitHub装的。触发:脚本加载时|参数SCRIPT_VERSION=1.2.14;SCRIPT_LINE=测试版;SCRIPT_BUILT=(本地未发布)|来历0913 实盘日志里三处硬编码不同步(辅助 @version 已是 1.2.10 启动日志却打 1.2.8,监控 1.2.8 打 1.2.7),两次误判「beta 没装全」「监控没更新」。日志撒谎比没有日志更糟|版本测试版1.2.9 版本标签防腐 + 构建时间自报 - 加载完成醒目横幅仅观测 — 脚本一加载就打一条绿底白字 16px 的大横幅:「✅ 轻量杀手监控-测试版 v1.2.14(构建时间)已加载,等待启动...」。文字引用三个版本常量。触发:脚本加载立即输出|版本1.1.14 启动横幅醒目化
- 启动时对比
GitHub最新版本仅观测 — 加载 8 秒后拉取GitHub测试线的meta.js,解析 @version 和 @x-release-date,和本机版本比较。一样就打「已是GitHub最新」,落后打橙色提示去篡改猴「实用工具→检查用户脚本更新」,领先打「本地开发版」。请求 URL 带时间戳参数,并用 cache:no-store 防缓存;解析不到版本号就打一行「无法解析,跳过」。版本比较按点分逐段数字比,缺的段按 0 算。触发:脚本加载后 8 秒,只执行一次|参数延迟 8000ms|来历延迟 8 秒是为了避开启动时的拥挤|版本1.1.13 版本检查 - 本机首次运行时间记录仅观测 — 记下「本机第一次运行这个版本的时刻」,当作篡改猴的安装或更新时间,写进版本检查日志。按「脚本名+版本号」建键:读不到就写入当前时间;存储本身异常时显示「未知」。触发:版本检查初始化时|存储
kami_ver_seen_轻量杀手监控_1.2.14|来历脚本没法直接读篡改猴的更新时间,只能用首次见到该版本的时刻近似|版本1.1.13 版本检查 - 版本检查失败 24 小时降噪仅观测 — 拉取
GitHub失败时(通常是游戏页运行时注入的 CSP 禁止外联,属正常),24 小时内只打一次说明文案,其余时候静默。localStorage记上次提示的时间,满 86400000ms 才再打一次并刷新时间戳;localStorage本身出错时退回每次都打。文案说明这不影响篡改猴自动更新,并给出手动检查路径。触发:版本检查 fetch 抛异常时|参数节流窗口=24h|存储kami_vercheck_csp_note_轻量杀手监控|来历游戏 SPA 运行时注入 CSP 的 connect-src 白名单,在游戏页永远拉不到GitHubraw;旧版每次加载页面都打一行,等于刷屏|版本1.1.15 版本检查降噪 setTimeout链式自调度 — 每轮检测完才排下一轮(随机间隔),日志「⏱️ 下次检测: N 秒后 (X 分钟)」;不用固定的setInterval。每轮间隔独立随机,网络慢时也不会堆积并发。排定前和回调触发时都会复查运行标记,停机后残留的回调直接退出。代码实际:定时回调没包 try/catch,如果某轮抛出未捕获异常,后续轮次不会再被排定。触发:启动后首轮完成时,以及每轮检测完成后- 启动防重入 — 监控已在运行时再次启动,只打「⚠️ API杀手监控已在运行中」,不会重复启动。检查
__monitorRunning运行标记。触发:调用startKillerMonitor时|命令startKillerMonitor() - 运行态快照挂 window仅观测 —
window.__killerMonitorState= {running,mappingBuilt,lastRoundAt},辅助脚本健康看板直接读(闭包变量外部读不到)。启停时更新 running,建映射和回滚时更新mappingBuilt,每轮开头更新lastRoundAt。触发:脚本加载时创建,运行中持续更新|存储window.__killerMonitorState(跨脚本读取) - 启动配置回显测试线独有仅观测 — 启动时打「🛡️ 启动 API 杀手位置监控」,并列出基础间隔、随机范围、名单 kami 数量、邻居预警开关;邻居预警开启时再说明隔壁静默不停采的条件。触发:
startKillerMonitor成功进入时|版本测试版1.2.14 - 启动流程:建映射 → 立即首检 → 排定循环 — 启动后先建映射,接着马上检测一轮,再进入随机间隔的循环。依次等待完成。如果建映射时身份没就绪,首检开头会发现映射没落地,再建一次。触发:
startKillerMonitor|命令startKillerMonitor() startKillerMonitor()启动位置监控 — 在控制台手动启动 API 杀手位置监控:建映射、立即检测一轮、进入随机间隔循环。脚本加载 150 秒后也会自动调用。已在运行时只提示不重入;停止后再启动会重新建映射。触发:命令|命令startKillerMonitor()stopKillerMonitor()停止位置监控 — 清掉运行标记和待执行的定时器,句柄置 null,打「🛑 API杀手监控已停止」。正在进行中的一轮会跑完,但不会再排下一轮(调度前复查运行标记)。触发:命令|命令stopKillerMonitor()checkKillerPositions()立即检测一轮 — 不等定时器,马上完整执行一轮检测(补查、换主核对、位置判定、按需紧急停采)。代码实际:没有并发锁,手动执行和定时轮次重叠时,两轮各有自己的「本轮已触发」标记,可能各触发一次紧急停采。触发:命令|命令checkKillerPositions()rebuildKillerMap()重建映射 — 就是buildKillerPlayerMap:重新读自家身份、清空并重建敌方映射和自家杀手列表、注册MY_KILLER_KAMIS。改完名单后用。代码实际:身份没就绪时不会回滚到旧映射(只有换主核对路径有快照回滚),映射标记置为未建立,下一轮开头会再建。触发:命令|命令rebuildKillerMap()addKiller(index)临时添加杀手测试线独有 — 把一个 kami 编号加进内存里的名单,刷新页面后失效(长期生效要改源码)。1.2.11 起同时登记进补查队列,下一轮轮询开头自动反查主人并归属,不用手动重建映射。已在名单里就提示「已在杀手列表中」直接返回;成功后打「✅ 已添加…(下一轮轮询自动归属主人;也可立即运行rebuildKillerMap())」和当前列表。注释写「运行期改动后需执行rebuildKillerMap()才进入监控」,代码实际是下一轮自动补查(前提是身份已缓存)。查重是严格相等,数字和字符串会被当成不同。触发:命令|命令addKiller(12345)|版本测试版1.2.11(登记补查队列)removeKiller(index)临时移除杀手 — 从内存名单中删掉一个编号,打「✅ 已移除」,提示运行rebuildKillerMap()更新映射,并打印当前列表;刷新页面后失效。不在名单里就提示「不在杀手列表中」直接返回。代码实际:不清理映射和补查队列,重建前这只 kami 和它的主人仍在监控中。触发:命令|命令removeKiller(12345)listKillers()查看名单、映射与活跃度 — 打印当前名单数组;映射已建立时列出「名字(id短标): Kami …」;再从活跃度快照列出每个主人的「最近动作|累计击杀|房间」。活跃度读的是上次轮询存下的localStorage快照,不发任何查询;快照为空就不打活跃度段。触发:命令|命令listKillers()|存储kami_killer_activity(读)|版本v1.1.4 起附活跃度- 页面加载 150 秒后自动启动 — 脚本加载时打「⏳ 等待 150 秒后自动启动 API 杀手监控...」和「ℹ️ Feed 监控为已停用的历史功能,默认关闭」,150 秒后自动启动位置监控;Feed 监控不会自动启动。一次性
setTimeout。触发:脚本加载后 150 秒|参数INITIAL_DELAY=150s|来历给游戏页面、链上接口和核心脚本留足初始化时间,避免一启动就建映射失败;延迟与核心脚本的启动节奏配合 - 核心脚本缺席提示仅观测 — 到点启动前检测
window.emergencyStopHarvest,不存在就打「⚠️ 核心脚本未检测到,杀手监控仍会运行但无法触发紧急停采」,然后照常启动。不管核心脚本在不在都启动,缺席时降级为只告警。触发:自动启动时 - 命令清单横幅仅观测 — 脚本加载时打印一次说明横幅:脚本名、版本和构建时间;映射优化说明(每轮只查玩家位置、自家 kami 用
harvest.node.index追踪并同步MY_KILLER_KAMIS,要配合核心和辅助脚本才能完整跳过部署/升级/reset);各组命令清单(位置监控、已停用的 Feed 监控、杀手列表管理)。全部走 clog,控制台彩色输出的同时写进共享日志缓冲。触发:脚本加载立即输出(早于 150 秒自动启动)|存储window.__kamiLogBuffer
附录
附录 A 控制台命令总表
所有命令都在游戏页按 F12 打开控制台后直接输入。标红的高频/高危命令在各脚本启动横幅里也会列出。标〔内部/调试〕的是挂在 window 上、主要供脚本之间互相调用的内部函数,列出来方便排查,日常不需要手动调用。
核心脚本
| 命令 | 用途 | 所在节 |
|---|---|---|
emergencyStopHarvest() |
紧急停采:HP 触及停采线(≤ 停采线+1%)的和所有 STARVING 的 kami,健康的不动 | 1.4 紧急停采 |
stopCurrentRoom() |
一键停采当前地块所有采集中的 kami(危险的先停),换地块前用 | 1.3 停采 |
stopMinorityForTransfer() |
一键停采所有 minority(不适配当前账户主地形)的采集中 kami,方便转给别的账户;依赖辅助脚本,自动跳过杀手 | 1.3 停采 |
resumeDeploy() |
上面两个命令会暂停自动部署 10 分钟,用它提前恢复 | 1.5 部署 |
setKamiMode('greedy') / setKamiMode('normal') |
切换停采模式:贪婪(停采线 5%)/ 正常(清算线 +3%),写入后 1.5 秒自动刷新生效;测试版1.2.50 起旧名 'starving' 已移除,输入会被拒收并提示改用 greedy(本地残留的旧值仍会自动迁移) |
1.10 模式与停采线 |
getKamiMode() |
查看当前模式 | 1.10 模式与停采线 |
GREEDY_THRESHOLD / STARVING_THRESHOLD |
只读:贪婪模式停采线(5),后者是旧名别名 | 1.10 模式与停采线 |
DEFAULT_THRESHOLD_NORMAL / DEFAULT_THRESHOLD_OTHER / MAX_THRESHOLD |
只读:没有清算线数据时的默认停采线(65 / 76)与封顶(80) | 1.10 模式与停采线 |
showBlockedKamis() |
查看部署黑名单 | 1.5 部署 |
showStopBlockedKamis() |
查看停采黑名单 | 1.4 紧急停采 |
clearBlockedKamis() |
清除全部黑名单(部署 + 停采) | 1.5 部署 |
clearStopBlockedKamis() |
只清停采黑名单 | 1.4 紧急停采 |
clearFeedFails() |
清除喂食失败冷却记录,失败的 kami 立刻可重喂 | 1.6 喂食 |
clearStarvingStuck() |
清除"饿死喂食卡住"黑名单 | 1.6 喂食 |
setStarvingFeedChannel('queue'\|'raw') |
切换饿死救援喂食通道;不带参数打印当前值(默认 queue) | 1.6 喂食 |
clearXPPotionFed() / clearFortifiedFed() |
清除 XP 药水已喂记录(两个名字同一函数,后者是旧名) | 1.9 合成与 XP 药水 |
feedXPPotionNow() |
立即喂一轮 XP 药水(只喂不合成;LT>70% 且休息中的 kami) | 1.9 合成与 XP 药水 |
showMyKillers() |
查看自家杀手 kami 清单(XP 喂食、部署会跳过) | 1.9 合成与 XP 药水 |
showGasRules() |
打印各动作的 gas 消耗规则说明(静态文字) | 1.12 gas 账本与余额 |
showGasReport() |
gas 真值报告:按动作分类、24h/3d/7d/30d、日均、revert 白烧、余额续航 | 1.12 gas 账本与余额 |
getTxLockStatus() |
查看紧急锁 / 普通锁当前持有者和持有时长 | 1.11 TX 双锁与 nonce |
hasEmergencyLock() / setEmergencyLock() / releaseEmergencyLock() |
紧急锁的查询 / 上锁 / 释放(主要供脚本间调用,手动慎用) | 1.11 TX 双锁与 nonce |
tryAcquireNormalLock() / releaseNormalLock() |
普通锁的抢占 / 释放(供辅助脚本共用) | 1.11 TX 双锁与 nonce |
waitForNormalLockRelease() / waitForEmergencyRelease() |
等普通锁 / 紧急锁让路 | 1.11 TX 双锁与 nonce |
setStopTxChannel('mud'\|'raw') |
切换停采发送通道:mud=MUD 队列(统一 nonce)/ raw=原始签名器;不带参数查当前 | 1.11 TX 双锁与 nonce |
setKeepAlive('on'\|'off') |
人化保活开关(默认 on) | 1.14 保活 |
startMyKamiDeathMonitor() / stopMyKamiDeathMonitor() |
启动 / 停止自家 kami 死亡监控 | 1.13 死亡监控与停摆检测 |
checkMyKamiDeath() |
立即扫一次自家死亡 kami(发现就批量复活) | 1.13 死亡监控与停摆检测 |
showFrontendSensor() |
打印前端冻结传感器读数(区块号是否停滞、连接、rAF 帧率等) | 1.15 日志、调试与诊断 |
saveKamiLogs() |
手动把当前日志保存成文件(不刷新页面) | 1.15 日志、调试与诊断 |
kamiDebugOn({ parse: true }) |
开调试日志并刷新;flags 可选 parse/start/stop/api/dom/feed | 1.15 日志、调试与诊断 |
kamiDebugOff() |
关调试日志并刷新 | 1.15 日志、调试与诊断 |
syncKamiDb() |
手动增量同步精简数据库(只补不删,自动保存) | 1.16 数据库恢复与自愈 |
window.__reviveSentAt.clear()〔内部/调试〕 |
清空复活 15 分钟防重发登记,让刚发过复活 tx 的 kami 立刻可以重发(确认丝带没被消耗时才用) | 1.7 复活 |
__evalFrontendFrozen(expectedIntervalMs)〔内部/调试〕 |
立即评估一次前端是否冻结,结果写入 window.__frontendFrozen 并返回;主循环和紧急停采入口会自动调用 |
1.15 日志、调试与诊断 |
__kamiGasRecord(action, kamiIds, txOrHash)〔内部/调试〕 |
gas 账本记账入口(即核心的 _gasLedgerRecord),辅助脚本发洗点/加点/升级/合成/复活 tx 后调用;手动调用会真的往账本写一条,慎用 |
1.12 gas 账本与余额 |
安装说明() / showKamiInstall() |
打印四件套安装链接、更新机制、回退办法 | 1.17 杂项工具 |
辅助脚本
| 命令 | 用途 | 所在节 |
|---|---|---|
upgradekamis() |
一键升级所有休息中的 kami(等级 + 技能加点 + 必要时洗点) | 2.4 升级与技能管理 |
checkAllKamiSkills() |
全量技能体检,找出非标准加点(只读,不发 tx) | 2.4 升级与技能管理 |
getRespecPotionCount() |
查询洗点药水(Respec Potion)库存 | 2.4 升级与技能管理 |
STANDARD_SKILLS |
只读:标准技能集合,供比对 | 2.4 升级与技能管理 |
scanTopPredators() |
全网扫描最强杀手(四桶帕累托前沿 + 主人 + 哨兵,20~60 秒),结果供精确清算线用 | 2.3 杀手扫描 |
showTopPredators() |
查看当前生效的威胁档案与扫描状态 | 2.3 杀手扫描 |
findKillerCandidates() |
扫描杀手候选(默认 vio≥23、harm≥15、pow≤12,可传 {vio, harm, pow} 自定义) |
2.3 杀手扫描 |
refreshPreciseLT() |
按当前档案重算全库精确清算线并写回 | 2.2 清算线显示与计算 |
computePreciseLTForRecord(harmony, bodyAffinity, ratio, shift, maxhp)测试线独有 |
对单条记录按活跃档案算最坏清算线(供核心统一口径) | 2.2 清算线显示与计算 |
compareLT() / compareLT(true) |
对照兜底线与实战线的清算线差异(默认列差异最大 20 只,true 列全部) | 2.2 清算线显示与计算 |
setPredatorEquipCapacity(n) |
设定假定的杀手装备容量(默认 1) | 2.2 清算线显示与计算 |
setPredatorEquipHeadroom(shift, ratio) |
设定杀手装备余量(默认 0.10 / 0;0,0 全关) |
2.2 清算线显示与计算 |
kamiAnalyze() / analyzeKamiTerrainFit() |
地块适配分析:当前房间属性 + kami 四类分类 | 2.7 地块适配分析 |
getRoomAffinity(i) / getRoomName(i) / getCurrentRoomInfo() |
查询房间地形属性 / 名称 / 当前房间完整信息 | 2.7 地块适配分析 |
isKamiSuitable(body, hand, terrain) / isKamiSuitableForRoom(body, hand, affinities) |
判断 kami 是否适合某地形 / 某房间 | 2.7 地块适配分析 |
showHealth() |
代码健康看板:哪个模块该跑没跑 | 2.8 代码健康看板 |
autoCraft() |
手动触发一次自动合成 | 2.5 自动合成与步长读取 |
startAutoCraft() / stopAutoCraft() |
启动 / 停止自动合成定时任务 | 2.5 自动合成与步长读取 |
getStaminaFromDOM() |
从页面读实时步长 | 2.5 自动合成与步长读取 |
AUTO_CRAFT_CONFIG / CRAFT_PRIORITY |
自动合成配置与配方优先级表(可在控制台临时改) | 2.5 自动合成与步长读取 |
__refreshLT()〔内部/调试〕 |
立即重跑一次卡片 LT 显示与危险标红(即 combinedUIUpdate) |
2.2 清算线显示与计算 |
__getMinorityKamis()〔内部/调试〕 |
返回 minority 分类结果 {majority, minorityKamis, excludedKillers, …},供核心 stopMinorityForTransfer() 调用 |
2.7 地块适配分析 |
__classifyKamisByAffinity()〔内部/调试〕 |
返回按地形亲和给 kami 分类的原始数据(调试用) | 2.7 地块适配分析 |
精简数据库
| 命令 | 用途 | 所在节 |
|---|---|---|
rebuildKamiCoreDb() |
手动全量重建精简数据库(先备份旧库) | 3.6 主流程、防重复与备份 |
window.kami_core_db_old |
查看构建前备份的旧库 | 3.5 构建数据库 |
轻量杀手监控
| 命令 | 用途 | 所在节 |
|---|---|---|
startKillerMonitor() / stopKillerMonitor() |
启动 / 停止 API 杀手位置监控(页面加载 150 秒后会自动启动) | 4.7 启动、日志与命令 |
checkKillerPositions() |
立即完整检测一轮(补查、换主核对、位置判定、按需紧急停采) | 4.7 启动、日志与命令 |
rebuildKillerMap() |
重建杀手 kami → 主人映射 | 4.7 启动、日志与命令 |
addKiller(index) / removeKiller(index) |
临时加 / 删杀手(只在本次页面有效;添加后下一轮自动反查主人) | 4.7 启动、日志与命令 |
listKillers() |
查看名单、主人映射与活跃度快照 | 4.7 启动、日志与命令 |
startFeedMonitor() / stopFeedMonitor() |
启停 Feed 清算消息监控(已停用的历史功能) | 4.6 Feed 监控(已停用) |
附录 B 可调参数表
只列对使用者有意义的参数。改参数要改脚本源码(篡改猴编辑器里),自动更新会覆盖本地修改。
核心脚本 · 停采线与模式
| 常量 | 默认值 | 含义 |
|---|---|---|
LT_STOP_MARGIN |
3 | 停采线 = 精确清算线 + 3%;调大更保命但采得短 |
MAX_THRESHOLD |
80 | 停采线封顶 80%,保证每周期至少采 20 个百分点 |
GREEDY_THRESHOLD |
5 | 贪婪模式停采线 5% |
DEFAULT_THRESHOLD_NORMAL / DEFAULT_THRESHOLD_OTHER |
65 / 76 | 读不到清算线时的默认停采线(normal 体质 / 其他体质) |
LIQUIDATE_WINDOW_MS / LIQUIDATE_COUNT_TRIGGER / SAFE_COOLDOWN_MS |
5 分钟 / 2 条 / 15 分钟 | 杀手判定窗口(依赖已停用的 Feed 监控,默认不生效) |
SINGLE_STOP_DANGER_DELTA |
-1 | 省 gas 单停:HP 低于停采线 1 个点以内继续等凑批,再低就立刻停 |
核心脚本 · 停采与紧急停采
| 常量 | 默认值 | 含义 |
|---|---|---|
STOP_TRIGGER_HARD |
36 | 批量修剪的目标 / 预警线:候选超过它开始预警 |
STOP_TRIGGER_ACT |
42(=36+6) | 候选达到它才硬触发修剪,只停超额部分到剩 36 |
TARGET_BATCH_MIN |
6 | 紧急停采凑批门槛:无危险 kami 且不足 6 只本轮不发 |
EMERGENCY_DANGER_DELTA |
-1 | 紧急停采里判"危险"的 delta 线 |
EMERGENCY_CONFIG.CRITICAL_DELTA / HIGH_RISK_DELTA / DANGER_DELTA |
-20 / -10 / 0 | 极危 / 高危 / 危险分级(HP 低于停采线多少个点) |
EMERGENCY_CONFIG.CONFIRM_CONCURRENCY |
8 | 预检、状态查询并发数 |
EMERGENCY_CONFIG.BASE_WAIT_MS / PER_BATCH_WAIT_MS |
9000 / 1500 | 确认等待 = 基础 + 批数×增量;基础值已被"最近 30 笔确认耗时 p90"自适应取代,9000 是冷启动值 |
EMERGENCY_CONFIG.MAX_ROUNDS / ROUND_INTERVAL_MS / BATCH_INTERVAL_MS |
3 / 3000 / 500 | 最多重试轮数、轮间隔、批间隔 |
EMERGENCY_CONFIG.GAS_FULL_EXEC_PER_KAMI / GAS_FULL_REVERT_PER_KAMI |
1,200,000 / 300,000 | 每只均摊 gas 判"像全执行" / "全 revert"的阈值 |
EMERGENCY_CONFIG.INVOCATION_HARD_CAP_MS |
180000 | 停采阶段硬上限 3 分钟,到点收尾放锁 |
EMERGENCY_CONFIG.FEED_PHASE_CAP_MS / FEED_TAIL_GRACE_MS |
90000 / 30000 | 紧急停采前救援喂食阶段上限 90 秒 + 补发宽限 30 秒 |
EMERGENCY_CONFIG.CIRCUIT_BREAK_STREAK |
2 | 连续 2 批全 revert / 全不可停 → 本次调用熔断 |
COOLDOWN_DEFER_MIN |
3 | 冷却中待重试不足 3 只且都不危险时推迟到下轮 |
SUSPICIOUS_STARVING |
10 | 首扫 STARVING 超过 10 只视为可疑,等 5 秒重扫 |
STOP_BLOCK_THRESHOLD / STOP_BLOCK_COOLDOWN_MS |
3 次 / 30 分钟 | 停采连续失败 3 次才拉黑,30 分钟自动解除 |
STOP_BACKOFF_TABLE_MS |
7s, 20s, 45s, 90s, 180s, 300s | 停采发出后的退避复读时刻表 |
STOP_BACKOFF_SCAN_INTERVAL_MS / STOP_BACKOFF_SCAN_MAX |
15 秒 / 20 只 | 复读调度器扫描周期、单轮最多复读只数 |
STOP_BACKOFF_MASSLAG_MIN |
3 | ≥3 只同时卡在 90 秒以上档 = 群体组件滞后,冻结计数 |
STOP_BACKOFF_GASLIKELY_FULL_MS / STOP_BACKOFF_STALE_MS |
30 分钟 / 30 分钟 | gas 像执行过的证据窗;30 分钟仍未确认的条目移除并告警 |
KAMI_ACTION_COOLDOWN_SEC |
180 | kami 操作冷却(游戏常量,time.last + 180 秒) |
DOM_INCOMPLETE_THRESHOLD / SCALE_ANOMALY_RATIO |
0.5 / 0.1 | DOM 预检:不完整率超 50%、或数量不到上次 10% 视为页面没渲染好 |
MAX_TRIES / GAP_MS(紧急停采 DOM 预检) |
6 / 3000 | 页面未就绪时最多重扫 6 次、间隔 3 秒 |
MAX / RETRY_MS(盲扫自重试) |
3 / 20000 | 盲扫后最多自重试 3 次、间隔 20 秒 |
STATE_CHECK_DELAY_MS / RETRY_DELAY_MS |
5000 / 5000 | 普通停采、部署入场静默期与失败重试间隔(调小会增加 nonce 冲突) |
核心脚本 · 部署、喂食、复活
| 常量 | 默认值 | 含义 |
|---|---|---|
MIN_DEPLOY_BATCH |
6 | 部署凑批门槛:候选不足 6 只跳过本轮 |
UI_SETTLE_MS |
25000 | 批量操作后等前端状态刷新的时间 |
DEPLOY_BLOCK_CONFIG.FAIL_THRESHOLD / AUTO_CLEAR_MS |
2 次 / 30 分钟 | 部署连续失败 2 次拉黑,30 分钟解除 |
DEPLOY_PAUSE_AFTER_STOPALL_MS |
10 分钟 | 一键停采 / 转移停采后的部署暂停窗口 |
MIN_GAP_BURGER / MIN_GAP_HONEY / MIN_GAP_APPLE |
50 / 75 / 150 | 日常低血喂食:HP 缺口至少这么多才用对应食物(芝士汉堡 / 蜜露鳞 / 金苹果) |
BATCH_SIZE / FAIL_THRESHOLD(低血喂食) |
3 / 2 | 每批喂 3 只;一批失败 ≥2 只就熔断 |
FEED_MAX_FAILS / FEED_FAIL_COOLDOWN_MS |
2 次 / 5 分钟 | 喂食连续失败 2 次进冷却 5 分钟 |
STARVING_STUCK_THRESHOLD / STARVING_STUCK_COOLDOWN_MS |
2 次 / 30 分钟 | 喂过 2 次仍 STARVING 判定卡住,30 分钟后允许重试 |
STARVING_STUCK_TIME_MS / STUCK_RETRY_COOLDOWN_MS / STUCK_RETRY_SHORT_MS |
24 小时 / 6 小时 / 30 分钟 | 饿死超 24 小时的 kami 的重试间隔(确认喂进去用长间隔,没确认用短间隔) |
REVIVE_MAX_PER_ROUND |
30 | 单轮最多复活 30 只,防独占 TX 锁 |
REVIVE_RETRY_COOLDOWN_MS |
15 分钟 | 同一 kami 复活 tx 发出后 15 分钟内不重发 |
MY_KAMI_DEATH_CHECK_INTERVAL |
3 分钟 | 自家死亡监控扫描间隔 |
核心脚本 · 合成、XP 药水、步长
| 常量 | 默认值 | 含义 |
|---|---|---|
XP_POTION_LT_THRESHOLD |
70 | 只有清算线 > 70% 的 kami 参与 XP 药水轮喂 |
GREATER_RESERVE |
3 | Greater XP Potion 保留 3 瓶用于合成 Fortified |
POLLEN_BATCH / POLLEN_BATCH_STAM |
10 / 100 | 花粉合成每笔 tx 固定 10 次、耗 100 步长 |
核心脚本 · 锁、gas、诊断、保活
| 常量 | 默认值 | 含义 |
|---|---|---|
TX_LOCK_TIMEOUT / TX_EMERGENCY_TIMEOUT |
5 分钟 / 10 分钟 | 普通锁 / 紧急锁超时强制释放 |
TX_RETRY_DELAY |
3 分钟 | 抢锁失败后建议的重试间隔 |
GAS_LEDGER_MAX / GAS_LEDGER_RETAIN_DAYS |
5000 条 / 35 天 | gas 账本滚动上限 |
GAS_LEDGER_RECONCILE_MS / GAS_LEDGER_RECONCILE_BATCH |
3 分钟 / 20 笔 | 补账器扫描间隔与单轮上限 |
OWNER_GAS_TTL_MS / ADDR_GAS_24H_TTL_MS |
1 小时 / 1 小时 | 链上 gas 查询缓存时长 |
BLOCK_STALL_MS |
90000 | 区块号 90 秒不前进判前端同步冻结 |
TIMER_DRIFT_RATIO / TIMER_DRIFT_SUSTAIN_MS |
3 / 60000 | 定时器实际间隔达预期 3 倍且持续 60 秒 → 疑似后台节流 |
RPC_PROBE_N |
4 | RPC 网速诊断每次测几次 |
STALL_TICK_MS / STALL_SHORT_MS / STALL_SLEEP_MS / STALL_RECHECK_MS |
30s / 2 分钟 / 5 分钟 / 90s | 停摆检测心跳、短停摆、睡眠级停摆、醒后复检 |
REAL_IDLE_MS / MOVE_BASE_MS / SWEEP_BASE_MS |
3 分钟 / 75s / 5 分钟 | 保活:真人 3 分钟内动过就让路;合成移动、全程扫掠周期 |
TZ_OFFSET_HOURS |
'auto' | 日志时间时区(四个脚本都有),auto 跟随系统 |
辅助脚本
| 常量 | 默认值 | 含义 |
|---|---|---|
TOP_PREDATORS_TTL_MS |
6 小时 | 最强杀手档案有效期,到期自动重扫 |
PREDATOR_INACTIVE_DAYS |
10 | 杀手主人超过 10 天无链上动作 → 不参与清算线计算 |
PREDATOR_EQUIP_CAPACITY_DEFAULT |
1 | 假定杀手装备容量 |
PREDATOR_EQUIP_HEADROOM_ATS_DEFAULT / _ATR_DEFAULT |
0.10 / 0 | 杀手装备余量(shift / ratio) |
RESPEC_LT_THRESHOLD |
40 | 清算线 > 40% 的 kami 才用洗点药水重置技能 |
MAX_LEVEL_SUPPORTED |
56 | 经验表覆盖的最高等级 |
AUTO_CRAFT_CONFIG.checkIntervalMs / staminaThreshold |
30 分钟 / 80 | 自动合成检测间隔;步长 ≥80 才合成 |
CRAFT_PRIORITY[].enabled / maxCraft / minCraft |
配方各自 | 配方开关、单笔最多次数、起批下限 |
HELPER_REVIVE_MAX_PER_ROUND / HELPER_REVIVE_COOLDOWN_MS |
30 / 15 分钟 | 启动窗口复活单轮上限、防重发窗口(须与核心一致) |
T_VIO_MIN / T_HARM_MIN / T_POW_MAX |
23 / 15 / 12 | 杀手候选筛选默认阈值 |
MAX_WAIT / POLL_INTERVAL(升级等锁) |
10 分钟 / 15 秒 | 升级前等 TX 锁释放的上限与轮询间隔 |
精简数据库
| 常量 | 默认值 | 含义 |
|---|---|---|
CONC |
10 | 拉取 kami 详情的并发数 |
DB_TOP_PREDATORS |
内置档案 | 建库时算清算线用的最强杀手档案(兜底线口径) |
轻量杀手监控
| 常量 | 默认值 | 含义 |
|---|---|---|
KILLER_KAMI_INDEXES |
脚本内名单 | 被监控的杀手 kami 编号 |
KILLER_CHECK_BASE / KILLER_CHECK_RANDOM |
2 分钟 / 0~60 秒 | 轮询间隔 + 随机抖动 |
NEIGHBOR_WARNING |
true | 杀手在相邻地块也告警并停采 |
INITIAL_DELAY |
150 秒 | 页面加载后多久自动启动监控 |
KILLER_INACTIVE_HOURS |
24 | 杀手主人超 24 小时无链上动作视为不玩了,不触发停采 |
NEIGHBOR_QUIET_MINUTES |
10 | 隔壁杀手连续 10 分钟既没杀人也没移动(且 kami 不在我方节点)→ 不停采 |
FEED_CHECK_INTERVAL / LIQUIDATE_WINDOW_MS / LIQUIDATE_COUNT_TRIGGER / SAFE_COOLDOWN_MS |
3 分钟 / 5 分钟 / 2 / 15 分钟 | Feed 监控参数(已停用) |
内部常量(一般不用改,列出便于对照源码)
| 常量 | 默认值 | 含义 |
|---|---|---|
GAS_LEDGER_KEY |
'kami_gas_ledger' | 核心:gas 账本的 localStorage 键 |
OWNER_GAS_CACHE_KEY |
'kami_owner_gas_cache' | 核心:owner 手动 tx gas 查询缓存键 |
ADDR_GAS_24H_CACHE_KEY |
'kami_addr_gas_24h_cache_v3' | 核心:地址 24 小时 gas 分类统计缓存键 |
ROLLYTICS_HOST |
Yominet 官方索引器地址 | 核心:链上 tx 与 gas 查询用的免鉴权索引器 |
CHAIN_SYSTEM_KEYS |
64 个 systems key | 核心:官方客户端系统合约清单及中文标签,用于按运行时地址给链上 tx 分类 |
CHAIN_ACTION_MAP |
历史地址表 | 核心:系统合约换代前的旧地址 → 动作分类,保证旧账也能分类 |
WEI_PER_METH |
1e15 | 核心:1 mETH = 0.001 ETH = 1e15 wei |
ACTIONS / ACT_LABEL / ACT_ICON |
部署、停采、喂食、复活、拾荒、XP 药水、合成… | 核心:gas 报告的动作分类、中文名和图标 |
KAMI_COUNT_KEY / __KAMI_COUNT_KEY |
'kami_last_known_count' | 核心:紧急停采 DOM 扫描 / 主循环 DOM 预检记录历史 kami 数量峰值的键 |
XP_POTION_FED_KEY |
'kami_xp_potion_fed' | 核心:XP 药水已喂记录键 |
TOP_PREDATORS_KEY |
'kami_top_predators' | 辅助:最强杀手档案键 |
KILLER_ACTIVITY_KEY |
'kami_killer_activity' | 杀手监控:活跃度快照键 |
LT_BEATS |
EERIE 克 SCRAP、SCRAP 克 INSECT、INSECT 克 EERIE | 精简数据库:清算线公式里的亲和克制关系 |
SELF_NAME |
'核心脚本' / '辅助脚本' / '精简数据库' / '轻量杀手监控' | 四个脚本各自的名字,用于版本检查和日志 |
SCRIPT_VERSION / SCRIPT_LINE / SCRIPT_BUILT |
版本号 / '测试版' / 发布时间 | 四个脚本的版本三常量,SCRIPT_BUILT 由发布器打包时写入 |
META_URL |
GitHub 发布仓库的 meta.js | 四个脚本启动版本检查拉取的地址(游戏页 CSP 下通常拉不到,属正常) |
附录 C localStorage 键表
localStorage 跟着浏览器 profile 走,刷新页面不丢;清浏览器数据会丢。
| 键 | 所属脚本 | 用途 |
|---|---|---|
kami_core_db |
精简数据库写;核心、辅助读写 | 精简数据库主库(每只 kami 的清算线、等级、属性等 17 字段) |
kami_core_db_old |
精简数据库 | 重建前的旧库备份,误建时可手动复制回主键 |
kami_core_db_meta |
精简数据库写;辅助看板读 | 建库元信息:建成时间、条数、降级条数 |
kami_db_last_fail |
精简数据库写;辅助看板读 | 建库失败面包屑(失败阶段、时间),跨刷新保留 |
kami_mode |
核心写;辅助看板读 | 停采模式 greedy / normal(默认 greedy;旧值 starving 自动迁移成 greedy) |
kami_debug |
核心 | 调试总开关 '1' / '0' |
kami_debug_flags |
核心 | 调试分类开关(parse/start/stop/api/dom/feed) |
aux_debug |
辅助 | 辅助脚本调试开关,设 '1' 后刷新生效 |
kami_gas_ledger |
核心 | gas 账本:每笔 tx 的动作分类、hash、补齐后的 gasUsed×gasPrice;上限 5000 条 / 35 天 |
kami_owner_gas_cache |
核心 | owner 钱包 gas 查询缓存(Rollytics,1 小时) |
kami_addr_gas_24h_cache_v3 |
核心 | 地址 24 小时 gas 分类统计缓存(1 小时;v3 换键防旧结构串味) |
kami_gas_pending |
辅助看板读 | 旧版 gas 配对表;核心已不再写,看板只检查有无超 24 小时未消费的残留 |
kami_tx_confirm_hist |
核心 | 最近 30 笔停采 tx 的确认耗时,用来算 p90 自适应等待 |
kami_stop_tx_channel |
核心 | 停采发送通道 mud / raw |
kami_starving_feed_channel |
核心 | 饿死救援喂食通道 queue / raw(默认 queue) |
kami_last_known_count |
核心 | 上次读到的 kami 数量;主循环 DOM 预检和紧急停采 DOM 扫描都用它判断"数量突然少很多=页面没渲染好" |
kami_last_heartbeat |
核心 | 停摆检测器心跳时间戳,启动时回溯上次中断了多久 |
kami_reload_count |
核心写;辅助看板读 | 智能重载的连续错误刷新计数(加载成功复位为 0;可手动 removeItem 重置) |
kami_xp_potion_fed |
核心写;辅助看板读 | 已喂过 XP 药水的 kami 列表 |
kami_fortified_fed |
核心 | 旧键;新键为空时自动迁移到 kami_xp_potion_fed 并删除 |
kami_keepalive |
核心 | 保活开关,值为 'off' 时禁用 |
kami_top_predators |
辅助 | 最强杀手档案(扫描结果,6 小时有效) |
kami_predator_equip_capacity |
辅助 | 假定杀手装备容量(setPredatorEquipCapacity 写) |
kami_predator_equip_headroom |
辅助 | 杀手装备余量 shift(setPredatorEquipHeadroom 写) |
kami_predator_equip_headroom_atr |
辅助 | 杀手装备余量 ratio(setPredatorEquipHeadroom 写) |
kami_killer_activity |
杀手监控写;辅助看板读 | 杀手主人活跃度快照(最近动作时间、累计击杀、房间、MOVE 计数等) |
附录 D 跨脚本全局变量约定
四个脚本跑在同一个页面里,靠 window 上的变量互相通气。表里"谁读"只列跨模块的读者。
| 变量 | 谁写 | 谁读 | 含义 |
|---|---|---|---|
__kamiLogBuffer |
四个脚本都往里追加 | 核心 saveKamiLogs() / 自动存日志;辅助看板 |
共享日志缓冲区,导出日志文件的唯一来源 |
__kamiHealthBeats |
核心(主循环 / 拾荒 / XP流程)、辅助(LT显示 / 升级巡检 / 自动合成 / 健康自检) | 辅助健康看板 | 各模块"刚跑过"的时间戳埋点,区分"没活干"和"卡死" |
__kamiCoreInstance |
核心 | 核心(后加载的那份) | 当前在跑的核心实例(线别、版本、时间),单实例守卫用 |
__kamiCoreDuplicateBlocked |
核心(被拦下的那份) | 人工排查 | 记录哪份核心因重复加载被拦 |
__kamiHelperInstance |
辅助 | 辅助(后加载的那份) | 辅助单实例守卫 |
__kamiCoreVersion |
核心 | 核心 安装说明() |
核心版本号 |
__frontendFrozen |
核心前端冻结传感器 | 核心各门闩;杀手监控(冻结时不启用"隔壁沉寂放行") | 前端同步是否冻死 |
__evalFrontendFrozen |
核心 | 核心主循环每轮调用 | 刷新冻结判定的函数 |
__kamiGasRecord |
核心(暴露账本记账函数) | 辅助(洗点 / 加点 / 升级 / 合成 / 复活发 tx 后调用) | gas 账本记账入口 |
__kamiCallSource |
杀手监控('killer_monitor')、核心内部定时器 |
核心"手动调用"标记 | 告诉核心这次命令是谁调的 |
__txEmergencyLock |
核心(紧急停采) | 核心、辅助(发 tx 前等锁)、辅助看板 | 紧急锁:持有期间任何普通 tx 不许启动 |
__txNormalLock |
核心、辅助(经核心暴露的锁函数) | 核心、辅助、辅助看板 | 普通锁:部署 / 喂食 / 合成 / 升级共用 |
__kamiMode |
核心 | 核心、辅助看板(与 localStorage 对比,发现"切了没刷新") | 内存中生效的停采模式 |
__killerDetected |
杀手监控 Feed 模块(已停用,默认恒 false) | 核心模式模块、辅助看板 | 是否处于"检测到杀手"的安全模式 |
__lastKillerTime |
杀手监控 Feed 模块 | 核心、辅助看板 | 最近一次清算消息时间 |
__liquidatedTimestamps |
杀手监控 Feed 模块 | 核心 | 滑动窗口内的清算消息时间戳 |
__blockedKamiIds |
核心 | 辅助看板、控制台 | 部署黑名单(只读引用) |
__stopBlockedKamis |
核心 | 辅助看板、控制台 | 停采黑名单(只读引用) |
__stuck24hTried |
核心饿死救援 | 核心 | 饿死超 24 小时的 kami 上次尝试喂食的时刻与是否确认 |
__starvingFeedRunning / __starvingFeedSince |
核心饿死救援 | 核心紧急停采(等救援喂食排空再停) | 救援喂食进行中标志、开始时间 |
__deployPausedUntil |
核心 stopCurrentRoom / stopMinorityForTransfer |
核心部署;resumeDeploy() 清零 |
自动部署暂停截止时间 |
__emergencyStopRunning |
核心三个停采命令 | 核心、辅助看板(连续两次为 true 报卡死) | 停采互斥旗 |
__kamiOperationInProgress |
核心停采 / 部署 | 核心定时刷新与智能重载、辅助看板 | 正在执行停采或部署,刷新要等它 |
__emergencyBlindRetryCount |
核心 | 核心 | 紧急停采读不到 DOM 时的盲扫自重试计数 |
__ensurePartyInFlight |
核心 | 核心(启动 / 主循环 DOM 修复 / 紧急停采 DOM 预检) | 打开 Party 列表动作的串行锁 |
__stallDetectorOn |
核心 | 核心 | 停摆检测器已挂载(防重复) |
__stopBackoffSchedulerStarted |
核心 | 核心 | 停采退避复读调度器已启动(防重复) |
__gasLedgerReconcilerStarted |
核心 | 核心 | gas 补账器已启动(防重复) |
__deployPauseReminder |
核心主地块校验 | 核心 | 不在主地块时每 3 分钟提醒的定时器 |
__reviveSentAt |
核心复活、辅助启动窗口复活 | 同左两处 | kamiId → 复活 tx 发送时间,15 分钟内不重发(两处共用防重复消耗丝带) |
__kamiUnloadSave |
核心 | 核心 | 关页 / 刷新时存日志的监听器引用(防重复挂) |
__refreshLT |
辅助 | 控制台调试 | 立即刷新一次 LT 显示 |
__predFallbackWarned |
辅助 | 辅助 | "杀手档案回退到内置档案"只告警一次 |
__upgradeProcessed |
辅助 | 辅助 | 批量升级断点续跑进度 |
__getMinorityKamis |
辅助 | 核心 stopMinorityForTransfer() |
取 minority kami 分类结果 |
__classifyKamisByAffinity |
辅助 | 控制台调试 | 按地形亲和给 kami 分类 |
__KAMI_CORE_DB_BUILDING__ |
精简数据库 | 精简数据库 | 构建中标记,防同页重复构建 |
__killerWatchList |
杀手监控 | 辅助 scanTopPredators()、辅助看板 |
监控名单活引用(增删即时生效) |
__killerPlayerMap |
杀手监控 | 辅助看板、控制台 | 杀手 kami → 主人映射 |
__killerSelfOwned |
杀手监控 | 辅助扫描与看板 | 当前账户名下的杀手 kami 列表 |
__killerMonitorState |
杀手监控 | 控制台排查 | 监控状态 {running, mappingBuilt, lastRoundAt} |
MY_KILLER_KAMIS |
核心(手工清单)、杀手监控(自动注册自家杀手) | 核心部署 / XP 喂食、辅助升级 / 技能重置 | 自家杀手 kami 集合,自动化一律跳过 |
kami_core_db |
精简数据库、核心、辅助 | 全部 | 内存里的精简数据库 |
kami_core_db_old |
精简数据库 | 控制台 | 构建前备份的旧库 |
没有匹配的条目。换个词试试,比如命令名的一部分或中文关键词。