Files
mtgodot-poc/docs/RENDERING-REPAIR-PLAN.md
T
shenandshen c3a6fb973e fix(rendering): checkpoint main-character recovery and visual regression work
Handle late main-character data, guarded model construction, bounded retries and map correction. Validate player model sources and add lifecycle and real-asset visual regressions.

Include pending material and character-selection changes, updated A1 screenshot, and the detailed rendering repair plan. Hair occlusion, full UI parity and final macOS package acceptance remain unfinished.
2026-09-08 08:52:30 +08:00

266 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 新客户端渲染修复实施路线
更新日期:2026-09-08。目标客户端:本仓库 `metin2-client`,第一交付平台为 macOS。
本文是实施规格,2026-09-08 已开始第一批 R0/R1 修改;阶段结果见文末执行记录。未标明通过的工作仍待执行,不代表整体渲染或最终包完成。
## 1. 目标、依据与当前状态
第一交付目标:实际 macOS 客户端进入游戏后显示正确地图和真实人物,身体/发型/装备组合正常,人物在地面上,底部技能栏可用。之后再精细还原 40250 的材质、环境、树木、特效和性能。
画面真值优先使用用户当前 40250 Windows 客户端的同资源、同场景截图。EnriqueGF 的场景截图是可行性和场景完整性参考,不能直接当作 40250 像素真值;其主角背对镜头,也不能证明正面发型和所有动画正确。原始骨骼数值以固定版本 Granny oracle 的可复现实测为参照。
| 当前问题/证据 | 状态 | 实施要求 |
| --- | --- | --- |
| 用户反馈进入游戏出现胶囊、背景异常 | 待真实客户端复验 | 检查地图、实体、相机与加载时序,不直接归因于模型格式 |
| `game_scene.gd` 已加入 `_sync_main_character()``_main_model_attempted` | 工作区有补丁,尚未完成最终画面验收 | 检查晚到实体、失败重试、角色切换和重连的生命周期 |
| 本地 `world_demo.gd` 曾加载真实 A1 地图及模型 | 历史组件证据 | 只能证明这组资源链路可运行,不能证明联机 `.app` 正常 |
| 发型挡脸、人物悬空 | 用户反馈,根因仍需隔离 | 分别验证变换、蒙皮、深度、地形高度与动画接触点 |
| 底部技能未显示 | 用户反馈,已有 `quickbar.gd` | 检查创建、布局、图标、服务端槽位恢复,不重新发明快捷栏 |
| 日志曾出现 `unknown GC header 59 (last: 32,148)` | 历史协议阻塞,是否仍存在待复验 | 若重现,单列协议问题;不得跳过未知包继续解析来伪造联机通过 |
| 选角 `flip_winding`、材质及视觉测试已有未提交修改 | 实验状态 | 保留现有改动,记录基线,再逐项验证,不全局翻转或覆盖 |
历史 README / MIDREVIEW 的阶段完成描述不代表当前最终包通过。现有截图、日志在使用前检查是否存在、属于哪个构建和资源版本。
## 2. 两个上游项目的使用边界
本路线依据已经检查的固定提交;上游后续变化需要重新审查:
- EnriqueGF/metin2-godot`078d39a41a3ef17c9e09fb9807956cb1a2f16641`
- kcanakdag/metin2-godotime`6a82d793c1d83e34f3e71200fdfc67a78bcb96d3`
| 参考实现 | 本项目的应用方式 | 不应推导的结论 |
| --- | --- | --- |
| EnriqueGF`tools/compare_visual_golden.py` | 引入区域、遮罩、误差阈值和差异图方法 | 截图存在或整体平均误差低不代表脸部正确 |
| EnriqueGF`tools/build_character_glb.py``godot/client/online/presentation/warrior_actor.gd` | 同一角色离线 GLB 对照;核对身体/发型/武器的组合和动作同步 | 转换包含 TRS 分解、舍弃 shear;不能假定动画无损 |
| EnriqueGF`godot/client/online/presentation/avatar_registry.gd` | 借鉴有效实体数据驱动创建、切地图清理、重复数据处理 | 其快照协议不能直接替代我们的原版协议事件 |
| EnriqueGF`tools/prepare_legacy_ui_assets.py``godot/client/online/ui/` | 核对 `.sub` 裁剪、原版布局和按钮状态 | 西班牙语素材、字体、窗口尺寸不能直接替代当前中文配置 |
| EnriqueGF`tools/audit_city_asset_dependencies.py` | 地图资源依赖追踪、缺失分类和报告 | 全部依赖可解析不等于全部已经正确渲染 |
| godotime`tools/export_playable.py` | 实际包资源清单、哈希与加载审计,适配 macOS | Web/Linux 通过不代表 macOS 通过 |
| godotime`content/profiles/p0-warrior-dog.json``client/scripts/content/` | 模型、动作、附件和依赖的统一验证清单 | 三个精选模型通过不代表四职业和全量换装通过 |
| godotime`tools/test_browser_accounts.py` | 借鉴双客户端、重连、换装及账户生命周期用例 | 不迁移其 SpacetimeDB 服务端来修复渲染 |
参考源码:
- [EnriqueGF 固定版本](https://github.com/EnriqueGF/metin2-godot/tree/078d39a41a3ef17c9e09fb9807956cb1a2f16641)
- [EnriqueGF 场景截图](https://github.com/EnriqueGF/metin2-godot/blob/078d39a41a3ef17c9e09fb9807956cb1a2f16641/docs/images/ingame-shinsoo.png)
- [godotime 固定版本](https://github.com/kcanakdag/metin2-godotime/tree/6a82d793c1d83e34f3e71200fdfc67a78bcb96d3)
直接移植 EnriqueGF 原创代码时保留 MIT 声明和提交来源。godotime 在本次检查的提交没有项目级许可证,先借鉴方法;直接复制实现前明确授权。原始游戏资源的来源与使用范围沿用本仓库 [THIRD-PARTY.md](THIRD-PARTY.md),不把上游许可证视为原版资产授权。
## 3. 技术路线与顺序
执行顺序:R0 基线 → R1 场景装配 → R2 模型/发型 → R3 材质/环境 → R4 UI → R5 最终包验收 → R6 性能与内容扩展。R4 可在 R1 后独立推进;R5 的资源清单工具可提前建设,最终验收必须等待 R1–R4 完成。
1. 保留原版网络协议与当前 GR2 主链路,以最小改动修复实际缺陷。
2. 引入独立 GLB 对照场景,不先全量转换资源或更换运行时。
3. 对照决定具体资源采用哪条路径;结果需要记录,不能预设 GR2 或 GLB 必然更好。
4. 角色与静态物件分别决策。静态物件可先试离线 Godot 资源缓存;人物保留完整仿射矩阵的需求单独评估。
5. 每项修复必须在真实 GameScene 和导出包中复验。单模型预览只能作为中间证据。
## 4. R0:建立可重复基线
涉及现有文件:`test/compare.py``project/char_select_visual_test.gd``project/world_demo.gd``project/game_camera.gd`
### 具体步骤
1. 记录当前 Git 提交、未提交补丁摘要、Godot/导出模板版本、扩展库哈希、macOS/GPU、渲染后端、窗口和实际渲染分辨率。不能仅凭提交号标识脏工作区构建。
2. 固定资产来源及哈希:身体、发型、武器、护甲、MSM/MSA、动画 GR2、纹理、地图和环境。两个上游与我们可能使用不同资源版本,必须先核对。
3. 为每个场景保存地图名、服务器坐标、地图基点、转换后本地坐标、相机位置/目标/FOV、角色朝向、装备、动作及取样时间。
4. 建立三种独立场景:无网络的单角色实验台;真实资产的 GameScene + FakeClient;实际服务器上的导出 `.app`
5. 保存正面、侧面、背面和脚底近景,以及选角界面和游戏全景。待机/跑步/攻击每段至少取开始、中间、结束,过渡动作另测。
6. 在现有截图工具基础上增加局部区域和差异图。脸、头发、脚底、技能栏不允许被动态遮罩排除;时间文字、随机粒子等可单列遮罩。
7. 区分两类比较:同引擎修复前后用于自动回归;40250 对照用于保真评估。没有匹配的相机和动作时,只能做人工结构检查,不执行严格像素判定。
拟新增产物(尚未实现):`test/rendering/cases.json``test/rendering/README.md``tools/rendering/compare_regions.py`。本地日志/截图/报告建议放 `build/rendering/<run-id>/`,不覆盖失败证据。
阈值:先在不改代码的条件下重复采样三次,测量阴影/抗锯齿噪声,再设置每区域阈值。报告包含 MAE、RMSE、变化像素比例和遮罩范围;不能为了让错误图片通过而放宽阈值。
完成条件:每条用例可由配置重复启动,并能将截图关联到确定的代码、资源、相机和构建;缺参考图的用例标为待验证。
## 5. R1:修复主角装配、地图定位与相机
涉及现有文件:`project/game_scene.gd``project/net_world.gd``project/net/mapcoord.gd``project/ui/player_view.gd``project/game_camera.gd`。必要时追踪 `extension/src/net/m2_client.cpp` 的事件顺序及 `extension/src/metin2_world.cpp` 的地图数据。
### 具体步骤
1. 增加定点诊断:阶段进入、地图就绪、主角 VID 获得、实体行有效、模型创建、贴地定位、相机开始跟随。每条带角色/场景生命周期编号,避免把上一次登录数据误认为当前数据。
2. 复核现有 `_sync_main_character()`:明确“等待数据、加载中、成功、失败”的状态;缺 race 等必要数据时保持等待,重复事件不创建重复人物。
3. 检查 `_main_model_attempted` 是否在失败后永久阻止恢复。数据或资源就绪条件变化时允许受控重试;永久资源错误显示明确失败原因,不能每帧重复加载或刷日志。
4. 切角色、地图切换、重连时清理旧模型/相机目标/信号并重置对应状态。异步完成结果必须核对是否仍属于当前角色与场景。
5. 统一通过 MapCoord 转换:服务器全局厘米 → 地图本地米。现有地图本地 Z 约定与上游不同,禁止直接复制对方轴翻转或地图中心公式。
6. 验证坐标往返、合法边界及高度查询位置。`(0,0)` 可是合法地图基点,地图是否就绪应有明确状态,不能仅靠非零基点推断。
7. 在地图可查询、角色位置有效后计算地面高度并绑定相机;模型就绪后移除胶囊。资源等待期间提供加载状态,不能把永久占位当正常游戏画面。
### 必须补充的回归
- 主角 VID 在 setup 前已有、setup 后出现、实体行再晚一帧、多帧后出现。
- 不发 `entity_main_set`,只更新实体存储,最终仍创建主角。
- 重复主角事件只产生一个模型;缺 race 时不使用错误职业创建人物。
- 加载失败有可见原因;条件恢复后可受控重试;旧加载不会覆盖新角色。
- A1/C1 各一个合法出生点;基点为零的合成坐标用例;地图外坐标被明确诊断。
- 切角色、重连、切地图后相机跟随当前人物,旧节点与旧状态不残留。
测试落点:扩展 `project/gamescene_test.gd``project/app_flow_lifecycle_test.gd`;新增真实资源的 GameScene 视觉用例。FakeClient 的字段/信号需匹配实际接口。
完成条件:自动回归通过;真实 GameScene 截图同时出现正确地表与真实主角。协议若中断,保留本地通过结果,但将实际联机验收标为未通过。
## 6. R2:修复身体、发型、武器与贴地
涉及现有文件:`extension/src/metin2_model.cpp``extension/src/metin2_anim.cpp``extension/src/gr2_bridge.cpp``project/ui/player_view.gd``project/ui/equip_model.gd``project/ui/race_spec.gd``project/ui/char_select_screen.gd`
### 同资源对照与定位顺序
1. 先选用户出错角色的确切身体/发型/装备;再建立男战士最小对照。GLB 转换必须使用同一组源文件,记录转换器版本与参数。生成文件留在隔离输出目录。
2. 在统一实验台逐步加载:身体静态绑定姿势 → 身体动画 → 加发型 → 加武器 → 换护甲。每一步保存矩阵、包围盒、贴图绑定和截图。
3. 检查模型初始 placement、单位缩放、轴转换、父子节点变换是否重复应用。分别核查三角形绕序和负缩放,不能以全局 `flip_winding` 代替定位。
4. 对身体和发型按骨名核对 mesh binding、骨架索引、inverse bind、当前 world/skin 矩阵。验证发型是否错误重复乘身体变换,是否使用了另一套不兼容的绑定姿势。
5. 检查附件共享的动画时间与过渡状态。身体切动作/换装时,发型不能停留在旧姿势。武器验证手部附件空间,避免在 model-local 与 world-space 之间重复转换。
6. 先用不受光照影响的诊断材质、线框或深度视图判断几何是否错误;几何正确而发型仍盖脸时进入 R3 检查深度和混合。
7. 数值正确后比较 GR2 CPU、GR2 GPU 与 GLB 的同时间画面。对关键骨骼矩阵、选定顶点和完整动画周期采样,保留已有 oracle 精度要求,不只检查 NaN。
### 贴地专项
1. 分开记录实体基准高度、地形采样高度、模型局部原点和脚部接触点。
2. 平地待机时用足底接触点判断高度;行走/攻击存在抬脚,不能用动画每帧 AABB 最低点强行贴地,否则人物会抖动。
3. 测试坡地、台阶或可行走建筑表面,以及地图边界。确认所采样的是人物实际站立表面。
4. 阴影检查与几何检查分开:阴影偏差可造成悬空观感,但不能用加深阴影掩盖高度错误。
5. 建议初始平地接触误差上限为 2 cm;这是待以资源与 40250 校准的项目目标,不是原版已证实精度。腾空动作不使用该判据。
### 路线决策
| 实测结果 | 后续动作 |
| --- | --- |
| GR2 数值正确,仅材质/装配错误 | 修复现有渲染与组合,不迁移动画 |
| GLB 在选定资源上可靠,GR2 缺陷短期难解决 | 为该资源提供可回退的 GLB 适配,保持相同的角色表现接口 |
| GLB 丢 shear 或过渡不一致 | 不将该角色纳入迁移范围;保留失败样本 |
| 静态 GLB/原生资源缓存画面一致且加载更快 | 单独推进静态物件缓存,与角色动画路线解耦 |
GLB 扩大范围前必须覆盖四职业、实际使用的性别/护甲/发型变体,以及待机、走、跑、攻击、受击、死亡和选角动作。缺少资产时明确未覆盖,不以一个战士替代全体结论。
完成条件:出错角色的正面/侧面/背面及关键动作通过;换装、重新选角后仍正确;记录 GR2/GLB 选择依据。清理临时强制绕序、调试打印和 `/tmp` 截图导出时只删除本轮明确归属的诊断代码。
## 7. R3:材质、地形与环境校准
涉及现有文件:`extension/src/m2_material.cpp``extension/src/static_object.cpp``extension/src/terrain_splat.cpp``extension/src/environment_builder.cpp``extension/src/metin2_world.cpp``project/ui/char_shadow.gd`
### 具体步骤
1. 逐 surface 输出源 mesh、tri-group、材质槽、最终纹理和渲染状态。确认 mesh-local 材质索引没有误当成全局索引,贴图回退有明确诊断。
2. 身体、脸等不透明面验证深度写入;发丝按资产语义选择 alpha-test;真正半透明面单独混合。不要把所有头发强制成同一模式,也不要对所有表面关闭背面剔除。
3. 同贴图下比较标准材质和自定义 shader,逐项打开光照、顶点色、透明、雾。检查 DDS 解码、颜色空间、alpha、mipmap 和双重 gamma。
4. 身体、头发、武器分开检查投影与受影。正面、侧面、逆光及脸部重叠处验证深度;根据场景尺寸校准 shadow bias。
5. 地形逐层验证 tile/alpha splat 对应、UV 比例、接缝和法线。固定至少一处道路/草地边界作为回归,防止纹理层顺序交换。
6. 列出地图物件:应有、找到资源、解析成功、实例化、可见、失败原因。资源 CRC 可解析不等于模型已画出,树木占位必须单独计数。
7. 基于原 `.msenv` 对照太阳方向、角色/背景光、环境光、雾距离与天空;逐项记录 Godot 近似规则。EnriqueGF 的固定 filmic/雾参数只作实验参照。
8. 完成角色与场景单项校准后再组合全景,避免同时改曝光、贴图与相机导致无法归因。
完成条件:脸/身体不误透明,发丝边缘和遮挡正确;道路与草地层正确;主要建筑不缺材质;角色高度与阴影分别通过;每项与 40250 的剩余差异有说明。
## 8. R4:选角界面与技能栏
涉及现有文件:`project/ui/char_select_screen.gd``project/ui/ui_assets.gd``project/ui/quickbar.gd``project/game_scene.gd`
### 具体步骤
1. 对照当前中文 40250 的 `uiscript``.sub`、字体及图集,记录选角背景、角色相机、按钮、任务栏、快捷槽的来源与尺寸。
2. 检查 `.sub` 裁剪边界、大小写路径、透明通道与纹理加载失败;输出 UI 依赖清单。
3. 从 GameScene 开始检查快捷栏是否创建、挂在正确 CanvasLayer、可见、位于窗口内;核对缩放、锚点、裁剪和其他窗口遮挡。
4. 跟踪 `quickslots_changed``restore_from_server` 的调用时序:初始槽位、晚到数据、重新登录均应恢复。保留现有服务端 36 槽与本地 4 页 × 8 格映射。
5. 将空槽、未学习技能、图标缺失、技能不可用、冷却中区分呈现。不得通过凭空填充技能图标让截图看似正常。
6. 使用确实已学习并已配置的技能验证图标、拖放、翻页、数字键/F 键和冷却;UI 对比先用 FakeClient,真实施放在适用测试环境下执行。
7. 验证背包/聊天打开关闭后输入焦点,避免空格攻击误触 UI 按钮。
8. 按 1280×720、1920×1080 和实际 Retina 窗口验证逻辑尺寸/物理像素关系;中文字体宽度单独检查。
完成条件:选角画面达到已记录的参考布局;底部栏始终可见,槽位状态真实;切角色/重连能恢复;技能冷却和输入行为保持正确。
## 9. R5:最终 macOS 包与验收证据
涉及现有文件:`build-macos-client.sh``script/live_smoke_test.sh``project/live_smoke_test.gd``project/asset_pack.gd``project/bake_asset_index.gd`
### 具体步骤
1. 扩展构建清单,记录代码版本及脏补丁摘要、构建参数、扩展/模板版本、资源及实际产物哈希。
2. 对实际 `.app` 检查身体、发型、装备、动作、地图、UI 图标依赖是否存在且可加载。清单生成后仍要启动包验证,不能用工作区资源替代包内检查。
3. 从不依赖开发者资源路径的目录启动最终包;验证资源解析不会偷偷回退到源码 checkout。用受控资源根测试覆盖此情况。
4. 执行只读登录冒烟,再运行有画面和输入的 GameScene 验收。现有脚本不是完整视觉测试;需新增或扩展包内测试场景并保存截图。
5. 移动、攻击、换装等会改变服务端状态的用例使用适用测试角色/服务器;不要把现有冒烟默认包含的聊天发送当作渲染验收必需动作。
6. 两客户端用例验证角色互见、移动朝向、武器/护甲变化、断开移除及重连重建;同时检查本地与远端外观。
7. 排除凭据、私有日志、源码资产归档和临时探针出现在普通交付包中。若通过带探针的包定位问题,移除探针后还要对最终普通包重新冒烟。
8. 将通过记录绑定到交付 `.app` 的确切构建。此后任何影响画面的代码、资源、导出设置变化,都使相关验收失效,需要重测受影响范围。
### 现有命令与覆盖边界
以下命令在仓库根目录运行。执行前检查本机 Godot、扩展和资源配置;这些是已存在的入口,本文没有执行它们或宣称通过。
```bash
godot --headless --path project --script gamescene_test.gd
godot --headless --path project --script equip_model_test.gd
godot --headless --path project --script environment_test.gd
godot --path project --script char_select_visual_test.gd
./build-macos-client.sh release
./script/live_smoke_test.sh --no-build --no-mutations --no-reconnect
```
- Headless 测试仅证明其断言覆盖的逻辑,不能证明实际 GPU 画面正确。
- 当前选角视觉脚本有实验性诊断,使用前复核其输出和退出行为;它不是完整 40250 比较器。
- `--no-mutations` 不覆盖移动、聊天、重连、换装和攻击;此处同时给出 `--no-reconnect` 以明确首轮只读范围。
- 当前脚本包含特定服务器地址与端口,运行前检查是否仍是目标环境;凭据只通过既有本地配置/环境提供,不写进报告。
- 现有产物路径为 `build/export/mtgodot-poc.app`;日志在 `build/live-smoke/`。保留每次结果时复制到独立 run-id 归档,避免脚本固定路径覆盖历史。
### 第一版交付检查表
- [ ] 首次进入、切角色、重连后出现真实主角,地图和相机正确。
- [ ] 用户出错角色的身体/发型/装备在正面及动画中正常。
- [ ] 平地和选定坡地测试通过,脚底与阴影分别正确。
- [ ] 底部任务栏可见,已配置技能的图标、输入、恢复、冷却正确。
- [ ] 材质与地图关键区域通过固定场景比较,差异图有人审阅。
- [ ] 最终普通 `.app` 独立启动,关键资源无开发目录回退。
- [ ] 协议错误未导致用例提前终止;未完成的联机项明确标注。
- [ ] 日志、截图、报告和包哈希对应同一次构建。
上述为本次内部 macOS 修复版交付标准,不代表全游戏、所有地图或所有平台均已验收。
## 10. R6:性能、缓存与内容扩展
在 R1–R5 通过后执行,不以优化替代正确性修复。
1. 固定场景采集 1/8/20/50 个角色的 CPU/GPU 帧时间、p95/p99、内存、冷加载与重复加载耗时,明确前台/后台和帧率限制。
2. 量化当前 CPU 蒙皮逐帧构建网格的成本;GPU 路径先通过数值与画面回归,再扩大启用范围。
3. 选择一组代表性静态建筑试缓存,缓存键至少包括源资源、转换器版本、坐标和材质选项;改变任一输入必须失效重建。
4. 测量缓存对冷加载、包体和内存的影响,存在收益再扩大导入规模。保留原始资源来源和可重建流程。
5. 树木、效果、LOD 和其他地图分别建立缺失清单与回归场景。占位替代物不能计入原版模型已完成数量。
6. 最后扩展 Android/iOS 独立验证;macOS 的画面和性能结果不能直接外推。
## 11. 任务拆分与执行记录
每项宜形成一个可独立复查的补丁;无需先完成所有工具才修复已定位的问题。已有用户改动应在相邻上下文中保留,不因路线实施而重置。
| 任务 ID | 交付物 | 前置条件 | 当前状态 |
| --- | --- | --- | --- |
| R0-01 | 固定资源/场景基线与区域截图配置 | 可用参考资源 | 已有 A1/C1 实景入口和部分哈希;区域差异待补 |
| R1-01 | 主角晚到、重复事件、重连回归与修复 | R0 最小基线 | 晚到/重入/重试回归通过;真实重连待验 |
| R1-02 | 地图坐标、高度、相机就绪检查 | R1-01 | A1/C1 晚到定位实景通过;坡地/边界待验 |
| R2-01 | 同战士 GR2/GLB 实验台及对照报告 | R0 | 待执行 |
| R2-02 | 发型/身体/附件修复及路线决定 | R2-01 | 待执行 |
| R3-01 | 深度/材质/环境与地形校准 | R1、R2 几何稳定 | 材质已有改动,待验证 |
| R4-01 | 选角、技能栏装配与数据恢复 | R1 | 待执行 |
| R5-01 | 包资源清单、最终 `.app` 冒烟与画面报告 | R1–R4 | 待执行 |
| R6-01 | 性能基线与静态资源缓存试验 | R5 | 后续 |
每项完成记录必须包含:问题复现、确认根因、修改文件、源资源哈希、实际执行命令、断言/截图结果、包或库哈希、未覆盖范围。失败时记录下一步所需证据;不要把“方案合理”“截图已生成”更新成通过。
与现有文档的关系:本路线聚焦用户当前渲染故障和首个 macOS 修复包;[SHINSOO-WORLD-RENDERING.md](SHINSOO-WORLD-RENDERING.md) 提供世界渲染背景,[PARITY-GAP.md](PARITY-GAP.md) 和 [BACKLOG.md](BACKLOG.md) 保留更广范围。后续只在实测完成时同步这些文档的对应状态。
### 2026-09-08 第一批实施记录
- 修改 `game_scene.gd`:setup 完成相关接线后才开始兜底;加载期间阻止网络事件重入;按 VID/race/资源根区分模型;失败间隔 1 秒最多尝试 3 次;丢弃构建期间失效的主角结果;避免重复动画订阅和每帧重置人物位置。
- 发现地图路径原来只在 setup 时按已收到的主角位置选择。新增首次有效主角位置到达后的地图重选,复用已有地图重载和相机/移动同步基点定位函数。
- 修改 `ui/player_view.gd`:拒绝非法 race、缺失身体文件及解析失败的源文件。模型节点在入树时才实际加载,因此使用源文件预检,不能把入树前为空的 `get_info()` 当成加载失败。
- 新增 `main_character_sync_test.gd`:测试失败重试、晚到/无信号实体、重入、重复事件、移动不回滚、过期构建等,已执行得到 `failures=0`。注入失败用例的 warning 属预期。
- `gamescene_test.gd``equip_model_test.gd` 已通过;前者故意使用缺失地图,其预期加载错误不能当成真实地图测试通过。
- 新增 `gamescene_visual_test.gd`Apple M4 / Metal Forward+ / Godot 4.7.1 上已执行 A1/C1A1 加载 20 区块/601 物件,C1 加载 20 区块/565 物件,身体加发型共 4 个表面,实体定位断言通过。两者均使用 FakeClient,未连接实际服务器。
- 截图人工观察:地图和真实人物出现;UI 仍有布局/风格问题,不能标为 R3/R4 完成。选角截图仍复现发型遮脸;单独翻转 winding 的试验未解决,未保留新的绕序修改。
- 用例命令、输出位置和验证边界见 [test/rendering/README.md](../test/rendering/README.md)。早期失败截图保留在 `build/rendering/`;后续脚本变化应重新执行,不复用旧报告作为新代码证据。
- 尚未完成:GLB 对照、发型根因修复、完整视觉基线与区域阈值、真实服务器重连、最终 macOS 包验收、R6 优化。下一优先项是发型的身体/附件变换与深度隔离。