你在大发娱乐里点了一下“停运17路”,屏幕上只是一条线路变灰。这一下背后,系统究竟动了哪些数据,又故意没有动哪些,先后顺序又是怎样安排的呢?

一次点击,先被当作指令,而不是直接改数据

玩家在界面上点下“停运17路”,世界状态并没有立刻变化。这个操作首先被记录成一条指令:谁发出的、作用对象是哪条线路、生效时间是哪一个时间步、是否与已经排队的其他指令冲突。

指令校验只做规则代码能做的事:预算够不够支付违约成本,这条线路是否属于玩家管辖,同一时间步里是不是已经有人调整了它的班次。校验失败就直接拒绝,不产生任何状态变化。之所以要多这一层,是因为如果点击直接改数据,两个同时到达的操作就会互相覆盖,而回放和排错时也没有“当时到底发生了什么”的记录。

通过校验的指令进入队列,等待下一个时间步开始时被统一取出。此时大发娱乐官网所介绍的那套工作流才真正启动。

影响半径:先回答“谁需要被重算”

假设这座城市有6200名AI居民(模拟示例,仅用于说明逻辑)。如果每次玩家操作都让6200人各自重新想一遍,一天的开销就会被一次点击吃掉。所以系统要做的第一件事,是根据线路、站点和居民的日常路径,划出一个影响半径。

在这个模拟示例里,17路沿线有12个站点,途经3个住宅区。居民的通勤路径表里,约380人每周至少乘坐一次17路,这是第一圈,叫直接受影响者。这些人的路线换乘之后,会挤进相邻的9条线路和约60个路段,这些路段上的其他乘客构成第二圈,约1100人,叫间接受影响者。剩下约4700人的日常路径与17路没有交集,当天不做重算。

层级 被更新的对象 模拟示例数量由谁计算是否调用大模型
指令与账目 预算、线路状态、排班表 3项规则代码
直接受影响居民常乘17路的居民约380人 规则与行为模型仅个别(示例中约20人)
邻近路网与线路载客量、拥挤度、准点率 9条线路、约60个路段 传统模拟器
间接受影响居民 换乘、绕行、改期出行 约1100人 批量推理轻量模型,批量处理
慢变量 房租、沿线店铺客流、企业招工 数周后结算 定期结算

回到标题的问题:一个决定到底更新了多少东西。在这个模拟示例里,答案是大约1500名居民、数十个路段和三项账目,不到全城居民的四分之一,其中真正需要调用大模型的只有个别人。至于哪些人属于“个别”,规则很朴素:只有当既有的行为规则给不出合理答案时才升级。比如某位居民每天必须乘17路去照顾住院的家人,替代路线全部超出他的时间预算,这样的处境不能靠一个平均公式糊弄过去。这类决策点怎么设计,在事件驱动Agent一文里有更详细的讨论。

还要补一条:影响半径不是一次划定就不变的。第一轮居民重新选路以后,邻线拥挤度可能上升,进而把原本在圈外的几十个人拉进来。所以半径的划定是迭代的,直到新增被点亮的对象低于某个阈值才停止。这个阈值就是大发娱乐AI在精度和成本之间留的一个旋钮:调得太低,一次停运会把半座城市重算一遍;调得太高,就会漏掉真实存在的连锁反应。

更新顺序:先算容量,再算行为,最后算钱

影响半径划出来以后,紧接着是一个容易被忽视的问题:这一圈人和路段之间,谁先更新。大发娱乐的模拟设计里,一个时间步内的顺序大致如下。

  1. 应用指令:17路状态改为停运,站点客流清零,排班表冻结。
  2. 更新供给侧容量:相邻线路的可用运力、路段的通行能力,先把“世界现在能承载什么”确定下来。
  3. 更新居民的行为选择:直接受影响者根据新的容量重新选路,间接受影响者在拥挤度变化后再判断是否绕行。
  4. 汇总实际发生的出行:谁最终上了车、谁改期、谁放弃出行,写入当日流量。
  5. 结算当日的财政:票款收入、运营成本、违约金。
  6. 写入记忆和日志:记录居民经历,生成影响半径日志供后续检查。

为什么必须先容量、后行为?如果反过来,居民读到的是旧容量:他以为邻线还有空位,选择了换乘,结果汇总时才发现车厢已经塞满,只好当场回滚或者在下一天才修正。表面上看只是一拍的延迟,累积几天后就会表现为居民永远在追着上一天的拥堵做决定,曲线上出现一种奇怪的锯齿。

另一头,也没有必要让所有主体都严格等待同一个全局时钟。Zhiqiang Xie 等人在2024年发表于 arXiv 的 AI Metropolis 研究里指出,LLM 多智能体模拟如果用全局同步步进,会制造出大量本不存在的“假依赖”,限制并行度和批处理规模;他们用依赖追踪做乱序执行,在其测试的工作负载上,相对带全局同步的并行模拟报告了1.3倍到4.15倍的加速。这个结果针对特定负载和硬件,不能推广到所有模拟,但它提供了一个设计方向:只有真正会互相影响的主体才需要排序,相隔很远的两个居民同一步里各算各的即可。

哪些东西被故意留着不更新

在这条工作流里,“不更新”本身就是一种设计选择。至少有三类东西被有意识地留白。

  • 影响半径以外的居民。他们的行为参数没有变,日常路径不重算。如果某个世界变量之后越过了他们的阈值,例如全城平均通勤时长升高到一定程度,才会被脏标记唤醒。
  • 慢变量。房租不会因为一次停运当天就变,它按周或按月结算,读的是一段时间的平均客流,而不是某一天的尖峰。否则一次停运会在一天内把整个租房市场扰动一遍,再一天内弹回去。
  • 已经发生的历史。停运之前的出行记录、票款账目保持不变,新的状态只从生效时间点往后写。回改历史会让存档和回放无法对齐。

脏标记的作用就是把“是否需要重算”变成一个便宜的判断。每个居民、每条线路都带一个标记,表示自己依赖的输入有没有变化;没有变化,就沿用上一次的结果。真正花钱的地方只留给标记被点亮的对象。

记忆:这次停运会留在谁的脑子里

更新并不会随着当天结束而消失。直接受影响的居民会把这次经历写进自己的记忆:某天某站没有车,被迫换乘花了多少时间,最终有没有迟到。这些记忆之后会改变他们的偏好,比如下一次同类情况发生时,他们可能更早出门,或者干脆换一种通勤方式。

这也是为什么撤销指令不等于回到原点。玩家第二周恢复17路,一部分居民已经形成了新习惯,他们不会立刻回来。这个滞后正是设计想要的效果:世界记得曾经发生过什么。长期的路线记忆怎样形成,可以参考大发娱乐App里的AI城市为什么有时会突然堵车中的例子。

玩家看到的,只是这一圈更新的摘要

整个过程里,玩家不会收到几千条日志。大发娱乐城市AI的界面把这一圈更新压缩成几句话:沿线三个住宅区的通勤时间平均增加了多少,邻线哪几段开始拥挤,预计哪些居民会在本周内换掉出行方式。摘要由汇总后的数字生成,大模型可以负责把它说得像人话,但数字本身来自前面几步的计算,不来自语言模型的想象。

这一点很关键。假如摘要里的数字是模型编出来的,它读起来越流畅,玩家越容易被误导,而系统内部的真实状态却和界面说的不是一回事。大发娱乐AI的分工是:状态由模拟器保存,语言由模型润色,二者之间只通过明确的字段传递。这样即使模型措辞出了偏差,账目本身也不会跟着错。

玩家决定的每一步,也因此都对应着一个可追溯的更新记录。当玩家怀疑“为什么这条路突然拥堵”时,回答不是一句含糊的解释,而是一条可以翻出来核对的因果链:哪条指令、哪个影响半径、哪几名居民重新选了路。大发娱乐官网强调的“一个决定牵动多少东西”,落到工程上就是这条记录。

三种办法,检查这次更新有没有出错

更新范围划得对不对,靠感觉判断很不可靠,所以工作流里预留了三种检查。

第一种是守恒核对。当天所有居民的出行去向加起来,必须等于计划出行的总人数:乘各条线路的、步行的、改期的、放弃的,一个都不能多,一个都不能少。这类核对不需要任何智能,只要加法,却能抓住大量“人凭空消失”的错误。

第二种是影响半径日志。每次更新,系统记下谁被点亮、为什么被点亮、用了哪个层级的计算。事后如果发现某个居民的行为很怪,可以回头看,是漏掉了应该更新的他,还是错误地重算了不该更新的人。

第三种是撤销对照。把同一条指令在另一份存档里撤销,对比两份世界的差异。因为记忆的存在,差异不会是零;差异的大小和衰减速度,就是记忆机制的实际效果,可以用来判断它是过强还是过弱。这类校验思路和AI模拟世界的验证和校准是一脉相承的。

好的工作流,能说清谁没有被更新

回到那次点击。屏幕上只是一条线路变灰,背后被更新的是一圈对象,被留着不动的是更大的一圈,并且每一处“不动”都能给出理由:依赖没变、属于慢变量、属于已经写下的历史。判断一个模拟工作流是否靠谱,不妨反过来问它:你这一步没有更新谁?如果它能说清楚,并且随时可以用日志证明,这套系统才算真的知道自己在做什么。