# 17　阅读实验、调试路线与制作自己的 RTS

## 1. 学习目标：能解释、能预测、能验证

源码阅读最容易产生的错觉是“所有文件名都眼熟，因此已经理解”。更有用的标准是：给出一个变化，你能预测哪些字段、函数和系统受影响，并指出需要什么证据证明预测。

以下实验分为两类。前五个仅靠仓库和文本工具即可完成；后三个需要你后续自行建立可运行版本，本文只给实验设计，没有声称已经执行。它们不依赖你先把全部历史工程修好。

## 2. 实验一：给一个坦克制作数据身份证

**任务**：选择 `2TNK`，列出它的枚举、静态原型、类型池建立点、INI 读取点、主要运行期类。

先查 `UDATA.CPP` 的原型，再查 `UnitTypeClass::Init_Heap` 和 `Read_INI`，最后沿继承找到 `TechnoTypeClass::Read_INI`。不要把静态原型中的每个布尔参数凭顺序猜成游戏属性；对照构造声明核验。[原型](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/UDATA.CPP#L158-L187)、[类型池初始化](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/UDATA.CPP#L926-L963)、[共同数据读取](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TECHNO.CPP#L6239-L6319)。

**合格产出**：一张表中区分“代码固定、INI 可覆盖、实例独有”三类字段。若缺少实际 RULES.INI，武器、价格和生命的最终值应写“待资源确认”，不能根据记忆填入并称为源码结果。

**检验题**：只添加 `[MYTANK]`，为什么不一定增加一个能生产的新兵种？答案应涉及类型注册、名称解析、枚举边界、资源以及生产资格，而不只是“因为没有图片”。

## 3. 实验二：手算规则转换，不混淆单位

**任务**：用第05章教学输入，解释 `Range=5`、`Speed=50`、`Strength=420` 分别变成什么。

距离经 `Get_Lepton` 以格为输入，转换成内部坐标量；车辆速度经过 `Techno` 的缩放；生命是普通整数，但后续仍有类型、难度和实例状态影响。[距离转换](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/CCINI.CPP#L282-L286)、[速度缩放](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TECHNO.CPP#L6212-L6219)。

**预期结果**：按一格256的坐标量，Range 得1280；Speed 50缩放为128。这两项都不是墙上时钟计时的“米/秒”。数值进入不同消费者时还会受到地形、任务、计时和取整影响。

**扩展题**：分别比较 `Speed=99`、`100`、`101`、`-1` 的裁剪与取整；再比较武器速度读取和车辆速度读取是否使用同一函数。目的不是证明“写负数能作弊”，而是学会追踪每个字段自己的转换契约。

## 4. 实验三：画出一条有障碍的寻路过程

**任务**：在纸上画一块小网格，加入一个 L 形障碍，标出起点和目标。对照第04章，跟踪直线尝试、碰障后的两侧边缘搜索、路径选择和平滑。

**合格产出**：标明每一步检查的是静态地形、移动类别、区域还是动态占用。说明为什么到达相同目标的不同单位可能得到不同结果，以及为什么已有路径仍会因另一辆车挡路而失效。

不能直接画一张标准 A* 的 open/closed 表然后称为本项目算法。先检查 `FINDPATH.CPP` 的实际状态和循环，再使用自己的算法术语。[寻路实现文件](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/FINDPATH.CPP)。

**扩展题**：你会怎样构造一个让局部绕障产生不理想结果的地图？预测应明确是无法到达、路径过长还是容易拥堵，三种现象不能混称“寻路错误”。

## 5. 实验四：从“点了却不动”定位问题层级

按顺序寻找以下源码入口：`FootClass::Active_Click_With`、`TechnoClass::Player_Assign_Mission`、`Queue_Mission`、`EventClass::Execute`、任务分派、寻路和移动提交。

可使用这些只读检索，路径大小写按仓库保留：

```bash
rg -n 'Player_Assign_Mission|Queue_Mission' CODE/TECHNO.CPP CODE/QUEUE.CPP
rg -n 'case MEGAMISSION|Assign_Destination' CODE/EVENT.CPP
rg -n 'MissionClass::AI|Commence|Assign_Mission' CODE/MISSION.CPP
```

**合格产出**：给三个不同故障各安排一个最早可辨别的观察点：没有入队、事件执行时对象失效、路径被障碍阻断。避免在还没确认任务写入之前就改寻路代码。[入队函数](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/QUEUE.CPP#L241-L283)、[执行时检查](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/EVENT.CPP#L676-L710)。

## 6. 实验五：核查一条注释，找出真正契约

选一个看起来含义很明确的函数，例如生产扣款、伤害衰减、连接包顺序或智能指针。分开抄下“注释承诺”和“函数体实现”，再查调用者是否依赖该承诺。

示范是 `FactoryClass::AI`：阶段先推进，钱不够时退回；余额归零与暂停在完成条件里处理。真正契约要从分支中读出来，而不是只把它概括成“更新生产”。[阶段和资金代码](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/FACTORY.CPP#L201-L239)。

**合格产出**：对疑点写出条件、字面结果和待验证部分。不要把所有不符合现代习惯的代码立即叫作 bug，也不要因为注释完整就忽略代码相反的证据。第A4附录提供更多可练习对象。

## 7. 实验六：建立最小可运行观察场景（待环境复原）

在具备合法游戏资源和可调试构建后，先做只有一名玩家、几辆普通坦克、一个障碍和一个靶建筑的场景。固定规则、初始随机状态和命令序列。

建议记录帧号、对象稳定标识、任务、坐标、NavCom、TarCom、开火计时、生命和关键创建/销毁事件。一次只改一个参数，如炮弹伤害或射程，并比较轨迹。不要同时更换编译器、修改寻路、调整平衡和提高帧率，否则差异无法归因。

**通过标准**：不仅最后胜负相同，关键状态轨迹也能解释。若首个不同点在对象创建顺序，后面伤害与随机结果可能只是其连锁后果。

## 8. 实验七：确定性与存档恢复（待环境复原）

对同一初始状态和命令流运行两次，记录同步相关状态。先确认同一构建能复现，再比较迁移构建。若不同，定位第一处状态分歧，而不是只对最终截图。

存档试验则在某帧保存，继续运行一段；另一路加载后输入相同后续命令，比较对象关系、队伍、工厂、任务和随机序列。类型、虚表、池槽与指针编码均可能影响恢复，详见[第14章](14-save-load-and-binary-formats.md)。

特别注意：新增日志应只读状态。这个项目中把随机对象转换成整数会取下一个随机值；为了打印而触发这种转换，可能改变你要观察的程序。[随机对象整数转换](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/RANDOM.H#L48-L67)。

**通过标准**：明确比较了什么状态、忽略了哪些本地表现数据、从哪个帧开始一致，不能仅以“没崩溃”宣布兼容。

## 9. 实验八：替换一个平台模块（待环境复原）

如果目的是学习移植，选一个边界明确的模块，如文件读取、像素输出或定时器。先保留游戏层接口，再用现代实现替换底层，建立可比较的输入输出。

像素输出可比较索引色缓冲、调色板与最终截图；定时器可比较不同游戏速度下的逻辑推进和等待行为；音频可比较采样格式、播放结束通知和资源生命周期。它们的通过条件不同，不能以“窗口弹出”代替渲染正确，或以“能听到声音”代替播放队列正确。

修改整数宽度、结构布局和对象遍历顺序属于更深的兼容性工作。先证明旧行为，再决定哪些行为有意改变。迁移路线和构建缺口详见[第15章](15-platform-libraries-tools-and-porting.md)。

## 10. 今天制作一个小 RTS，哪些思想值得留下

| 设计问题 | 可以借鉴的思想 | 今天可明确化的部分 |
|---|---|---|
| 大量同类单位 | 类型数据与实例分开 | 类型 schema、稳定 ID、引用检查 |
| 多种操作来源 | 将输入表达为语义命令 | 命令版本、验证、可记录执行顺序 |
| 持续任务 | 显式状态与定时推进 | 状态图、调试可视化、可测的转移条件 |
| 世界空间 | 占用、地形、移动能力分开 | 清晰的空间查询契约与动态障碍策略 |
| 内容制作 | 规则与场景覆盖 | 覆盖顺序、单位、错误提示和编辑器约束 |
| 运行一致性 | 固定规则、顺序与随机序列 | 确定性回归、版本化数据和状态摘要 |
| 对象生命周期 | 入场、离场、销毁显式处理 | 带代次的句柄、所有权和订阅清理 |
| 平台迁移 | 封装文件、显示、计时、音频 | 平台边界与可验证后端 |

这些是工程建议，不是声称原项目已经实现所有现代保障。也不必把所有历史结构原封不动搬进新游戏。深继承、全局变量、固定容量池、汇编优化都与当时约束有关；是否适合今天，要看项目规模、目标平台和兼容性要求。

## 11. 建议的自制 RTS 里程碑

1. **可观察世界**：网格、单位、选中与移动，能显示坐标、任务和路径。
2. **完整战斗闭环**：目标、射程、冷却、弹体、伤害、销毁及引用清理。
3. **经济闭环**：资源、采集、生产、扣款、交付失败处理。
4. **内容与局部自主行为**：类型配置、任务状态机、少量关卡触发器。
5. **可复现性**：命令记录、稳定更新顺序、保存恢复；若要联机，再建立同步层。
6. **表现与工具完善**：动画、声音、侧栏、资源制作和性能分析。

每一步应让前一步继续可观察、可解释。没有必要一开始复刻全部兵种、剧情影片或旧联网服务；但若目的是兼容原版，则必须把兼容范围明确写成独立目标，不能把自制近似玩法称为已经移植原引擎。

## 12. 本资料的验证产物

提供的 `index_source.py` 会重新统计跟踪文件、行数和 SHA-256；`audit_guide.py` 会核对源文件链接、提交和行号范围。它们帮助复核这套分析与哪个源码快照对应。

已经完成的是源码获取、主要子系统静态研读、文档交叉核验和结构检查。这里设计的游戏运行、网络联机、存档往返与移植实验尚未执行；相关原因和剩余问题见[覆盖附录](../appendices/A3-evidence-coverage-and-limitations.md)。
