jevbooks

← 全部设计模式

恒温器

迟滞 · Thermostat

20 度开,22 度关

一条高线管进入,一条低线管退出,状态就不会跟着数字一起抖。

.6.8退出进入onoff

问题

你在做一个游戏的画面覆盖层:玩家一进入战斗,血条和技能栏放大;战斗结束,再缩回去。这规则根本写不出来——怪物、特效、界面每个游戏都不一样——所以你让 Jev 每帧看一眼画面上的文字,给你一个「正在打架」的概率。

你定了一条线 0.7 就上线了。第一天就有人发了段录屏,配字「这玩意儿抽风了」。你把那几秒的日志拉出来:0.68、0.72、0.69、0.71。没有一次答错。战斗的边缘本来就模棱两可,是你那一条线把「差不多」翻译成了一秒五次的「是、不是、是、不是」。

你把线抬到 0.8:不怎么闪了,但血条晚半秒才放大,玩家先挨一刀才看得清自己的血。你降到 0.6,闪烁挪到了战斗结束的时候。怎么调都不对,因为毛病不在线的高低,在于只有一条线。

解决方案

恒温器模式说:用两条线,并且记住上一次的结论。进入用高线——概率到 0.8 覆盖层才放大,宁可晚半拍,不要误报。

退出用低线。一旦进了战斗,只要还有 0.6 就留在战斗里,跌破 0.6 才算结束。0.6 到 0.8 中间这一段是专门留给「差不多」的:落在这里,什么都不变。

第三样东西是记忆。这一帧要对照上一帧来判。上一帧已经在打,0.71 的意思是「继续」;上一帧没在打,同一个 0.71 的意思是「还没到」。同一个数,两种含义,区别就在那份记忆里。

问 · 答 · 拍

  1. 问把当前画面上的文字和按钮(发给 Jev 看的那份材料,术语叫 state)放进去,只问一句:现在正在发生战斗吗?
  2. 答0.71。
  3. 拍上一帧已经在战斗里,0.71 也没跌破退出线,于是保持在战斗里,覆盖层一动不动。

名字从哪来

家里的恒温器 20 度开暖气、22 度才关。要是开和关都定在 21 度,锅炉会在 20.9 和 21.1 之间开开关关响一整夜。中间那两度,就是它留给「差不多」的地方。

什么时候用

你判的是一个会持续一阵子的状态——在战斗中、这个任务需要人接手、这条链路该走大模型——而且来回切换是有成本的:界面闪、重复通知、缓存重建。

什么时候别用

每次调用判的是彼此独立的东西:一封邮件、一次改动、一条评论。它们之间没有「上一次」,硬塞一份记忆进去,只会把上一封的结论拖到下一封身上。

做这件事的项目

  • huncho — 在 System One 模型上将决策作为代码:类型化问题、滞回、日志、校准。
  • foreman — 使用决策模型的智能体监督者与软件工厂工头。
  • jev-browser — 由 TypeSafe 的 Jev 模型驱动的快速、低成本浏览器自动化。

用这种模式的全部项目 →

看谁的代码
  • hunchosrc/policy.ts:268-276

    十行代码:一条进入线、一条退出线,加上上一次的结论,三样一起决定这个状态保不保持。

  • foremansrc/foreman/policy.py:21-127

    一个监工在打断下属之前先等三十秒,并把自己上一次的判断回喂进去:同一个想法,量的是时间而不是概率。

  • jev-browsersrc/navigate.ts:441-458

    「卡住了」第三步之前不算数;同一个动作连着两次没效果,就换成第二顺位的那个,不再原地重复。

想看代码
function active(p, previous, { enter, exit }) {
  if (p >= enter) return true;              // 到了高线:进入
  return previous === true && p >= exit;    // 已经在里面,又没跌破低线:保持
}
const prev = held.get(session.id) ?? false;
const now = active(answers.combat.p, prev, { enter: 0.8, exit: 0.6 });
held.set(session.id, now);
jevbooks 怎么认出它

我们怎么知道一个项目用没用这一招?拿它的 README 问 Jev 一句话,Jev 给个百分比,数字越高说明越像。你要抄这道题,记住一点:别问「这个项目好不好」,要问「有没有讲到这一件具体的事」。

questions.json · 每份 README 都发这道题
{
  "hysteresis": {
    "type": "noul",
    "instructions": {
      "what": "Does the text describe different thresholds for entering and leaving a state, or feeding the previous decision back to prevent flip-flopping?",
      "not_for": "A single static threshold."
    },
    "criteria": {
      "true": "The text explicitly describes this. One sentence is enough; other content does not cancel it.",
      "false": "The text does not describe this."
    }
  }
}

用 Jev 做一个 →