系列《城市生命力》· P7 · UE5 逻辑 Demo
P1 从成熟开放世界的运行时结构出发,讨论稳定身份、区域人口、分层预算与表现交接。本篇转向 UE5 重建工程,实现并验证一套可运行、可存档的人口编排原型,重点分析 Full、Simulated、StubTier 三级逻辑档位的成本控制。行人运动属于 P10,表现方案与 L2 表现落地分别属于 P13 和 P19。
对应前篇:《城市生命力》P1|开放世界的人口编排
导言:逻辑人口优先于角色表现
人口原型常从批量生成角色起步。几十个运动对象足以填充场景,却不会自动形成身份所有权、街区人口目标、区域迁移、表示预算和存档恢复。
P7 建立一套独立于角色表现的逻辑人口,再将部分身份交给池化 Actor 或批量实例。验收地图使用临时几何体;即使关闭表现,身份、区域、日程、层级和存档仍可运行。

本文所称“三级降级”属于逻辑调度分层,与角色模型的缩放或显隐无关。Full、Simulated、StubTier 分别决定更新频率和玩法资格;Actor、ISM、Marker 或不绘制属于表现选择。原工程的 Entity Stub 是跨表示持续存在的身份底座,与 UE 枚举中的最低频 StubTier 含义不同。三个逻辑档位均持续保有 AgentId 与 Zone 记录。
本文以 LargeBlock_Demo 的操作过程组织验证。每项操作依次对应原工程依据、UE5 实现和验收结果:
| Demo 操作 | 观察对象 | 原工程依据 | UE5 重建机制 |
|---|---|---|---|
| 启动 PIE | 72 个身份全部绑定,三层同时存在 | Stub 先于实体,身份独立于表示 | AgentId、Zone 记录与 SoA |
| 按 F1–F5 | 社区在居住、通勤、工作和休闲阶段间变化 | Community、时间段与 EntrySpawner | JSON 日程、工作点和 Schedule Coordinator |
| 按 F6 | 目标缺口、待处理数与活动人口逐步收敛 | 注册、生成与 attach 分段受理 | 有限生命周期批处理、休眠重激活与延期计数 |
| 移动玩家 | 请求层、容量目标、实际层和表现租约逐步变化 | 重要性竞争、挂载预算与静态 LOD | Full/Simulated/StubTier、滞回、容量目标和转换预算 |
| Quick Save/Load | 当前 PIE 会话内恢复人口快照、观察者位置并重建表现 | 社区、存根和实体 ID 持久化 | Zone 快照、会话级观察者状态、schema 闸门与表现重建 |


从原工程到 UE5:移植的是运行时语义
P1 已分析原工程的人口系统。本篇只选取直接影响重建的五项运行时语义:身份先于表现、需求先于生成、请求受预算约束、表示可反复挂卸、失败与回收具有明确状态。实现不逐类复刻内部类型,也不宣称覆盖完整的人口能力。
原工程的人口系统是一层协调器
原工程的人口运行时模块承担协调职责,范围超出单次 NPC 生成。源码目录包含人口池、社区、社区条目生成器、街头人群、远景人群、响应单位生成、可见性、预算、传送和存档等多组实现。核心人口系统连接以下对象:
- 关卡作者配置的社区与作息数据;
- 先于实体存在的轻量存根;
- 运行时实体生成服务;
- 街头人群使用的车道槽,以及与车辆共享的道路拓扑基础;
- 远景点渲染数据;
- 存档与跨系统事件。

源码调用关系要求人口实体注册前已有对应存根;注册请求随后交由人口池,并在后续池更新中统一处理:
实体添加请求
→ 确认 Entity Stub 已存在
→ 进入注册阶段
→ 写入人口注册队列
→ 后续池更新统一处理
添加入口返回成功仅表示请求已受理,不保证实体完成构造、挂入世界或具备 AI、动画和交互状态。UE5 侧不能以 SpawnActor 返回值概括人口系统的完整生命周期。
人口需求与空间依据来自社区或车道槽,稳定身份从 Stub 开始
原工程常规人口的一条主要来源是社区系统。社区数据描述一组人口条目、活动区域、时段和生成阶段;社区随区域流入后进入激活队列,条目生成器再为每个成员寻找空间依据。对带有工作语义的条目,它会先预留 AI spot,取得世界变换后才请求创建或恢复存根。
存根通过异步请求创建。条目生成器保存创建 token,成功后再执行“存根已创建或已恢复”的后续流程。没有空闲位置、预留失败、区域流出、条目停用和创建失败都属于正常状态;部分失败会进入定时重试,停用时必须取消尚未执行的回调。若实体需要跨越社区生命周期继续存在,系统还提供孤儿容器式的交接路径,使其不随原条目立即丢失。
街头背景人群由另一条路径产生。人群控制器从交通车道片段申请槽位,并围绕槽位建立存根;行人还可关联工作点。槽位可能在存根创建期间失效,因此完成回调仍需重新检查依赖。源码中的存根控制器保存裸存根指针,并通过锁保护下的删除回调清除映射,要求系统显式管理存根生命周期。
两类来源入口不同,随后汇入同一处理顺序:
社区条目 / 街头人群需求
↓
空间槽或工作点成立
↓
创建或恢复 Stub
↓
注册到人口系统
↓
按预算请求实体表示

作息表产生需求,不直接批量生成
社区作息由时间段和生成阶段构成。阶段记录数量、生成标记或 spot,以及是否按序处理。时间段管理器在配置变化时先注销旧回调,按当前游戏时间立即应用一次,再注册新的时间通知;跨午夜时会回绕到最后一个合法时段。回调还按帧号去重,避免同一帧重复切换。
社区系统以任务和受限预算处理区域流入、流出和激活队列。区域进入可用状态仅表示相关社区可以开始推进,不表示全部存根和实体已完成。该机制将“内容期望”与“本帧实际完成量”分离:时间表可一次提出较大的数量变化,人口系统按自身节拍逐步处理。
原工程的生成链分成注册、构造和挂载
注册请求进入人口池后,池更新处理注册与注销,评估未挂载记录的重要性,并决定实体请求对象。构造阶段创建生成上下文,将 token 写入生成队列后返回。生成回调仅设置“需要检查已生成对象”的标记;后续池更新确认 token 完成后取得实体并安排 attach。
完整路径如下:
Stub Create / Restore Request
→ StubRequestCompleted
→ Stable Entity Stub
→ Population Registration Queue
→ Entity Spawn Request
→ EntitySpawnCompleted(尚未挂载)
→ Attach Budget / Queue
→ EntityAttached
→ GameplayReadyFlags 逐项成立

任一阶段都可能等待、失败或取消。删除请求若命中生成中的对象,将取消对应 token;清理仍须等待 token 进入完成态。实体生成成功后,attach 事件可能早于部分下游副作用完成,因此“已挂载”与“玩法完全就绪”属于不同状态。
原工程还会限制同一阶段可推进的数量;后台队列达到压力阈值时取消较弱请求,为高重要性对象释放处理额度。预算耗尽后的工作延续到后续帧,不由当前帧全部承担。
两类预算控制“有实体”和“能挂载”
原工程的人口池同时维护已挂载实体与未挂载实体的使用量和上限。生成完成但暂时不能 attach 的实体可以进入未挂载集合;当高成本名额释放后,再从候选中选择对象挂载。若未挂载集合也达到上限,系统需要回收较弱对象或拒绝继续扩大队列。
候选选择同时考虑静态类型、来源、距离、高度差、可见性、兴趣区域和请求策略。人口池依据重要性决定是否释放未挂载名额、回收低优先级实体,以及允许视野内生成。可见对象、即将进入视野的对象和不可见对象使用不同判断路径,以降低相机转动引起的高成本表示竞争。
实体挂载后还会根据静态 LOD 裁剪组件。Crowd、Simple、Occupant 等档位除模型差异外,低成本档还会移除状态效果、小队、感知、肢解、投射物和物体携带等能力。原工程的降级同时减少实体数量、降低单体模拟开销并缩减组件集合。
P7 只还原实体数量、单体模拟成本和表现租约的逻辑基础,没有复刻尚无法用临时角色验证的组件过滤表;这部分留到 L2 表现与玩法组件接入后实现。
删除同样经过判定、排队和交接
原工程的 RemoveEntity 会进入注销流程,并取消同一身份尚未结束的生成;已有实体可能立即 dispose,也可能进入淡出队列,或因仍可见而延迟。立即删除判定会区分未挂载记录、正在淡出的实体和当前可见实体,避免把“请求删除”解释成“本帧已经消失”。
街头人群还定义了具体的清理原因。控制器会处理车道流出、超出生成范围、交通停滞、死路、生成错误、密度过高,以及进入视野却长期没有实体等情况。多种原因最终汇入统一的槽位—存根删除阶段;交通停滞对象还要同时满足距离与不可见条件才会被回收。
远景人群则是另一条视觉数据路径。远景控制器跨多帧选择车道、更新点缓冲并向渲染端提交结果;系统关闭或有效车道不足时会发送空结果。远景点可在合适位置触发常规人群生成请求,但点本身没有稳定身份、玩法状态或实体完成语义。
dot → stub → NPC 不是原工程的身份变形链。远景点负责视觉密度,Stub 负责稳定身份,挂载实体负责交互能力。
重建工程保留语义,不照搬内部形状
UE5 重建采用项目自有 SoA,人口主线不依赖 Mass Entity、MassTraffic 或 ZoneGraph。UE 原生方案具备承载人群的能力;P7 的选择用于优先验证原工程中不会由通用框架自动提供的运行时语义。对应关系如下:
| 原工程机制 | UE5 P7 对应实现 | 有意保留的差异 |
|---|---|---|
| 动态 Entity Stub | AgentId、Zone 活动/休眠记录、SoA 状态 | P7 将身份字段拆入多个项目结构,没有复制原类型层级 |
| Community 与 EntrySpawner | JSON 社区日程、Schedule Coordinator、工作点槽 | 当前只验证三社区和确定性空间槽,不复刻完整 AI spot 系统 |
| 注册队列、spawn token、attach 调度 | 生命周期限额队列、表现交接请求、Actor 池同步 | P7 没有伪造多线程异步 token;用可观察的固定步队列表达同一预算边界 |
| Attached / Unattached 实体容量 | 逻辑层级容量与表现租约容量分别控制 | 两边都采用“分阶段裁决成本”的原则,状态本身不一一对应 |
| Static LOD 组件裁剪 | 暂未实现 | 等生产角色组件进入 L2 阶段后再建立可验证的裁剪表 |
| 车道存根与异常清理 | Zone 生命周期、休眠、区域迁移 | P7 先证明身份连续性;具体行人空间图与避让放到 P10 |
| Distant Crowd dots | 不纳入 P7 身份层 | 匿名远景密度留给后续表现阶段,避免与 Stub 混写 |
| 社区与人口存档 | Zone 状态记录、独立社区快照 API、Demo SaveGame | Quick Save 只写 Zone 记录,不包含社区成员表 |

P7 以运行时合同为还原单位。确定性 SoA、Zone 生命周期、逻辑 LOD 和表现池验证三项性质:身份不依赖 Actor,预算耗尽时工作可以延期,表现能够从逻辑状态重建。
身份与数据底座
四类标识符及其生命周期
工程同时使用 AgentId、活动数组索引、稳定槽号和表现池槽。四者均可定位数据,但生命周期不同。
AgentId 是跨层引用的逻辑身份。区域迁移、日程变化、层级切换和存档恢复都以它为对照。活动数组索引只用于访问当前 SoA 列;数组使用交换删除保持紧凑,记录被移除后,末尾记录会进入空位,因此索引不能写入持久引用。
稳定槽号属于逻辑存储。删除记录时,槽号进入空闲表;新记录可以复用槽号,但仍获得自己的 AgentId。表现池槽只对应当前租用的 Actor。身份从 Full 降到 Simulated 后,Actor 槽可立即释放并重新分配,逻辑身份和稳定槽不受影响。

PIE 用 runtimeBound=72、duplicateAgentIds=0 和 missingAgentIds=0 验证身份绑定。交换删除可改变活动索引,降级可释放 Actor 槽,迁移可改变 Zone;只有 AgentId 必须贯穿全过程。存档因此保存身份与必要逻辑状态,不保存可重建的数组索引和 Actor 池槽。
状态所有权也需要单独约束:
| 状态 | 权威所有者 |
|---|---|
| AgentId、社区、Zone、职业、活动/休眠 | Zone 人口记录 |
| 位置、速度、路线进度、固定步计数 | SoA |
| 当前日程需求 | Schedule Coordinator |
| 请求层、容量目标与实际层 | LOD Resolver 输出,Zone 保存最新结果 |
| Actor、ISM、Marker 租约 | Representation Pool |
| SaveGame | Zone 状态的序列化投影;不包含独立社区成员表 |

SoA 写回 Zone 的位置与调度字段是供迁移、存档和表现交接使用的快照,不改变 SoA 对运动状态的权威所有权。
人口主线采用项目自有 SoA
当前实现没有把 Mass Entity、MassTraffic 或 ZoneGraph 设为人口主线依赖。早期兼容实验仍可保留,但权威状态由项目自有的结构数组、固定步调度、区域记录和表现接口维护。
SoA 将同类字段按列保存:
struct FCyberPopulationSoAState
{
TArray<FName> AgentIds;
TArray<FVector> Positions;
TArray<FVector> Velocities;
TArray<float> SpeedsCentimetersPerSecond;
TArray<int32> RouteOffsets;
TArray<int32> RouteCounts;
TArray<int32> CurrentTargetIndices;
TArray<int32> AgentFixedStepCounts;
TArray<int32> StableSlotIds;
TArray<int32> FreeStableSlotIds;
};
实际结构还保存速度、路线循环策略、完成标记、共享路线点缓冲和从 AgentId 到活动索引的查找表。逻辑位置不在 Scene Component 中,路线进度也不依赖角色移动组件。表现层只能消费这些列产生的结果,不能把自己的 Transform 反写为人口事实。
区域、协调器和表现桥仍可使用 UE Actor 接入关卡,但一名逻辑行人无须对应一个可 Tick 的 UObject。未来即使更换批量计算载体,AgentId、区域所有权、日程和表现交接合同仍可保持不变。
数据列与共享路线缓冲
路线不以 TArray<FVector> 逐人嵌套保存在活动记录中。SoA 使用一块共享路线点缓冲,再为每个身份保存 offset、count 和当前目标索引。这种布局提供连续访问,并明确限定生成、交换删除和存档转换涉及的字段集合。
配置有两条入口:普通 Configure 按 AgentId 稳定哈希选择路线模板;ConfigureAssignedRoutes 恢复已经分配的逐人路线。后者要求 AgentId、路线、初始位置、目标索引、速度和循环策略等长,并逐条检查路线、速度与索引。两条入口最终生成同一种 SoA;任一输入不完整便拒绝配置,避免列错位后继续运行。
生命周期批次由 ApplyLifecycleBatch 完成。它先验证既有 SoA,再逐项交换删除并添加合法请求;稳定槽优先从 free list 复用。结果记录请求数、实际完成数、复用槽数和最终结构状态。
LifecycleResult ApplyLifecycleBatch(DespawnIds, SpawnRequests)
{
if (!ValidateState(Current)) return Failed;
RemoveUniqueAgentsAndRecycleSlots();
AppendValidAgentsAndReuseFreeSlots();
RebuildAgentIndex();
Result.bSucceeded = AllRequestsApplied() && ValidateState(Current);
return Result;
}
bSucceeded=false 仍可能伴随状态改动。重复删除、非法生成或后置不变量失败可能发生在部分删除、添加完成之后,而当前函数没有变更日志或快照回滚。该布尔值只表示“请求全部兑现且最终状态通过校验”,不具备事务提交语义。调用者须按可能的部分提交处理失败并停止推进,不可直接重试整个批次。生产接口应区分 RejectedNoChange、Committed、PartiallyCommitted 与 InvariantFailure;原子批次还需全量预检,或在临时状态验证通过后再提交。
固定步按层级分配更新频率
人口运行时维护统一调度帧号,个体是否在本步推进则由层级步幅或外部准入掩码(admission mask)决定。Demo 中 Full 每个固定步更新,Simulated 每两个固定步更新,StubTier 每八个固定步更新。未获准推进时,AgentId、最后位置、目标索引和逐人固定步计数仍可查询。
低频对象获得更新时,最大位移按 Speed × FixedDelta × Stride 计算,速度也除以同一段模拟时长。单一路段会补偿跳过的固定步,实际速度不会因低频调度自动降至 1/2 或 1/8。若一次位移抵达路径点,当前实现会停在该点并切换目标,不继续消费剩余距离;现有测试只验证 1/2/8 步幅和更新数量,没有证明 Full、Simulated、StubTier 在多拐点路线上的等时进度。严格时间守恒与视觉插值留给 P10 补测。

外部 mask 可暂缓某个身份的运动而不改变表现层级。固定步结果按层记录请求、推进、跳过、路线完成数、步幅和帧号,用于区分“本步未获得更新资格”和“身份丢失”。Demo 的 1/2/8 步幅只验证多速率调度,不是平台最终参数。
删除活动记录不等于销毁身份
活动 SoA 采用紧凑数组。删除时,各列在同一个位置执行交换删除,末尾记录补入空位,随后重建受影响的 AgentId 索引;被释放的稳定槽号写入 free list。生成批次会先检查 AgentId 非空、路线有效、速度有效且身份未重复,再优先取用空闲槽。
该过程保持连续数组的批量遍历效率,避免长期生成和回收留下大量空洞;稳定槽则允许外部统计逻辑存储复用,同时避免将易变数组索引用作身份。
生命周期验证会检查:
- 所有 SoA 列长度一致;
- AgentId 唯一且查找表指向正确索引;
- 活动槽号唯一;
- 空闲槽号唯一,并且不与活动槽重叠;
- 活动槽与空闲槽之和覆盖已经分配过的槽号范围。
自动化按“删除一个身份、生成替代身份、继续固定步、再次释放”的顺序验证 free list。验收条件包括身份无重复、路线继续推进和复用后结构合法,数组数量相等只是一项表面结果。
Zone、日程与生命周期
Zone 管理人口记录,不为每个人创建管理 Actor
人口区域是日程需求与生命周期收敛的单位。一个 Zone Actor 保存该区域的密度预算、活动人口记录、休眠记录、流动路径和表现预算。这些记录是 UE Demo 的状态载体,不等于原工程 Entity Stub,也不是场景 Actor。
区域首次接收人口请求时,先以密度预算限制接纳数量,再为每个被接受的对象建立稳定身份、原型、区域、逻辑层级和流动位置。区域状态可以回答当前有多少活动人口、多少休眠人口、还有多少生成或回收差额,以及上一步有多少请求因容量被拒绝。
Zone 不决定各时段的人口需求。它接收目标数量,并以有限批次执行重激活、生成或回收;需求由上层日程协调器提供。职责边界如下:
日程协调器:某时刻每个区域需要多少、哪类人口
Zone:在容量和每步限额内,使当前人口趋近目标
SoA:推进已经活动的逻辑人口
表现池:显示当前被批准的表示
Zone 活动记录保存社区、职业、日程、工作点和生命周期标记;SoA 保存位置、速度和路线进度。二者以 AgentId 对照,固定步后只把交接所需快照写回 Zone。数量、身份或区域不一致时,状态构建失败。
一次区域更新按以下相位执行:
接收目标人数
→ 协调器先处理跨区迁移(当前无独立限额)
→ Zone 按增长额度重激活或新建
→ Zone 按回收额度转入休眠
→ 更新活动与休眠记录
→ 配置或修补 SoA 状态
→ 按逻辑层级执行固定步
→ 计算新的 LOD 申请
→ 生成表现交接快照
日程数据给出需求,不直接生成角色
验收地图使用一份项目自有 JSON,定义三类社区、三种职业和三处工作点。市场摊贩、办公人员和维护人员各有全天日程;每段日程给出阶段、活动、目标区域、目标工作点和活跃密度比例。
运行时加载后先验证日程。每个小时的中点样本必须恰好命中一个条目;区域、社区和职业标识不能为空,密度比例必须合法。通过验证的日程才能解析为需求:
社区 + 当前小时
→ Home / CommuteToWork / Work / Leisure / CommuteHome
→ 目标区域
→ 目标工作点
→ 目标活动人口数
目标人数等于社区基础人数乘以该阶段的密度比例,并限制在零到基础人数之间。日程切换只改变“应当有多少人位于哪里”,不会在同一个函数中立刻生成所有 Actor。
ValidateSchedule 要求目标 Zone 有效,起止小时位于 0–24,密度比例位于 0–1,并在 24 个小时中点检查唯一命中;起始时间大于结束时间时按跨午夜处理。数据模型允许浮点小时,却没有执行区间端点级覆盖检查,因此它只能证明 24 个样本唯一覆盖,无法排除样本之间的短空档或重叠。冻结 JSON 使用整数小时边界,所以当前验收数据可用;生产验证仍应拆分跨午夜区间、排序端点并检查 [0,24) 连续覆盖。
TargetActiveAgentCount = Clamp(
Round(BasePopulationCount * ActiveDensityScale),
0,
BasePopulationCount);
验收数据中的三个社区基础人数均为 24,三处工作点也各提供 24 个槽。相同容量便于测试区分日程需求与空间接纳能力,不代表生产城市中的岗位容量必须等于社区人口。工作点不足时,协调器可以报告占用与等待,但尚未实现人员重新择业或动态选择其他工作点。
验收地图用 F1 至 F5 切换 06:00、08:00、12:00、18:00 和 22:00。每次先解析社区目标,再迁移或重激活既有成员;HUD 检查职业、Target、Active、Dormant 与迁移计数,AgentId 集合应保持连续。三种职业只是数据驱动样本,不代表通用职业经济模拟。

JSON 覆盖与失败策略
验收场景先构建可运行默认数据,再尝试读取 JSON 覆盖工作点与社区日程。文件缺失或 JSON 语法解析失败时记录 Warning 并继续使用编译期默认值;解析成功后才替换非空覆盖字段。
覆盖数据不能跳过项目数据验证。工作点需要合法的职业、区域、位置和正数槽位;社区日程引用的目标区域必须能在场景注册表中找到。协调器配置完成时还会统计日程涉及的全部 Zone,实际解析出的 Zone 数量与预期不一致便拒绝启动。
解析成功不代表语义有效。覆盖数据仍须通过数据集、日程和场景绑定校验;文件存在但引用非法时,系统不会退回默认日程继续运行,而是将人口场景配置判定为失败。缺失或语法错误采用显式 Warning 与默认回退;解析成功后的语义错误则采用 fail closed。
Zone 内生命周期队列把人口增减摊到多个步骤
日程从清晨切到工作时段时,多个区域的目标数量可能同时变化。立即补齐所有缺口会把生成与表现同步集中到一帧;立即删除过量对象则会造成成批瞬时消失。
日程协调器使用固定生命周期间隔,为各 Zone 写入目标数量,并设置增长与回收上限。Zone 内部的休眠重激活和新记录创建共用增长额度,活动记录转入休眠则消耗回收额度:
- 需求增加时,优先从休眠集合恢复旧身份;
- 休眠记录不足时,创建新的逻辑记录;
- 需求减少时,从较远对象开始转入休眠;
- 未处理的差额保留到下一生命周期步骤;
- 目标超过区域容量时,记录被拒绝的数量。
该队列不是后台线程,也不模拟原工程的多令牌异步生成链,而是 UE5 原型中的显式有限批处理协议。HUD 分别显示待生成、待回收、延期和容量拒绝,区分“需求已提出”与“对象已就绪”。
协调器默认以 0.25 秒为处理间隔,每个 Zone 的增长和回收额度各为两个。F6 将二者降为一个,并把间隔调为 0.35 秒,使 Zone 内差额可观察。表现池只在逻辑处理后消费交接结果。

生命周期状态至少区分五类计数:目标缺口、待生成、待回收、因区域容量被拒绝、因本步限额被延期。例如目标从 8 变为 24,而本步只允许生成 2 个,状态应记录为“完成 2 个,剩余缺口 14 个等待后续步骤”,不能记作“生成失败 14 个”。将延期误报为失败会导致上层重复提交请求,最终产生过量人口。
P7 没有照搬原工程的实体 token。当前逻辑身份创建不依赖资源流送,也没有生产角色异步加载,因此 token 状态机缺少可验证的异步对象。现阶段保留队列受理、预算延期、失败计数和表现后置等语义;生产角色接入后,再把 Actor 资源加载与 attach 阶段替换为实际异步流程。
F6 能证明 Zone 内重激活/新建和转休眠受额度限制,不能证明跨 Zone 迁移有界。
迁移、重激活与新建的执行顺序
目标区域出现缺口时,协调器先迁移同一社区位于其他区域的成员,再转移其他区域的超额活动或休眠记录,最后由目标 Zone 在增长额度内重激活本地休眠记录或创建新身份。
这条链并非全部共享同一预算。冻结实现会在进入 Zone 生命周期队列之前批量提取并接纳跨区记录,按社区迁移时使用无上限请求;源区提取、目标区接纳和 Zone 记录中的归属更新都不消耗增长/回收额度。一次时段切换仍可能同步移动多条记录,该过程也不更新展示运行时持有的独立社区成员表。P7 已有迁移失败退回协议,但没有 MaxMigrationsPerStep;生产化必须为跨区迁移设置独立预算,或将其纳入统一 LifecycleWorkBudget。
需求下降同样不会直接释放身份。超额活动记录进入休眠,之后可以被同一社区在另一区域接纳。因此,“上下班”首先改变人口所有权和活动状态;只有缺少可复用记录时,身份总量才会扩大。
实现按区域顺序协调这些步骤,并使用 AgentId 处理确定性平局。当前合同只有一个权威协调器:Zone 内增减有界,跨区迁移仍可能批量执行;多街区并行化还需迁移预算与并发事务。

回收先进入休眠集合
P7 的回收不是最终销毁。Zone 需要降低活动人口时,会在候选中优先选择距离较远者,将记录从活动集合移动到休眠集合,设置为非活动、隐藏表现,并保留 AgentId、社区、职业及其他可恢复状态。
下一次需求增加时,休眠记录先于新身份被激活,以减少日程往返造成的身份抖动。06:00 与 12:00 往返切换后,既有社区成员的 AgentId 集合应保持连续。
休眠、原工程 Entity Stub 与 UE Demo 的 StubTier 需要区分:
- Entity Stub 是原工程跨表示存在的身份底座;
- StubTier 是 UE Demo 活动人口的最低频逻辑档,仍可参与低频宏观更新;
- 休眠记录不属于当前活动目标,表现隐藏,等待未来需求;
- Actor 释放只表示失去完整表现,不等于进入休眠;
- 匿名远景点不在 P7 的身份生命周期中。
当前休眠集合仍保存在 Zone Actor 内,可用于验证单街区与多区域迁移合同。扩展到整座城市时,还需要把不活跃区域的数据分片、流送或聚合到更高层存储;该生产化问题不在 P7 的验证范围内。
区域迁移移动的是记录所有权
日程目标可能位于另一个区域。协调器先按社区找出位于非目标区的活动或休眠成员,从源 Zone 提取状态记录,再交给目标 Zone 接纳。接纳失败的剩余记录会尝试退回原区域,避免迁移中断后身份失去归属。
展示运行时另有一份用于社区连续性验收的成员表,保存 AgentId、社区、Zone、最后门户和迁移次数,并支持独立快照校验。日程协调器的跨 Zone 迁移不更新这张表,P7 的 Quick Save 也只保存 Zone 快照;因此成员表、门户与迁移计数都不会随 Demo SaveGame 恢复或重建。
验收地图将 72 个逻辑身份划分为三个社区;一组自动化记录显示其中 48 个身份经过门户迁移。该结果验证了所有权连续性,但未覆盖 World Partition 的生产流送。当前门户属于项目自有空间合同,验证范围是源区交出、目标区接纳和失败回滚,不涉及真实城市分区加载。
三级降级与表现租约
三级降级经过请求、容量目标和实际应用三阶段
层级管理器接收 AgentId、距离、当前层级和 ForceFull 标记。进入/退出阈值先结合当前层级得到距离请求,避免对象停在边界附近时反复升降。本文将这个尚未经过容量裁决的结果称为 DistanceRequestedTier。
容量裁决使用一套稳定名额排序:强制 Full 优先,其次是请求层与当前层一致的对象,再按距离和 AgentId 平局规则排序。普通 Full 超出目标容量后改投 Simulated,Simulated 超出目标容量后改投 StubTier。这个结果在概念上称为 CapacityTargetTier;现有代码没有单独字段,而是把容量调整后的结果继续写在 RequestedTier 中。
CurrentTier + Distance + Hysteresis + ForceFull
↓
DistanceRequestedTier
↓
稳定名额排序 + Full / Simulated 目标容量
↓
CapacityTargetTier(代码中为容量调整后的 RequestedTier)
↓
升格 / 降格执行排序 + 每步转换预算
↓
AppliedTier + Deferred

目标容量不是本步的硬上限。转换预算耗尽时,待降格对象保留旧层并记为延期,实际 Full 或 Simulated 数量可能暂时高于目标,再经多个步骤收敛。ForceFull 可以越过普通 Full 容量,升格时也不消耗普通名额,运行时另行统计其数量。冻结 Demo 尚无绝对强制上限;生产系统需增加 MaxForcedFullSafetyCap,同时记录强制升格数与超额数。任务语义可以绕过普通名额,但不能绕过系统级安全上限。
容量名额排序与转换执行排序不是同一套规则。前者保护仍合法的既有层级;后者把升格按强制、近处和 AgentId 排序,把降格按远处和 AgentId 排序。平局使用 FName::LexicalLess 的跨进程稳定字典比较,不使用内部索引、哈希表遍历顺序或对象创建顺序。
移动玩家会同时触发请求层变化、容量目标调整和受限转换。HUD 的 tier transitions、deferred、pool leased 与 unassigned 用于区分“逻辑层尚未收敛”和“逻辑层已请求高成本表示,但表现池没有空槽”。验收按 AgentId 检查变化,不以区域颜色变化代替身份级验证。

LOD 解析输入不是场景 Actor。输出汇总实际 Full、Simulated、StubTier 数量,以及升格、降格、延期和强制 Full 数量。现有结构没有同时保留 DistanceRequestedTier 与 CapacityTargetTier;正文中的三阶段名称用于解释算法,不代表三个已经落地的成员字段。若 HUD 需要分别显示距离请求与容量裁决,应增加对应诊断字段。
容量稳定性与现层保留偏好
如果只按距离分配容量,两个距离接近的对象会在边缘频繁互换名额。容量排序对“请求层与当前层一致”的对象给予优先级,使已有逻辑档位在仍合法时尽量保留。进入/退出滞回控制空间抖动,保留现层偏好控制容量抖动,两者解决的是不同问题。
强制 Full 用于任务或近场交互等少量重要身份。它不消耗普通升格次数,并可越过常规 Full 容量。冻结实现只统计强制对象数量,没有绝对安全上限;若整组人群均被标记为强制 Full,系统将突破普通预算。生产实现除限制内容配置外,还需设置不可绕过的运行时安全上限,并将超额暴露为容量错误。
每步统计已执行升格、降格和延期数,HUD 与测试据此区分“没有层级变化”与“转换受预算延期”。
逻辑层级与表现租约是两套决策
Full、Simulated、StubTier 决定更新频率和玩法资格;Actor、ISM、Marker 或 None 由表现策略与资源预算决定。两层使用独立枚举,通过交接请求建立映射,不存在三组恒定的一一对应关系。
| 逻辑档位 | Zone 表现预算结果 | Actor 池再次失败时 |
|---|---|---|
| Full | Actor;Actor 预算不足则 ISM,再不足则 Marker | Actor 请求若 Unassigned,本轮为 None,不再二次回退 |
| Simulated | ISM;实例预算不足则 Marker | 不进入 Actor 池竞争 |
| StubTier | P7 调试 Marker;产品可为 None | 不进入 Actor 池竞争 |
表现分两次裁决。Zone 先根据可见 Actor 与简化实例预算写出 RequestedRepresentation:Full 优先 Actor,失败后可在此阶段降到 ISM 或 Marker;Simulated 只竞争 ISM,StubTier 使用调试 Marker。交接到表现池后才得到 AppliedRepresentation 和分配结果。

StubTier Marker 对应稳定 AgentId,仅用于录制和验收,与 P1、P6 中的匿名远景人群无关。产品版本可以关闭该 Marker,逻辑身份仍然存在。
完整表示由预创建 Actor 池提供。同步时收集本帧需要 Actor 的 AgentId,并与池中现有租约对照:仍需要的身份保留槽位,不再需要的槽位释放,新身份取得空闲槽。Actor 位置来自交接请求,表现组件不得改写 SoA 位置。
取得 ISM 表示的身份没有逐人 Actor、碰撞和 Tick,每个实例由逻辑身份的当前位置生成。表现同步还统计重复 AgentId;同一身份在一轮交接中出现两次时,系统仅接受第一条、记录错误,并阻止两个可见表示同时存在。
表现池在配置时预创建固定数量的完整 Actor 槽,并初始化两个实例组件:一个承载简化表示,一个只在验收时绘制 StubTier Marker。同步函数先清空本轮 ISM/Marker,再扫描交接请求:无效或未就绪请求跳过,重复 AgentId 计入错误;Actor 请求写入期望租约集合,简化表示与调试 Marker 直接提交实例变换。
随后,槽池以 AgentId 集合执行一次同步,结果分成四类:
- Preserved:身份上轮已经租用槽位,本轮仍需要 Actor,继续使用原槽;
- Released:身份离开 Actor 层,槽位先归还;
- Assigned:新的 Actor 身份取得空槽并配置原型与位置;
- Unassigned:Zone 已请求 Actor,但池槽不足;身份保持有效,本轮没有 Actor,池内不再回退到 ISM 或 Marker。
Actor 槽更新时先根据租约找到对应交接请求,再配置原型、调试表现和世界位置;无法找到请求或配置失败的槽会被隐藏并关闭碰撞。池状态向 HUD 报告总容量、已租用、可用、简化实例数、StubTier 逻辑数、Marker 数,以及四类同步结果和重复身份数。
同一个 Full 可能在 Zone 预算阶段得到 ISM/Marker,也可能在请求 Actor 后因池容量不足进入 Unassigned + None;两种结果需要分别统计。Full 只授予高频逻辑资格。依赖逐人碰撞、动画或交互的玩法还须检查 AppliedRepresentation == Actor,不能以 Full 代替 Actor Ready。
冻结 Demo 会在下一轮同步再次尝试未分配请求,但没有连续失败计数、宽限步数或强制退出策略;只要 Zone 的 Actor 请求预算与池容量长期不一致,Unassigned + None 就可能持续。生产实现应记录 ConsecutiveUnassignedSteps 与 UnassignedReason,超过 MaxUnassignedGraceSteps 后重新进入 Zone 表现策略或报告容量配置错误;依赖 Actor 的交互在此期间必须拒绝进入 Ready 状态。
表现交接只传递状态快照
交接请求包含 AgentId、区域、原型、位置、流动索引、逻辑层级、生命周期状态和期望表现层级。为避免与原工程的实体就绪语义混淆,本文将其中代码字段 bReady 称为 RepresentationSnapshotReady:它只表示交接快照字段完整、可供表现同步消费,不表示 EntitySpawnCompleted、EntityAttached 或任何 GameplayReadyFlags。表现池处理后不会取得人口所有权,也不会改变 Zone 中的活动/休眠集合。
Actor 池同步分为保留、释放、分配和未分配。未分配身份的 AgentId 与逻辑位置仍在,下一轮可重试;工程由此分别观察逻辑 Full、Zone 请求 Actor 和本轮实际租约三个事实。
ISM 与 Marker 每轮按交接快照重建实例,不保留可反向访问的个体组件。若玩法需要与某个身份交互,必须先通过 AgentId 找到逻辑记录并取得合适表示,不能从实例索引推断持久对象。这与前文“不把活动数组索引当身份”是同一项纪律在渲染侧的延伸。
空间槽与存档恢复
工作点目前是确定性空间槽
项目数据为三处工作点定义标识、职业、Zone、位置、槽位方向、数量与间距。协调器计算需求、可占用数和等待数;展示层则直接使用 Hash(AgentId) % SlotCount 选择位置。
市场人员沿摊位排列,办公人员靠近办公锚点,维护人员在设施区域活动。头顶的 V、O、M 只是 Demo 标签,用于区分职业与当前活动。它们不属于人口权威状态。
哈希结果可重复,但当前实现没有开放寻址或唯一占用表,不同身份可能落在同一槽位。OccupiedJobSlotCount 只是 min(Demand, SlotCount) 的容量统计,不表示可视位置唯一。P7 验证了日程需求到确定性锚点的映射;唯一槽分配、等待队列、动画对位、抢占和失败恢复仍未实现。
存档保存区域人口,不保存临时表现
Demo SaveGame 保存 schema 版本、当前小时和各区域的人口状态记录;Actor 池租约、ISM 索引和 Marker 不进入存档。QuickSaveCity 在同一 PIE 会话内还会由玩家角色另行缓存观察者 Transform、控制器朝向和相机模式。这部分不属于 UCyberPopulationDemoSaveGame,也不是跨进程持久化数据。
原工程的人口存档以社区、条目生成器和存根状态为主要边界,加载时还需要处理版本分支、已保存实体 ID、社区状态映射和仍被其他系统持有的对象。P7 没有复刻这套完整格式,但沿用了同一原则:保存可重新生成表现的逻辑事实,不序列化渲染对象本身。
UCyberPopulationDemoSaveGame schema 1 的顶层只有当前小时和 Zone 快照数组。通用记录的实际字段来源如下:
| 状态 | 当前来源与 Load 行为 |
|---|---|
| AgentId、原型、Zone、活动/休眠 | Zone Snapshot 先应用;Zone 与活动状态随后可能被 reconcile 改写 |
| CommunityId | 随 Zone 记录反序列化,表示该记录的逻辑归属;不等于独立社区成员表已经恢复 |
| 职业 | 随 Zone 记录反序列化,随后可能由社区日程配置重新分配 |
| Activity、JobSite | 保存的是当时调度结果,加载后的即时 reconcile 可以重写 |
| JobSlot | 保存的是 Demo 展示槽结果;位置由稳定哈希派生,不是唯一预约事实 |
| 路线位置、目标索引、固定步相位 | 从 Zone 记录恢复 |
| PlayerDistance、LOD | 快照暂存并恢复,之后才随观察者更新 |
| LastPortal、MigrationCount、社区成员表 | 不在 Demo SaveGame 中恢复 |
| Actor 槽、ISM 索引、Marker | 不保存,加载后重建 |
该格式混合了持久事实、可恢复模拟状态和派生缓存。PlayerDistance 与 LOD 不属于长期世界事实;生产格式应重新计算,或明确采用“恢复调度快照后,按当前观察者复核”的协议。
保存流程遍历所有 Zone。加载先检查 schema,再逐 Zone 应用记录;任一区域失败便返回,但已应用区域不会回滚。随后 ApplyPopulationScheduleHour 会立即执行一次 reconcile,可能迁移、重激活、创建、休眠并重写职业/活动分配,而不是只设置时钟;最后同步展示数据和表现池。
因此加载结果必须拆成三阶段:SnapshotApplied 表示 Zone 快照已反序列化并通过基础校验;ScheduleReconciled 表示系统按保存时间重新计算需求与人口关系;RepresentationRebuilt 才表示依据重算后的逻辑状态重新租用表现。最终状态是“应用快照后得到的新合法运行状态”,不保证所有 Zone、职业、Activity、JobSite、JobSlot 与 LOD 字段仍逐值等于存档内容。
独立社区成员表不在这条 Quick Load 路径中:它既不会从 SaveGame 恢复,也不会从 Zone 记录重建,展示运行时会保留加载前的成员表状态。Zone 记录中的 CommunityId 仍可参与日程协调,但 LastPortal、MigrationCount 和独立成员索引没有随存档回退。P7 的 Quick Load 因而不是完整社区所有权恢复。
保存:时间 + ZoneId + 人口状态记录
加载:schema 校验
→ SnapshotApplied:逐 Zone 应用人口记录
→ ScheduleReconciled:按保存时间立即重新协调
→ RepresentationRebuilt:重建表现租约与实例

当前工程的会话级 Quick Load 先调用 LoadPopulationDemoState,完成 Zone 记录应用、日程协调和表现同步,再恢复玩家 Transform、控制器朝向和相机模式。录制脚本先在 12:00 的近距离状态保存,随后把观察者移动到远处,使人口由 Full 逐步降为 Simulated 与 StubTier,再切换到未保存的 06:00。Quick Load 最终回到 12:00、72 个活动人口和 24/32/16 的三级分配;保存范围为三个 Zone、72 条人口记录,观察者位置恢复误差为 0.0 cm。
视频验证了 Zone 逻辑快照的成功路径和表现资源重建。跨版本迁移、派生状态复核、社区成员表和大规模分区存档不在当前覆盖范围内。
加载过程没有全局暂停事务,也没有“恢复时间但抑制派发”的模式。现有顺序只保证 Zone 记录先于日程 reconcile 和表现重建。生产加载还需先全量预检,在临时状态恢复 Zone 与社区索引,暂停派发后设置时间并重算距离和层级,验证通过后原子提交。
观察者恢复存在顺序缺口:人口加载先恢复快照中的 PlayerDistance 和 LOD 并同步表现池,QuickLoadCity 随后才恢复玩家 Transform。视频证明同一 PIE 会话中的最终位置可精确恢复,三级表示也能重新收敛,但加载瞬间仍可能使用快照距离或旧观察者状态。恢复后必须复核 AgentId 唯一性、活动/休眠归属、Zone 容量、距离和层级;Actor 池槽属于可重建缓存,不参与一致性判断。观察者状态只保存在玩家角色的会话内存中,进程重启后无法从 Population Demo SaveGame 恢复。
Demo 验收与能力边界
验收同时检查状态、场景和规模
P7 验收分为运行时、PIE 场景和规模三层,场景中出现可见角色只是其中一项结果。
第一层是纯运行时合同:SoA 列一致、free list 合法、固定步分层生效、24 个日程中点唯一命中、Zone 内生命周期队列收敛、社区迁移所有权有效、存档成功路径一致,以及人口主线没有意外依赖实验插件。
第二层是 PIE 场景合同。冻结地图请求 72 个行人身份,自动化检查 72 个身份全部绑定运行时状态,没有重复或缺失 ID;Full、Simulated、StubTier 同时存在并由调试表现显示,职业和工作点标签完整,玩家移动会改变个体层级而非整群统一变色。保存的一次验收报告还记录了三个社区和 48 个迁移身份。
第三层是规模合同。多区域自动化覆盖 1024 个行人和 128 辆车,并执行 2048/256 的压力配置。验收检查逻辑数量、身份唯一性、社区所有权、工作上限和恢复结果;运行时间只作诊断,不构成跨机器性能指标。
纯运行时测试会主动构造无法从截图识别的错误。例如 SoA 生命周期测试先删除中间记录,确认交换进入该位置的身份索引已更新,再生成新记录验证 free list 复用;日程测试覆盖普通区间和跨午夜区间,验证冻结整数小时夹具的 24 个中点唯一命中,并能拒绝测试样本覆盖到的空档、重叠和非法密度。任意浮点端点的严格连续覆盖仍需补端点排序与连续性校验。LOD 测试把多个候选放在相同距离,依靠 AgentId 平局规则检查结果是否可重复;依赖隔离测试则扫描人口主线,防止后续代码无意重新引入 Mass、MassTraffic 或 ZoneGraph 前提。
PIE 测试检查类实现之外的地图接线。它确认 GameMode、人口展示 Actor、Zone、道路和人行横道等权威对象存在,72 个请求身份全部取得运行时绑定,AgentId 无重复或缺失,三种逻辑层和表现池均已运行。职业标签、社区迁移、路线推进和玩家距离层级也从场景状态读取,不仅检查 C++ 函数返回值。
规模测试使用聚合状态和确定性摘要验证多区域结果,无须为 2048 个逻辑行人创建同等数量的 Actor,因此可单独检查身份与调度在规模扩大后的正确性。该测试不替代渲染、动画、导航和平台帧率测试;验证范围限于人口架构的所有权一致性与单步工作上限。
P7 冻结基线与 P7–P12 总收口基线分别承担单篇验收和全系列对应关系确认。具体提交、地图 blob、测试报告和警告数量保存在私有证据索引中,公开正文只保留读者能够从 Demo 复核的结论。
冻结之后,共享工程继续接入行人运动、路口预约与跨系统整合,部分 LargeBlock 展示代码随之变化,但地图没有重存。这些变化属于后续文章或综合验证,不能反向扩张 P7 的完成边界。P8–P12 各自使用独立验收地图,截图和结论仍可回到明确版本。
一次完整的 Demo 走查
打开 LargeBlock_Demo 并开始 PIE 后,HUD 显示 72 个运行时身份以及 Full、Simulated、StubTier 数量。青色对象来自完整 Actor 池,橙色对象是简化实例,紫色矮标记表示活动人口中的最低频身份。三层数量随玩家位置变化,单次截图中的分布不代表固定配置。
沿街移动时,附近身份逐个进入更高层级,远处身份逐步退出。验收关注四件事:总逻辑身份仍为 72,池中没有重复租约,层级转换数受每步预算限制,移动前后的 AgentId 集合一致。若画面颜色变化但身份数或重复计数异常,不能视为成功。
按 F1 至 F5 可观察清晨、通勤、工作、休闲和返家。V、O、M 分别标出三种职业,颜色与 HUD 活动字段反映当前日程。工作时段中,市场、办公和维护社区迁往各自目标区;切回清晨后,部分记录进入休眠或迁回居住区。
按 F6 后,每个 Zone 的增长和回收额度各为一个。HUD 中目标先变化,Zone 内重激活/新建和转休眠逐步收敛;跨 Zone 迁移可能在 reconcile 中批量完成,不属于 F6 有界证据。它也不证明真实 Spawn Token、资源加载或 Attach Ready 链。
执行 Quick Save,移动观察者并切换时间后再执行 Quick Load,可验证 Zone 快照得到应用、AgentId 与基础人口记录保持有效,并在恢复时间后重新完成日程 reconcile 与表现重建;Actor 槽号不进入存档。当前视频还验证了同一 PIE 会话内的玩家位置可恢复到 0.0 cm 误差。观察者 Transform 属于角色保存包装器的易失会话状态,不在 Population Demo SaveGame 中;该流程也未验证所有 Zone、职业、工作点与层级字段在最终画面出现前逐值保持,未覆盖独立社区成员表恢复或恢复过程的原子可见性。
验收警告与测试范围
保存的 P7 PIE 报告状态为成功、零错误,但含一条渲染线程控制台变量警告,因此不支持“所有运行日志零警告”的结论。P7 验收文档记录的编译、Smoke 与 ValidationAll 在冻结基线为零错误和警告;P7–P12 总收口基线的综合 ValidationAll 记录了四条已知警告。不同时间与测试范围的结果需要分别描述。
不同测试套件覆盖不同责任:纯运行时测试验证数据合同,PIE 验证地图连接,渲染警告属于场景运行环境。合并为“全部通过”会掩盖测试范围与诊断信息。

当前 Demo 的验证范围
P7 已在 UE5 工程中验证以下运行链条:
- 逻辑人口可以脱离逐人 Actor 存在;
- AgentId 在日程、层级、表现和区域迁移中保持稳定;
- Zone 内的休眠重激活、新记录创建与转休眠通过有限生命周期队列逐步收敛;跨 Zone 迁移已有所有权交接与失败退回,但仍可能批量执行,尚未纳入单步预算;
- Full、Simulated、StubTier 采用不同更新频率,并受容量目标与转换预算约束;
- Actor 池、ISM、Marker 或不绘制可以消费同一逻辑人口快照;
- 存档恢复不依赖保存临时表现对象;
- 独立地图、HUD、自动化和 Git 基线可以重复验证上述结论。
它尚未证明生产级角色网格、动画、局部避让、过街观感、远景人群和整体音画。P10 会处理行人运动原型,P13 讨论行人调度侧的表现方案,P19 才把这些方案接入 UE5 的 L2 表现资产。
上述边界将身份、调度和预算问题与动画、材质、镜头问题分开。P7 的临时几何体不承担最终画面验证,只用于确认人口系统不依赖成品资产也能运行。
结语:以运行时合同交付人口原型
P7 最终交付的是一组运行时合同:AgentId 维持稳定身份,SoA 持有权威运动状态,Zone 管理区域记录,日程协调器提出人口目标,Zone 生命周期队列限制本地增减,LOD 管理器控制模拟成本,表现池租用易失资源。Demo SaveGame 保存的是混合快照,不是生产持久化模型。
这些合同允许独立替换角色资产、动画系统或批量计算载体。若身份与表现仍绑定在同一个 Actor 上,表现升级将持续牵动身份和生命周期代码。
下一篇 P8 将沿用同样的验收方法,讨论车辆身份、车库请求、固定步调度与自动驾驶策略如何在 UE5 中形成逻辑 Demo。
AI 协作复盘
- AI 参与内容:整理工程任务记录,检索人口 SoA、区域生命周期、日程、社区迁移、LOD、表现池、存档与自动化测试,并将旧实现稿逐项对照现行代码。
- 主要修正:复审明确了 Entity Stub/StubTier、容量目标和 Ready 边界,并核实
Stride × FixedDelta、中点日程验证、迁移无单步预算、Actor Unassigned 不再二次回退,以及 Quick Save 不含社区成员表等限制。 - 人工判断边界:区分原工程 Entity Stub、UE Demo StubTier、调试 Marker 与匿名远景点;区分活动数组索引、稳定槽号和 AgentId,并把工作点、SaveGame 和规模测试限制在原型验收范围。
- 核验方式:正文结论同时对应运行时代码、冻结 Demo、验收文档、自动化报告、P7 版本 tag 与
8230d49c总收口基线;未以任务对话中的完成陈述替代代码证据。 - 遗留风险:多拐点时间等价、严格日程区间校验、迁移预算、唯一工作槽、原子 SoA 批次和事务化社区存档仍未实现;性能数据也不能替代目标平台实测。
《《城市生命力》P7|UE5 人口编排:稳定身份、分层调度与表现租约》有1条评论