系列《城市生命力》· P9 · UE5 逻辑 Demo
P5 分析成熟开放世界如何维护车道拓扑、流送节点、异步路径、路口灯组和交通槽。P9 转向 UE5 重建工程,在一张独立验收地图中实现道路图、拥堵改道、出口容量、车辆/行人冲突调度、预约超时和确定性恢复。车辆自身的跟车、制动和碰撞属于 P11;Near/Mid/Far/Hidden 表现分配属于 P12。
上一篇:《城市生命力》P8|UE5 车辆调度:车库派发、策略裁决与路线连续性
对应底层逻辑:《城市生命力》P5|开放世界的交通编排
导言:交通系统决定谁能使用下一段空间
车辆沿样条前进,只验证了路线播放。交通编排还要处理四类状态:拥堵后的替代路线、改道期间的车辆连续性、有限出口容量的分配,以及信号切换和预约失效后的冲突区清空。
P9 的验收地图 /Game/Cyber2077/Maps/TrafficOrchestration_Demo 将这些问题组织成一个 32 秒循环:
0–8 秒 FREE FLOW
8–16 秒 CONGESTION REROUTE
16–24 秒 DOWNSTREAM CAPACITY
24–32 秒 RESERVATION RECOVERY
地图内有一条主干路径、一条南侧绕行路径、一个共享出口容量门、一个信号冲突区和一个行人等待区。8 辆东行车与 6 辆西行车持续循环,黄色表示保持主路线,红色表示动态改道。HUD 报告路线成本、改道数量、等待中的预约请求、预约授予、容量阻塞、信号阶段、活动预约、行人组、过期租约和冲突不变量。
| Demo 阶段 | 操作与观察 | 需要证明的合同 |
|---|---|---|
| Free Flow | NETWORK 视角建立道路与双向车流 | 车道图和车队状态是独立运行时数据 |
| Congestion Reroute | 提高主路线拥堵代价,部分车辆走南侧绕行 | 只替换未来车道,保持当前车道、位置和速度 |
| Downstream Capacity | 三个预约请求竞争同一次批处理中的一个出口信用 | 本轮最多一份请求获得 Granted,其余请求继续排队 |
| Reservation Recovery | 行人组等待一个由孤儿车辆持有的冲突区 | 预约租约过期后,行人按真实信号、类型内排序与等待保护接管 |
| Save / Restore | 保存容量或恢复阶段,继续后再恢复 | 车队、路由、队列、信号、预约和阶段可继续重演 |
Demo 提供三组固定观察位。CAM: NETWORK 同时看到西侧车库、主干路、南侧绕行和信号连接,用于确认改道前后车辆没有瞬移;CAM: BOTTLENECK 对准共享出口,适合读取三个请求与一个信用之间的关系;CAM: SIGNAL 对准冲突区和行人等待区,用于观察车辆预约、AllStop、租约过期和行人接管。相机只改变观察位置,不写入运行时状态。
HUD 按模块显示运行时证据。路线区包含主路参考成本、选中路线成本、未来车道数和当前车道保留状态;容量区包含 Queued、Granted 与 Capacity Blocked,其中 Granted 表示预约授予,不代表车辆已经通过出口;信号区包含 Phase、Elapsed、Pedestrian Demand 和当前所有者;SOAK 区累计周期、改道、预约授予、过期租约和冲突违规。截图记录离散状态,32 秒录像用于核对状态转移顺序。

一、从原工程到 UE5:路网是持续运行的数据,不是场景装饰

原工程的交通全局路线采用带令牌、容量、取消和跨帧续算的异步协议。路径请求受理不等于已经找到路线,部分路径也不等于最终成功。信号灯并非直接改写所有车辆速度,而是形成可被局部运动层读取的停止约束;冲突预测、静态/动态占用和交通槽随后决定车辆如何接近路口。
P9 没有复刻离线编译、流送节点和跨帧 A*。它保留五项直接影响连续运行的语义:
- 车道由稳定 ID、拓扑端点、长度、限速和容量定义;
- 路由成本可以叠加距离、占用、拥堵和关闭状态;
- 改道只能替换尚未行驶的未来车道;
- 信号许可与冲突区实际占用是不同条件;
- 预约必须能够排队、释放、超时和恢复。
当前实现采用项目自有运行时,ZoneGraph、Mass Entity 和 MassTraffic 均不作为权威交通数据源。地图 Actor 负责配置和展示;道路图、车队、动态路由、容量预约和路口调度存放在可脱离场景运行的 C++ 数据结构中。
Demo 的状态所有权

RoadGraph:主路线、绕行路线与对向车道;Fleet / OpposingFleet:东行和西行车辆的路线与运动状态;Reroute:逐车阻塞帧、上次改道帧和统计;CapacityReservations:下游容量竞争使用的等待队列、等待分数与入口授予计数;Scheduling:信号、车辆预约、行人组、租约与冲突区占用。
HUD 和材质不参与状态推导。按钮将权威固定步推进到目标阶段;红车、信号颜色和冲突区高亮仅用于显示运行时结果。
从源码语义到原型模块的对应关系
原版系统将道路数据、全局寻路、交通槽、路口控制器、信号灯和车辆局部运动分配给不同模块。P9 沿用职责分离原则,并以可独立测试的合同重新组合这些模块:
| 原版可观察语义 | P9 中的对应模块 | 当前保留内容 | 当前省略内容 |
|---|---|---|---|
| 持久道路拓扑 | Road Graph Runtime | 稳定车道 ID、节点连接、方向、长度、限速、容量 | 离线烘焙、区域流送、道路版本迁移 |
| 全局路线查询 | Dynamic Routing Runtime | 加权代价、关闭拒绝、路径重建、失败原因 | 跨帧令牌、分层图、缓存与取消协议 |
| 交通槽推进 | Fleet Runtime | 车辆身份、当前车道、alpha、速度、未来路线 | 原版槽布局、完整车道变换与局部冲突走廊 |
| 下游通行资格 | Reservation Runtime | 请求、WaitFrames 评分、入口授予惩罚、单批出口信用、释放 | 实时车道占用率、车辆长度与清空时间预测 |
| 路口所有权 | Intersection Scheduling | 信号、全停、车辆租约、行人组、冲突区互斥 | 多路口网络协调、复杂相位与网络死锁检测 |
该映射不包含省略模块的能力。红色车辆成功绕行,只能证明当前小型路网中的路线交接成立;它不能证明 World Partition 道路节点流送或跨区路径令牌的取消机制已经实现。
原工程在不同层级分别实现了这些职责。离线交通编译器生成道路、样条、交叉口和持久连接;运行时交通系统处理节点注册、反注册、使用计数、碰撞表示和路口对象。全局交通路径以令牌进入有限队列,可以取消并跨帧续算;局部车辆运动读取灯光形成的停止约束、车道槽和预测冲突。P9 没有把这些模块压缩成一个“原版交通算法”,只抽取它们之间可验证的边界:拓扑先于车辆、路线结果先验证后提交、灯相只提供许可、实际占用必须独立释放。
原工程的持久路网、流送、异步路径和交通槽来自 P5 已完成的源码阅读。P9 使用从这些职责中提取的运行时合同;文中的 RoadGraph、Routing、Reservation 与 Scheduling 类型均为项目自有实现,不对应原工程中的同名类。
UE5 原型据此拆分:RoadGraph 不引用场景道路 Mesh;Fleet 保存 VehicleId 和路线运动状态,不以 Actor 指针表示身份;CapacityReservations 处理路径预约和信用;Scheduling 处理信号、冲突区与租约。ACyberTrafficOrchestrationDemoActor 只承担验收夹具与状态组合职责。生产实现可以替换地图构造、异步搜索或可视化层,同时保持这些运行时合同。
一次固定步中的先后关系


场景 Actor 还保存 bRerouteTriggered、bCapacityScenarioStarted 和 bRecoveryScenarioStarted。这些阶段标记属于场景协议:恢复检查点时,系统据此判断请求是否已经建立,避免仅依据 elapsed time 重复执行初始化副作用。
代码中的实际调用链比 HUD 显示更具体。Tick 只负责把渲染帧时间积累到 FixedStepAccumulator;每取得一个 0.1 秒步长,才调用 AdvanceTrafficOrchestrationDemoFixedStep。该函数先根据循环时间解析 Phase,再按一次性标记调用拥堵改道、容量初始化或恢复初始化。进入恢复阶段后,FCyberTrafficIntersectionSchedulingRuntime::Advance 负责租约倒计时、灯相转换和等待请求仲裁;之后才推进两支 Fleet,回收抵达终点且入口安全的车辆,并把状态汇总给 HUD。图中将这条调用链压缩为“阶段—调度—车队”三段,箭头表示同一逻辑步内的先后关系,不表示三个并行线程。
当前车队始终以 Open Gate 推进,容量与信号结果尚未接入可见车辆的制动控制。容量队列和冲突预约提供独立的调度证据;车辆运动执行留给 P11。P9 验证门控结果的计算、保存和诊断,不覆盖红灯或容量不足驱动的车身停车。
二、动态路由:代价变化只改写车辆的未来
道路图提供两条可竞争路径
Demo 的东行入口从 start 到 junction。主路线随后经过 short.a 与 short.b 到达终点;绕行路线从同一路口进入一组离散车道段,沿南侧曲线回到终点。东行车道拥有稳定 ID、From/To 节点、世界端点、12 m/s 限速和容量 6。另有一条限速 11 m/s、容量 8 的西行车道用于形成对向流。
初始主路线成本只计算 short.a + short.b 的二维长度。进入拥堵阶段后,系统为这两段各写入 1.0 的拥堵值;调试状态同时保留一个增加 500 的主路线参考成本,用于对比拥堵前后的数值。权威选路仍由动态路由器逐段计算,不直接使用 HUD 参考数。
路由结果由三类软代价与一类硬关闭共同决定

LaneCost = DistanceMeters × DistanceWeight
+ OccupancyCount × OccupancyWeight
+ Congestion × CongestionPenaltyScale
默认权重为距离 1、每个占用 40、拥堵倍率 250。硬关闭在候选展开阶段直接拒绝,不参与代价相加;结果单独记录评估车道数、受惩罚车道数和关闭拒绝数。查询还包含 MaxLaneSegmentCount,重建路径超过上限时返回失败,避免异常拓扑产生无限回溯。
策略结构中存在 IncidentPenalty,但当前 FindRoute 对关闭车道采用直接拒绝,没有使用该字段为事故车道增加有限代价。可验证输入只有距离、占用、拥堵与硬关闭;事故软惩罚尚未实现。
这些输入的证据范围并不相同。32 秒主循环在进入 Congestion Reroute 时,只向 short.a 和 short.b 写入 1.0 拥堵值,并遍历东行车队,直到成功改道 4 辆或全部 8 辆都已检查。占用计数由 BuildContext 从 Lane Occupancy 条目聚合,关闭集合由调用者传入;这两条路径主要由 TrafficDynamicRouting 自动测试覆盖。主录像能证明拥堵改道和连续交接,不能单独证明占用权重与关闭拒绝都在该画面中发生过。
FindRoute 的距离以世界端点二维距离除以 100,转换为米;所有可调权重在使用前都会钳制到非负范围,距离权重最低为 0.001。双向车道允许从 To Node 反向到 From Node,普通有向车道只沿 From→To 展开。每次松弛都会增加 EvaluatedLaneCount,遇到关闭车道增加 ClosedLaneRejectedCount,使用占用或拥堵输入时增加 PenalizedLaneCount。这些计数属于搜索过程证据,不是车流统计。
触发改道与找到新路是两个阶段

通过触发门后,查询从“当前车道的出口节点”开始,而不是从车辆世界坐标重新投影。若新结果与原未来车道完全相同,返回 reroute_unchanged;找不到路线、目标无效、超出车道上限或拓扑不连续也都有独立失败原因。
当前车道、运动状态和身份必须保留


current lane PRESERVED。ApplyRoutePreservingCurrentLane 先复制车辆当前车道,再按新路线 ID 逐段检查连接关系。新路径的 Source Node 必须等于当前车道的 To Node,每一段 From Node 必须接上前一段 To Node,最终节点必须抵达查询目标。全部成立后才一次替换 RouteLanes。
替换时,当前车道成为新数组第 0 项,CurrentLaneIndex 重置为 0;车辆的 LaneAlpha、位置、速度和 VehicleId 保持不变。车辆完成当前车道后进入新路线,不会瞬移到绕行路线起点。
Demo 在拥堵阶段遍历东行车队,直到成功改道数量达到 4,或所有 8 辆车都已检查。状态记录 Selected Route Cost、未来车道数和 bCurrentLanePreserved,红色只标记已经应用新路线的车辆。路线连续性属于运行时结果,不以颜色变化作为通过依据。
搜索范围:同步小图。
动态路由器在一次函数调用中遍历当前小型道路图,没有跨帧时间预算、优先队列或大图分区。它适合验证权重与路线交接,不代表生产城市可以每帧为大量车辆同步全图搜索。扩展时需要分层图、缓存、查询队列和时间片,并继续保留现有的触发冷却与原子路线替换。
最短路搜索的诊断信息。
当前实现使用一个面向小图的确定性最短路扫描。查询状态包含各节点当前最小代价、前驱车道和待处理节点集合。每次选择累计代价最低的节点,枚举其出车道,再把满足条件的新代价写回目标节点。到达目标后,沿前驱车道逆向重建并反转,得到从 Source 到 Target 的有序车道 ID。
该实现没有使用优先队列,复杂度不适合大规模道路图;测试可以稳定复现哪些车道被评估、被惩罚或因关闭被拒绝。查询结果除成功/失败外,还保存:
- 是否找到完整路线;
- 总代价与路线包含的车道段;
- 已评估车道数;
- 使用占用或拥堵惩罚的车道数;
- 因关闭被跳过的车道数;
- 失败原因和重建是否超过上限。
这些诊断字段用于将路线异常定位到具体输入。无路线结果可能来自目标不可达、入口无出边、全部候选关闭或前驱重建超过最大段数。仅返回空数组时,Demo 只会显示车辆停止,无法区分数据错误与策略选择。
路线替换先验证、后提交。
新路线不应在验证过程中写入车辆状态。若前三段连接正确、第四段指向错误节点,逐段写入会形成部分更新路径。P9 先在临时数组中完成以下检查:
- 当前车道存在,并可作为新路线第 0 段;
- 查询结果的起点等于当前车道出口;
- 每一段的 From Node 等于上一段 To Node;
- 所有车道 ID 都能在 Road Graph 中解析;
- 最后一段确实到达目标节点;
- 路线段数没有超过运行时上限。
全部检查通过后,完整临时数组一次替换未来路线。异步化后仍需保留该原子提交边界:跨帧搜索可以逐步产生候选,权威路线只在完整结果通过验证后切换。
占用、拥堵和关闭不是同一种惩罚
当前公式把 Occupancy 与 Congestion 都转成可比较代价,但两者来源不同。Occupancy 是某段当前包含多少逻辑车辆;Congestion 是 0–1 的外部拥堵尺度,可以来自速度下降、事故影响或区域策略;Closed 则表示道路不能作为合法候选。
软代价允许查询在“绕行太远”与“主路太挤”之间权衡;硬关闭不参与权衡。若事故只封闭部分车道,生产系统必须明确采用整段关闭、容量降低,还是可恢复惩罚。P9 当前使用硬关闭,IncidentPenalty 尚未参与公式,三类语义保持分离。
三、容量门控:绿灯不代表出口一定有空间


交通信号允许某股移动进入路口,只表示方向许可。若下游车道已经满载,继续授予进入资格可能使车辆停在冲突区内。P9 用独立的 FCyberTrafficIntersectionReservationRuntime 验证单次批处理中的下游容量门控。
进入容量阶段时,Demo 构造三个请求,分别竞争三个冲突区,但它们的 Exit Lane 都指向 lane.article3.shared.exit。请求在关闭 Gate 下进入容量请求队列,保存 VehicleId、入口、出口、请求帧和等待帧。随后一次批处理把 Gate 打开,本轮最多授予 3 个请求,却只提供一个出口容量信用:
ExitLaneCapacity[shared.exit] = 1
结果只能授予 1 份预约,其余 2 份请求保持等待并累计 CapacityBlocked。容量按出口消耗,而不是按冲突区分别计算,因此三条互不相同的入口不会在同一批处理中绕过共享下游瓶颈。Granted 只表示预约获得调度资格,不表示车辆已经驶入或通过出口。
三个计数分别表示不同状态:Queued 是容量请求等待队列中的记录;Granted 是已经获得完整路径预约的请求;CapacityBlocked 是本次处理时因出口信用不足而未获准的次数。CapacityBlocked 按事件累计,不等于当前队列长度。
全文按以下术语区分调度资格与实际占用:
| 运行时术语 | 本文含义 |
|---|---|
| Queued | 等待中的预约请求 |
| Granted | 已授予预约,不表示已经通过路口 |
| Active Reservation | 活动预约,即未来冲突空间所有权 |
| Crossing Agent | 实际进入或仍占据冲突区的对象 |
| Released | 预约释放 |
| Cleared | 实际冲突区清空 |
当前 Demo 把容量表示为整数信用,不模拟流量率、排队长度、车辆尺寸或下游清空时间。它验证了预约授予前读取本批下游预算的接口,实际容量模型仍需结合车道占用和车辆包络。
请求从排队到释放的生命周期。
容量预约运行时将请求分成待处理队列、已获准预约和出口信用占用。新请求先验证车辆、入口、出口与冲突区 ID,然后用单调序号进入队列。批处理时按序号稳定选择候选,再依次检查 Gate、冲突区和 Exit Lane 信用。
获准后,请求从等待队列移入 Conflict Reservation,保留 VehicleId、入口、出口、冲突区和剩余租期。当前出口信用只在一次 ProcessFairQueueBatch 调用内通过 GrantedCountByExitLane 消耗,因此可以保证本批三个请求只授予一份预约。Reservation 会持续占住冲突区,但现有实现不会在下一批开始时自动用活动 Reservation 扣减出口预算。
P9 当前验证的是单次批处理中的共享出口信用,不包含跨固定步维护的下游在途信用。生产实现需要依据活动预约、实际占用和已进入但尚未清空的车辆重算每步预算,或将出口信用建模为有生命周期的令牌。否则,连续两次批处理都传入 shared.exit = 1 时,第二次处理可能在首辆车尚未清空时再次授予预约。
若请求因为容量不足未获准,它继续保留在原数组位置,WaitFrames 在下一次批处理时继续增加。当前选择分数为:
Score = WaitFrames × 100 - DirectionGrantCount(EntryLane)
等待越久,分数越高;同一入口此前获得的预约授予越多,分数越低。分数相同时由稳定数组遍历顺序决定。它不是独立的全局序号协议,但能避免失败请求被删除后重新排到队尾,也能对连续获准的入口施加轻量惩罚。
容量预算优先查询 Exit Lane;没有出口预算时才回退到 Entry Lane。多个入口汇入同一出口时由此共享一个信用池。入口和出口同时配置预算时,以出口预算为准,不叠加两者扩大吞吐。
生产系统中的容量信用来源。
当前夹具直接提供 shared.exit = 1。进入生产级城市运行时后,这个数字可以由多种状态派生:

四、冲突区:信号阶段和实际占用共同决定通行
车辆与行人共享冲突区,但当前仲裁仍分两轮执行
FCyberTrafficIntersectionSchedulingRuntime 维护信号、待处理车辆、待处理行人组、活动车辆预约、活动行人组与实际 Crossing Agents。车辆请求和行人组请求共用单调递增的 NextRequestSequence,因此两类记录拥有同一时间基准;当前 ProcessFairRequests 并未把它们合并成一条全局优先队列。实现先按等待时间和 Sequence 排序行人请求,再用同样规则处理车辆请求。
行人组请求会验证 GroupId、SignalId、ConflictZoneId 和 AgentId 唯一性,随后增加信号的 Pedestrian Demand。车辆请求保存 LaneId,用于具有主/次方向窗口的路口判断。重复请求不会生成第二份所有权:已活动的请求直接返回 Granted,已排队的请求返回 Queued 和累计等待时间。
信号切换包含全停清空期


无扩展移动窗口时,信号从 VehicleGreen 开始。存在行人需求、车辆绿灯持续时间已满足且没有该信号下的活动车辆时,才进入 AllStop 或 PedestrianCrossing。若配置了 Clearance Duration,系统先进入 AllStop,时间结束后才开放行人。
行人阶段到期时,如果仍有 Crossing Agent,阶段时间被钳在上限并累计扩展次数;只有冲突区真正清空后,信号才通过下一次 AllStop 回到 VehicleGreen。许可时间结束不会强制清除仍在路口中的对象。
紧急 All Stop 是单独状态。请求会取当前剩余时间和新时长的最大值,并清空车辆预约、车道和租约映射;期间车辆与行人都不能新进入。它用于恢复保护,不是常规灯色之一。
冲突区所有权必须互斥
车辆取得预约前必须满足:信号为 VehicleGreen、冲突区没有活动 Crossing Agent。行人组被激活前则要求行人阶段允许、同区没有车辆预约。SetCrossingAgent 在存在活动车辆时拒绝写入;车辆预约也在存在行人时失败。
Demo 每步检查:
ActiveVehicleReservations 为空
或 ActivePedestrianGroups 为空
若两者同时非空,ConflictInvariantViolationCount 增加。该汇总针对当前单一验收冲突场景;通用多路口系统应按 ConflictZoneId 逐区检查,而不能把全城任何车辆预约与任何行人组视为冲突。
孤儿预约通过租约恢复


pedestrian resumed YES 且互斥检查仍为 YES。恢复阶段创建一个车辆预约 vehicle.article3.orphan,租约设为 1 秒,但不再提交 progress heartbeat。同时,一个行人组请求同一冲突区并进入等待。
Advance 每步减少活动车辆剩余租约。VehicleId 只有调用 ReportVehicleProgress 才会把租约刷新为配置时长。孤儿车辆没有心跳,租约归零后从所有冲突区、信号、车道和 Lease Map 中移除,ExpiredVehicleReservationCount 增加。之后信号进入行人许可窗口,行人类型内排序与等待保护将合法的等待组转为活动,HUD 标记 Pedestrian Recovered。
超时不是对所有慢车的强制清除。生产系统必须按网络延迟、车辆速度和路口大小选择租约,并允许有效车辆持续心跳。P9 只验证“失去所有者的预约不会永久锁死路口”。
从绿灯到实际进入之间有四层条件

| 条件 | 判定内容 | 非本层职责 |
|---|---|---|
| 信号阶段 | 当前方向是否处于许可窗口 | 不保证冲突区已经清空 |
| 冲突区所有权 | 是否已有车辆或行人占用共享空间 | 不保证出口有容纳空间 |
| 出口容量 | 下游能否接纳本次移动 | 不决定车辆怎样制动 |
| 车辆局部运动 | 车辆能否按车距和停止线执行许可 | 不改写信号与预约 |
车辆进入路口依次受信号许可、冲突区所有权、出口容量和局部运动条件约束。P9 实现前三层调度状态,并保留独立车队推进作为消费接口;这些结果尚未驱动可见车辆在停止线前制动或等待,车身执行属于 P11。本 Demo 验证四层职责边界和调度结果计算,不构成端到端车身通行验证。各层应保留独立等待原因;合并为单一 CanGo 布尔值后,将无法区分红灯、行人、前车和下游空间阻塞。
预约所有权与 Crossing Agent 不是同一个状态
预约表示“对象获得进入资格并占住未来冲突空间”;Crossing Agent 表示对象已经进入或仍实际占据该空间。二者会在一段时间内重叠,但生命周期边界不同。车辆可以刚获得预约尚未进入,也可以已进入后通过进度心跳继续延长租约;行人组则需要全部成员完成清空,才能结束实际占用。
信号状态因此不能只看预约数量。车辆预约已经释放但某个 Crossing Agent 仍在区内时,行人阶段不能立即接管。相反,一个预约持有者从未进入、也不再上报进度时,租约必须能够将其清除。P9 的孤儿夹具专门覆盖后一种失败。
共享序号不等于严格的跨类型全局队列

暂不满足许可条件的请求不会被删除。行人尚未获得过街窗口时继续留在 PendingPedestrianGroups;车辆受灯相、行人占用或不兼容车道阻塞时继续留在 PendingVehicles。等待超过 MaximumFairWaitSeconds 的行人会触发 StarvationPriorityCount,同一冲突区的新车辆暂缓获准。Pedestrian Demand 同时推动信号在满足车辆绿灯时长和清空条件后进入 AllStop,再切入行人窗口。
该组合覆盖行人等待保护和互斥所有权,但不属于通用全局公平调度器。若生产目标要求车辆、单个行人和行人组接受统一优先级比较,应将两张 Pending Map 投影为同一候选集合,在一次仲裁中比较等待时间、Sequence、紧急等级和相位切换成本。共享 Sequence 可作为统一排序的稳定依据。
五、固定步、场景控制与检查点恢复
Demo 使用 0.1 秒固定步。每步先推进 32 秒循环并判断阶段,再执行首次进入阶段的改道、容量或恢复初始化;恢复阶段继续推进信号与租约;随后刷新冲突统计并推进东、西两支车队。到达路线末端的车辆只有在入口包络空间空闲时才能回到起点。
NEXT PHASE 和五个场景按钮不直接写入 HUD 状态。它们暂时解除暂停,通过真实固定步运行到 8.1、16.1、24.1 或 25.2 秒,然后恢复原暂停状态。若当前时间已经超过目标,场景会先重置再前进。按钮缩短了验收等待,但仍执行同一运行时链。
暂停只冻结权威固定步
PAUSE 使 AdvanceTrafficOrchestrationDemoFixedStep 直接返回,FrameIndex、ElapsedSeconds、车队、队列和租约均不变化。摄像机与 HUD 仍可工作。自动化在第 69 步暂停并保存,确认暂停期间状态保持,再恢复运行进入改道阶段。
快照覆盖整个调度现场


检查点包含道路图、两支车队、逐车改道状态、容量预约状态、路口调度快照、Demo 汇总状态和三个阶段初始化标记。当前恢复前的交叉验证集中在版本为 1 的 Scheduling 快照内部,会检查:
- 活动车辆预约的 ConflictZone Key 与 VehicleId 非空,且 VehicleId 具有 Lane、有效 Signal 和 Lease 映射;
- 待处理车辆的 Map Key 与 VehicleId 一致;
- 活动行人组的全部 AgentId 已写入相同冲突区;
- 待处理行人组具有有效信号、冲突区和成员。
验证通过后才把调度状态写入 Demo,并清零固定步累加器、重建表现。RoadGraph、Fleet、Reroute、Capacity 和 Demo 汇总仍按同一会话快照直接复制,尚未建立覆盖整个 P9 状态的 schema、资源版本和跨模块引用预检。与 P8 一样,未满一个固定步的渲染时间相位不会保存;它保证恢复从固定步边界继续,不提供逐帧回放级时间精度。
RESET 会重建自由流场景并清空 Completed Cycle、累计改道、容量预约授予、过期租约和冲突违规等 SOAK 计数,但保留 Runtime Checkpoint 以及 capture/restore 次数。重置运行状态与删除检查点是两项独立操作。
恢复前验证内部引用。
调度快照由多张以 ID 为键的表组成。单独看每张表都可能合法,组合起来仍可能出现悬空引用:活动预约引用不存在的信号;Lease Map 有车辆但 Reservation Map 没有;活动行人组声明三个成员,Crossing Agent 表只登记两个;待处理 Map 的键与记录内 VehicleId 不一致。
P9 在应用快照前验证 Scheduling 内部关系,拒绝不完整的调度子状态。验证通过后整体替换 Scheduling,并重建场景表示。该顺序避免逐字段写入形成部分恢复状态;整个 P9 快照的跨模块一致性验证尚未实现。
当前道路图、车队和 Demo 汇总仍直接复制进会话快照,没有磁盘版本迁移。若未来写入 SaveGame,至少还要补:数据版本、Road Graph 资源版本、失效车道 ID 的重映射、已删除信号与冲突区的回退规则,以及旧版本容量预约的清理策略。
可复现按钮也是验收工具
场景按钮将人工走查映射到确定的逻辑时刻,不直接写入显示状态。Capacity 按钮运行到 16.1 秒,确保阶段初始化已经完成;Recovery 按钮运行到 25.2 秒,使租约完成过期并恢复行人组。
确定性跳转便于复测:代码修改前后可以在相同时刻比较 HUD、请求序号和车辆状态。自动化与人工录像调用同一固定步,不维护独立的测试结果生成路径。
六、验收与能力边界
每个阶段都要守住同一组不变量
四个场景阶段覆盖的调度条件不同,但验收不应只检查该阶段最显眼的数字。P9 在整个 32 秒循环中持续维护以下不变量:
| 不变量 | 典型破坏方式 | 诊断证据 |
|---|---|---|
| VehicleId 唯一 | 重置或改道时重复生成车辆 | 两支 Fleet 的身份集合 |
| 当前车道连续 | 新路线直接覆盖当前车道 | bCurrentLanePreserved、位置与 alpha |
| 路线拓扑连续 | 新段 From/To 接不上 | Route Apply 失败原因 |
| 单次批处理中的出口信用不超发 | 不同冲突区在同一批处理中分别消费同一出口 | Granted 与 CapacityBlocked |
| 冲突区所有权互斥 | 车辆预约与行人组同时活动 | Conflict Invariant Violation |
| 租约可回收 | 孤儿预约永久占区 | Expired Reservation Count |
| 暂停不推进 | UI 暂停但租约仍倒计时 | Frame、Elapsed 与 Lease Remaining |
| 恢复不重复副作用 | 阶段初始化再次建请求 | 初始化标记、请求数量、累计计数 |
例如在 Free Flow 中,Capacity 与 Pedestrian 计数都可能为零,但路线连续、身份唯一和暂停语义仍然有效;在 Recovery 阶段,租约过期成功也不能掩盖冲突区曾经同时出现两类所有者。Soak 测试用于验证这些不变量能否跨阶段、跨循环保持成立。
HUD 只读取运行时状态。
HUD 不保存自己的业务计数。Selected Route Cost 来自最后一次路径查询;Queued/Granted/Blocked 来自容量预约状态;Signal Phase、Active Reservation 与 Pedestrian Group 来自 Scheduling;车队数量、当前路线和改道标记来自 Fleet/Reroute。
该只读关系允许自动化在没有 HUD 的情况下检查同一状态,也允许更换 UI 而不改变运行时。若 HUD 自行累计“已改道车辆”,Reset、Restore 和循环边界可能与权威状态分叉。
失败分支也应进入人工走查
主视频记录成功改道、单批单信用预约授予和租约恢复。工程验收也包含失败分支:关闭主路与绕行路并确认返回不可达;提供断裂车道并确认路线应用被拒绝;将出口信用设为零并确认请求保留原数组位置和 WaitFrames;重复提交同一 VehicleId 并确认不会生成第二份预约;构造无效 Scheduling 快照并确认 RestoreSnapshot 在写入前失败。
这些用例不必全部进入文章主画面,但应纳入工程验收。失败分支用于确认无效输入不会形成部分写入状态,补足主视频对成功路径的验证。
人工走查
- 用
CAM: NETWORK建立车库、主干、南侧绕行、出口门与信号区; - 观察 Free Flow,确认 14 辆车持续循环;
- 点击
CREATE CONGESTION或PENALIZE PRIMARY,确认红车保持当前车道后才进入绕行; - 切到
CAM: BOTTLENECK,点击LIMIT EXIT,核对 3 个请求中仅 1 个获得 Granted;该状态表示预约授予,不表示车辆已经通过出口; - 切到
CAM: SIGNAL,依次触发PEDESTRIAN与STALE LEASE,观察孤儿预约过期和行人组接管; - 在容量阶段 SAVE STATE,运行到恢复阶段,再 RESTORE,确认同一转换再次出现;
- 连续运行多个周期,SOAK 中的 conflict violations 必须保持 0。
自动化证据


TrafficDynamicRouting 验证带拥堵与关闭上下文的加权路径,以及保持当前车道的路线交接。CityFlowSchedulingLifecycle 覆盖容量请求等待分数、单批授予预算和预约释放。TrafficSignalInterlock 覆盖车辆/行人许可、清空期、类型内排序、跨类型 starvation protection 与互斥。
TrafficOrchestrationArticle3Demo 检查暂停、按钮跳转、改道、单批单信用容量、租约过期、行人恢复、检查点和继续运行。TrafficOrchestrationArticle3DemoMap 验证地图、标签、固定摄像机、GameMode 与 HUD 接线。TrafficOrchestrationArticle3Soak 运行 1281 个 0.1 秒固定步,覆盖四个完整 32 秒周期,要求累计 16 次成功改道、4 次容量预约授予、4 次孤儿租约过期和 0 次冲突不变量违规。
| 统计字段 | 代码中的计数单位 | 跨周期累计 | 四周期 Soak 期望 |
|---|---|---|---|
CumulativeRerouteCount |
成功应用动态路线的事件;每周期目标 4 辆 | 是 | 16 |
CumulativeCapacityGrantCount |
单次批处理中的预约授予事件;每周期 1 次 | 是 | 4 |
CumulativeExpiredLeaseCount |
孤儿车辆租约过期事件;每周期 1 次 | 是 | 4 |
ConflictInvariantViolationCount |
车辆预约与行人组同时活动的违规固定步 | 是 | 0 |
证据采集在 Downstream Capacity 阶段的 step 171 保存检查点,推进到 Reservation Recovery 后再恢复;Phase、Time、路线、容量、信号和冲突状态均返回保存点。AllStop 与行人接管分别记录于 step 251 和 step 252,来自相邻权威固定步。运行至 step 1281 后完成 4 个循环,累计 16 次改道、4 次容量预约授予、4 次孤儿租约过期,Conflict Invariant Violation 为 0。
P9 冻结 tag 为 article3-traffic-orchestration-demo-v1/v2。当前候选已通过编译、通用 Smoke、ValidationAll、Standalone 操作检查和多周期观察。综合 ValidationAll 的 4 条已知警告属于全套验收环境,不是 P9 道路数据失败。
主场景覆盖:
- 拥堵代价触发改道,当前车道、VehicleId、位置与速度保持连续;
- 同一次
ProcessFairQueueBatch中,三个请求竞争一个共享出口信用,仅一份预约获得 Granted; - 信号阶段包含 AllStop 清空,孤儿车辆预约通过确定性租约过期释放,等待中的行人组随后接管冲突区;
- 车队、路由、容量、信号、预约和阶段可在会话检查点后恢复并继续;
- RESET 清空 SOAK 累计状态,同时保留检查点和 capture/restore 次数。
自动化补充覆盖:
- 小型项目自有车道图的距离、占用、拥堵三类软代价和硬关闭拒绝;
- 断裂路线、不可达路线和无效 Scheduling 快照拒绝;
- 重复预约去重、容量请求内部等待分数、类型内排序与行人 starvation protection;
- 四周期累计 16 次改道、4 次容量预约授予、4 次租约过期和 0 次冲突违规。
它尚未覆盖离线道路烘焙、World Partition 流送、跨帧大图寻路、多车道变换、复杂路口相位、网络级死锁、真实车辆包络通行时间和目标平台路由预算。车体运动与碰撞留给 P11;全城表现成本留给 P12。
迁移至城市路网时必须保留的合同
迁移到异步搜索后,以下状态合同仍需保持:
- 查询令牌可以取消,但旧路线在新路线提交前仍有效;
- 路网局部卸载时,稳定车道 ID 与保存数据要有可解释的失效路径;
- 拥堵输入可以更新,但不能让车辆无冷却地反复改道;
- 活动预约只有在完成、取消或超时后才释放冲突区所有权;跨固定步的出口信用生命周期仍需另行建立;
- 信号许可不能覆盖实际冲突占用;
- 行人与车辆的等待原因必须可分别诊断;
- Scheduling 快照必须先验证内部跨表引用;生产级全快照恢复还需扩展到 RoadGraph、Fleet、Reroute、Capacity 和阶段状态,再重建可见对象。
下一阶段需要补充规模证据:不同道路图大小下的查询队列长度、每帧展开节点上限、路线缓存命中率、拥堵更新频率、预约等待分布、租约误回收率和跨区恢复时间。上述指标稳定后,P9 的小型路网合同才具备进入城市运行时的条件。
Demo 颜色只承担诊断。
主路线、绕行路线、等待车辆、已预约对象和冲突区使用不同颜色,以便在同一画面中区分状态。颜色不参与选路、容量和调度决策;HUD 只读取运行时结果。将材质统一替换为灰色也不应改变固定步与自动测试统计。
正式表现阶段可以将这些颜色替换为道路标线、信号灯、车辆制动灯和人群动作,同时保留独立 Debug View。车辆停止时,单帧结果无法区分路线失败、出口满载、预约未获准和前车阻塞;P9 的诊断字段可继续用于编辑器可视化与线上日志。
与 P8、P10、P11 的接口边界
P8 的车辆调度决定“哪辆稳定身份的车可以从车库进入系统”,并提供初始路线需求;P9 决定它接下来可以使用哪些道路空间;P11 执行车身间距、制动和风险动作。P10 的行人过街则通过共享 Conflict Zone 和信号需求与 P9 相连。
P8 车辆派发与策略资格
↓ VehicleId / Route Need
P9 道路图、路径、容量与通行所有权
↓ Route / Gate / Reservation
P11 车辆运动、车距、制动与碰撞
P10 行人 Crosswalk Demand
↕ Signal / Conflict Zone
P9 路口调度
P9 不直接移动车辆 Actor,而是输出路线与通行约束,由车辆运动层消费。行人系统提交需求与实际占用,路口调度返回许可。P11 的车身模型和 P10 的角色动画由此可以独立替换,不改变信号状态机与冲突区所有权。
结语:通行权由拓扑、容量与所有权共同产生
P9 把交通编排从“沿路线移动”拆成三个可恢复的决定:动态路由选择未来空间,容量门限制同一次批处理中的预约授予数量,冲突区预约记录车辆与行人的共享空间所有权。信号提供许可窗口,却不能替代实际占用;改道提供新路线,却不能破坏当前车辆连续性;租约提供故障恢复,却不能替代有效对象的进度心跳。调度状态到车身制动和实际通行的执行链仍留给 P11。
下一篇 P10 将回到行人本身:人口身份和路口许可已经建立后,固定步 SoA 怎样推进人行空间、组织群组、处理避让,并把统一 Motion Frame 交给表现层。
AI 协作复盘
- AI 参与内容:检索原工程交通证据、UE5 道路图、动态路由、容量预约、信号调度、类型内请求排序、行人等待保护、快照和自动化测试。
- 主要修正:区分 HUD 参考成本与真实路由成本;确认
IncidentPenalty当前未进入选路公式;将出口信用限定为单次批处理;区分共享 Sequence 与分类型两轮仲裁;区分信号许可、活动预约、实际占用和 P11 车身执行;将跨表预检限定在 Scheduling 子快照;按代码确认四周期 Soak 累计 16 次改道。 - 人工判断边界:没有把同步小图搜索写成原工程跨帧全局寻路,也没有把整数容量信用扩张为完整交通流模型。
- 核验方式:正文对应
CyberTrafficOrchestrationDemoActor、CyberTrafficDynamicRoutingRuntime、CyberTrafficIntersectionSchedulingRuntime、容量预约运行时、六项自动化与 Article 3 验收文档。 - 遗留风险:大图搜索没有时间片,容量未直接绑定实时 Occupancy,租约参数尚未按道路尺度标定,恢复仍是会话内快照,复杂路口与跨区死锁尚未验证。
《《城市生命力》P9|UE5 交通编排:动态路由、容量门控与冲突区预约》有2条评论