fix(net): 主角 VID 归参考端所有,GC_CHARACTER_DEL 不再把玩家「去主角化」
实机故障:进入游戏后整屏灰紫,主角还是蓝色胶囊占位体,2D UI 与小地图正常。 根因(已实证,非推测)。`EntityStore::mut_despawn()` 在 despawn 的 vid 等于 主角 vid 时把 `m_main_vid` 清零。40250 服务器在 spawn-in 头几秒会对玩家自己的 vid 反复下发 GC_CHARACTER_DEL + GC_CHARACTER_ADD(视野重算,实测约 10 次)。 第一次 DEL 就把玩家永久去主角化:`get_main_vid()` 返回 0 → `_sync_main_character()` 直接 return → 建模代码从未跑过(main_attempts=0)→ 占位胶囊留在 y=0 → 相机跟着它钻到 C1 地形底下 → 满屏灰紫。地图其实一直是好的 (chunks_built 20/20、objects_placed 565)。 参考端怎么做(40250 ClientVS22)。主角身份属于 `CNetworkActorManager::m_dwMainVID`, 只由 `SetMainActorVID()`(GC_MAIN_CHARACTER)写,只在管理器析构时清; `RemoveActor()` 删 actor 行和角色实例,但绝不碰它;重新加入时 `__AppendCharacterManagerActor()` 用 `kCreateData.m_isMain = __IsMainActorVID(dwVID)` 重新推导,并再跑一次 `SetMainCharacterIndex(dwVID)`。 修法(entity_store.cpp): - `mut_despawn()` 只删行、只发 Despawn,不动 m_main_vid;m2dev 分支的 GC_CHARACTER_DEL 同规则。 - `mut_spawn_full()` 在 `e.vid == m_main_vid` 时把 `is_main` 置回 true, 并重新广播 MainSet,对齐参考端的重新推导。 顺带落地的渲染修复(同一次实机排查中定位): - libgr2 `sample_pose()` 增加 `root_offset` 形参;`Metin2AnimPlayer::apply_pose()` 显式传单位阵而不是模型的 InitialPlacement。Metin2 动画 gr2 的根骨轨道自带骨盆 高度,再乘一次 IP 会把整个人抬高一个骨盆高(武 99.4cm / 刺 92.6 / 巫 103.7 / 萨 95.5,正好各自的 IP.z)——即此前的「人物悬空」。`bind_pose()` 语义不变。 - `Metin2Model::_load_hair()` 改用与身体/武器同一条 `make_material()` (specular_disabled)而不是 roughness=1.0 的 StandardMaterial3D,头发不再被 PBR 高光洗成奶白色块。 取证与回归工具: - `project/net_trace.gd`(新):主角 vid 生命周期取证。挂在 AppFlow 之后逐帧读 `get_main_vid()`,归零时打印 `NETTRACE: MAIN_LOST was=.. now=0 reason=..` 加最近 24 条环形事件;`reason` 区分 despawn 与 silent(reset_for_map_change 清空 m_changes,是静默清零)。`MT_NETTRACE=1` 打开逐事件日志。 - `game_scene.gd`:诊断 tick 支持重复采样(分辨主角装配是「没跑」还是「没跑完」), 新增 main_sync_ready / main_loading / main_view_key / main_map_vid / main_retry_in_ms 字段;`MT_DIAG_SHOT=<png>` 让游戏自己存一帧实机画面 (screencapture 拍到的是终端窗口,没用)。 - `project/model_render_test.gd` + `test/rendering/model_baseline.json`(新): 八个种族的模型渲染基线(--bless 重新落盘)。 - `project/char_bench_test.gd`(新):无网络无地图的单角色实验台,五变体 × 六机位出证据图,只产图不做阈值判定。 - `project/package_render_test.gd`(新):导出包内的渲染自检入口 (导出模板不接受 --script,只能走 MT_TEST_MODE)。 - `/project/build/` 加入 .gitignore(渲染用例的落图输出)。 实机复验(真实服务器,非模拟): - 修前 `LIVE_SMOKE FAIL: 主角色 VID 有效`; `NETTRACE: MAIN_LOST was=25910 now=0 reason=despawn t=+7737ms`, 环里紧跟 `spawn vid=25910 is_main=false`。 - 修后 `LIVE_SMOKE RESULT: PASS`,main_vid=25933、entities=19; 40s 实机跑 16 次诊断采样全部 model_built=True / main_attempts=1 / main_sync_ready=True,player_pos 落在 C1 地形上而非 y=0; MT_DIAG_SHOT 存下的帧里地形、建筑、NPC、树木、真实角色模型、HUD、小地图俱全。 回归:gamescene_test / equip_model_test / gamescene_visual_test(feet_gap 0.001373)/ model_render_test(8 races)/ char_select_visual_test / 包内 MT_TEST_MODE=render 全绿;ctest 22/23,唯一失败 net.classic_session 已用 「还原本次改动 → 重跑 → diff 失败集合」确认与本次修改无关(失败集合完全相同)。 注:画面整体偏暗属于光照/色调差距,受 PARITY-GAP.md §0 门禁约束(对照帧未落盘前 不做相机/光照/色调/材质调参),本次不碰。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJugvEJwz3FK4hw9ti3SRb
This commit is contained in:
@@ -241,8 +241,8 @@ godot --path project --script char_select_visual_test.gd
|
||||
| R0-01 | 固定资源/场景基线与区域截图配置 | 可用参考资源 | 已有 A1/C1 实景入口和部分哈希;区域差异待补 |
|
||||
| R1-01 | 主角晚到、重复事件、重连回归与修复 | R0 最小基线 | 晚到/重入/重试回归通过;真实重连待验 |
|
||||
| R1-02 | 地图坐标、高度、相机就绪检查 | R1-01 | A1/C1 晚到定位实景通过;坡地/边界待验 |
|
||||
| R2-01 | 同战士 GR2/GLB 实验台及对照报告 | R0 | 待执行 |
|
||||
| R2-02 | 发型/身体/附件修复及路线决定 | R2-01 | 待执行 |
|
||||
| R2-01 | 同战士 GR2/GLB 实验台及对照报告 | R0 | 实验台与悬空修复已完成并有数值断言;GLB 对照仍待执行 |
|
||||
| R2-02 | 发型/身体/附件修复及路线决定 | R2-01 | 发型材质根因已修复并有八职业回归;导出包复验待做 |
|
||||
| R3-01 | 深度/材质/环境与地形校准 | R1、R2 几何稳定 | 材质已有改动,待验证 |
|
||||
| R4-01 | 选角、技能栏装配与数据恢复 | R1 | 待执行 |
|
||||
| R5-01 | 包资源清单、最终 `.app` 冒烟与画面报告 | R1–R4 | 待执行 |
|
||||
@@ -263,3 +263,109 @@ godot --path project --script char_select_visual_test.gd
|
||||
- 截图人工观察:地图和真实人物出现;UI 仍有布局/风格问题,不能标为 R3/R4 完成。选角截图仍复现发型遮脸;单独翻转 winding 的试验未解决,未保留新的绕序修改。
|
||||
- 用例命令、输出位置和验证边界见 [test/rendering/README.md](../test/rendering/README.md)。早期失败截图保留在 `build/rendering/`;后续脚本变化应重新执行,不复用旧报告作为新代码证据。
|
||||
- 尚未完成:GLB 对照、发型根因修复、完整视觉基线与区域阈值、真实服务器重连、最终 macOS 包验收、R6 优化。下一优先项是发型的身体/附件变换与深度隔离。
|
||||
|
||||
### 2026-09-08 第二批实施记录(R2-01 悬空 / R2-02 发型)
|
||||
|
||||
#### R2-01 人物整体悬空
|
||||
|
||||
- 复现:`gamescene_visual_test.gd`(A1、C1)与 `char_bench_test.gd` 的 feet 机位,角色脚底离地约一个骨盆高;`tools` 下的 foot_probe 预测偏差 0.146 cm 量级。
|
||||
- 确认根因:渲染桥把动画世界姿势按 Granny `GrannyBuildWorldPose` 的 `Offset4x4` 又乘了一次模型导出期的 `InitialPlacement`,而 Metin2 的动画 `.gr2` 根骨轨道本身已含骨盆高度,等于重复计一次(实测 IP.z:武士 99.443、刺客 92.589、修罗 103.674、萨满 95.473,单位 cm)。
|
||||
- 修改文件:`libgr2/include/gr2/gr2.h`、`libgr2/src/gr2_skeleton.cpp`(`sample_pose` 增加可选 `root_offset`,默认仍取 `sk.initial_placement`,`bind_pose` 行为不变,oracle 数值一致性未放宽);`extension/src/metin2_anim.cpp`(渲染路径显式传单位阵)。
|
||||
- 断言:`gamescene_visual_test.gd` 新增 `feet_gap`(阈值 0.03 m)。A1、C1 实测均为 **0.001373 m**,`passed: true`。
|
||||
- 未覆盖:只验证了 `wait` @0.5 s 与平地。坡地、跳跃/受击等位移较大的动作、以及 NPC/怪物模型未逐一复验。
|
||||
|
||||
#### R2-02 发型发白、看起来「挡脸」
|
||||
|
||||
- 复现:`char_bench_test.gd` 的 `face_side` 近景,额发和鬓角出现成片浅奶油色三角面。
|
||||
- 排除项(都不是原因):几何与挂接(发型权重 100% 在 `Bip01 Head`,骨骼按名重映射 74/74,蒙皮包围盒落在头部 z 149–178,`bind_pose_self_check = 7.6e-05`);贴图(`hair/hair_1_1.dds` 就是 gr2 材质里写的那张,256×256 DXT1,最亮纹素 RGB(90,81,90),**0 个透明纹素**,所以 alpha scissor 是空操作,`dxt.cpp` 无关);UV(全在 [0,1])。
|
||||
- 材质二分(同一机位逐项改一个开关):`TRANSPARENCY_DISABLED` 仍有、`CULL_BACK` 更糟、`metallic_specular = 0` 仍有、**`roughness = 0` 干净**、**`SHADING_MODE_UNSHADED` 干净**。
|
||||
- 确认根因:`Metin2Model::_load_hair()` 是全角色唯一没走 `mtgodot::make_material` 的表面,用的是裸 `StandardMaterial3D` 且 `roughness = 1.0`,浅色斑是 Godot PBR 的高光响应,与身体/武器用的前向材质(shader 带 `specular_disabled`)光照模型不一致。
|
||||
- 修改文件:`extension/src/metin2_model.cpp` 的 `_load_hair()` —— 改用 `mtgodot::MaterialDesc{blend = AlphaTest, alpha_scissor = 0.5, two_sided = true, spec_power = 0}` 走 `make_material`;无贴图时用 `modulate` 保留原来的深棕兜底色。GPU 蒙皮路径本来就已经走 `make_material`,此改动使 CPU 路径与之一致。
|
||||
- 资源哈希(sha256):`PC/ymir work/pc/warrior/warrior_novice.gr2` `7ac881b0c60ced40c9388d6186bdf02a353a58382d12804345cb65fc3dc1fa74`;`PC/ymir work/pc/warrior/hair/hair_1_1.gr2` `2e3c00bda5397d579a2399cfb38a69974667e4f7b92dca6b2a709058a37d0a84`;`PC/ymir work/pc/warrior/general/wait.msa` `7bc14bc29a63a78708b9a76618aee9229286f255e725dd669c60ef838cec6a15`。其余七个职业的身体/发型哈希记在各次 `report.json` 里。
|
||||
- 执行命令与结果:
|
||||
|
||||
```bash
|
||||
./build.sh Debug
|
||||
godot --headless --path project --script main_character_sync_test.gd # failures=0
|
||||
godot --headless --path project --script gamescene_test.gd # PASS
|
||||
godot --headless --path project --script equip_model_test.gd # PASS
|
||||
godot --path project --script char_select_visual_test.gd # PASS
|
||||
godot --path project --script gamescene_visual_test.gd # passed:true, feet_gap 0.001373
|
||||
godot --path project --script gamescene_visual_test.gd -- --c1 # passed:true, feet_gap 0.001373
|
||||
godot --path project --script char_bench_test.gd -- --race {0,1,2,3} # 四职业近景人工复核,浅色斑消失
|
||||
godot --path project --script model_render_test.gd # PASS(八职业)
|
||||
```
|
||||
|
||||
- 未覆盖:只验证了基础 `*_novice` 身体加 `hair_1_1`。5th 护甲等换装组合、`hair_skin` 覆写路径、GPU 蒙皮路径的实机画面未逐一复验;**导出包内复验尚未完成**(见下)。
|
||||
|
||||
#### 新增:不启动游戏的模型渲染自动回归
|
||||
|
||||
- 新增 `project/model_render_test.gd` 与基线 `test/rendering/model_baseline.json`。离线装配单角色(无地图、无网络、无服务器),固定灯光与机位,对像素算指标并断言,退出码即结论。
|
||||
- 指标与阈值、为什么用平均亮度而不是「炸白像素占比」、以及覆盖边界,见 [test/rendering/README.md](../test/rendering/README.md)。
|
||||
- **有效性已验证**:把 `_load_hair()` 临时改回 `StandardMaterial3D(roughness = 1)` 重新编译后,八个职业全部失败(`hair_mean_lum` 偏离基线 0.0109…0.0371,阈值 0.006);改回 `make_material` 后 `PASS`。这条不是「截图已生成」式的通过。
|
||||
- 局限:基线绑定当前引擎版本、灯光和资源。改模型/发型材质/灯光都会使其失效,必须人工看过渲染结果再 `--bless` 并在此记录原因。
|
||||
|
||||
#### 纠正前一批记录的一处误判
|
||||
|
||||
- 第一批记录里「选角截图仍复现发型遮脸」中被一并当作缺陷的「武器平躺在地上」不成立:那是选角页 `ui/char_select_screen.gd:517` 建的 `dragon_left/right_button_0{1,2,3}.sub` 两个 2D 导航按钮(`(714,712)` / `(1024,712)`,160×44),不是武器挂点问题。武器挂接没有发现缺陷。
|
||||
|
||||
#### 仍未完成
|
||||
|
||||
- ~~**导出包复验**:包内没有离线渲染入口(导出模板不接受 `--script`)~~ **已解决(2026-09-08)**:新增
|
||||
`project/package_render_test.gd`,由 `client_main.gd` 在 `MT_TEST_MODE=render` 时挂到主场景下——
|
||||
绕开导出模板不收 `--script` 的限制,走真实 `GameScene` + `Metin2World` + `PlayerView`,不连服务器,
|
||||
报告和截图落到 `user://render-check/`(`MT_RENDER_OUTPUT` 可改)。
|
||||
|
||||
```
|
||||
MT_TEST_MODE=render build/export/mtgodot-poc.app/Contents/MacOS/mtgodot-poc
|
||||
```
|
||||
|
||||
首次在**导出包内**跑的结果(2026-09-08,release 包):`PASS` —— 扩展三个类都注册、资源根解析到
|
||||
`Contents/Resources/assets`、地图 20/20 chunk、`model_built=true`、`surfaces=4`、`feet_gap=0.001373`,
|
||||
截图是正常的草地 + 角色。R2-01/R2-02 的「导出包中复验」这一步由此补齐。
|
||||
未覆盖:网络驱动的进场时序、40250 像素比对、性能。
|
||||
|
||||
- ~~**新发现的实机故障**:进入游戏后 3D 画面整片灰紫、主角仍是蓝色胶囊,2D UI 与小地图正常~~
|
||||
**已修复(2026-09-08)**。
|
||||
|
||||
**根因**:`EntityStore::mut_despawn()` 在 `GC_CHARACTER_DEL` 命中主角 vid 时把 `m_main_vid` 清零了。
|
||||
40250 服务器在进场那一两秒里会对**主角自己的 vid** 连发 `GC_CHARACTER_DEL` + `GC_CHARACTER_ADD`
|
||||
(视野重算,实测 10 次左右),于是主角被永久「取消主角身份」:`get_main_vid()` 返回 0 →
|
||||
`_sync_main_character()` 直接 return → 模型装配代码一次都没跑(`main_attempts=0`),
|
||||
占位胶囊留在 y=0,相机跟着它钻到地形底下 → 满屏灰紫。地图其实一直是好的。
|
||||
|
||||
**参考端怎么做**(`40250/ClientVS22/source/UserInterface/`):主角 vid 归
|
||||
`CNetworkActorManager::m_dwMainVID` 所有,只由 `SetMainActorVID()`(GC_MAIN_CHARACTER)写、
|
||||
只在管理器销毁时清;`RemoveActor()`(NetworkActorManager.cpp:440)删 actor 行和角色实例,
|
||||
**不碰 m_dwMainVID**。重新 add 时 `__AppendCharacterManagerActor()` 用
|
||||
`kCreateData.m_isMain = __IsMainActorVID(dwVID)` 把它重新认成主角,并再跑一次
|
||||
`SetMainCharacterIndex(dwVID)`。
|
||||
|
||||
**修法**(`extension/src/net/entity_store.cpp`,两处对齐参考端):
|
||||
1. `mut_despawn()` 不再清 `m_main_vid`(m2dev 分支的 `GC_CHARACTER_DEL` 同样处理);
|
||||
该字段现在只由 `mut_spawn_main()` 写、只由 `reset_for_map_change()` 清(对应参考端的销毁)。
|
||||
2. `mut_spawn_full()` 里 `vid == m_main_vid` 就把 `is_main` 置回 true,并补一条 `MainSet`
|
||||
变更,对应 `__AppendCharacterManagerActor()` 的 `m_isMain` + `SetMainCharacterIndex`。
|
||||
|
||||
**取证手段**(保留在树里):
|
||||
- `project/net_trace.gd`(由 `client_main.gd` 挂在 AppFlow 之后)每帧比对 `get_main_vid()`,
|
||||
丢主角时打 `NETTRACE: MAIN_LOST ... reason=despawn|silent` 和最近 24 条事件环。
|
||||
`reason` 直接区分「`GC_CHARACTER_DEL` 命中主角」和「`reset_for_map_change()` 静默清零」
|
||||
(后者会清空 `m_changes`,不发 despawn 信号)。`MT_NETTRACE=1` 打全量。
|
||||
- `game_scene.gd` `_diag_tick()`:进游戏若干秒后,`_model_built` 或 `_map_loaded()` 任一为假就打一行
|
||||
`GAMESCENE_DIAG:`(`race` / `pos_cm` / `map_path` / `map_base_set` / `main_attempts` /
|
||||
`main_sync_ready` / `map_report` …)。`MT_DIAG=1` 强制输出并连打 6 次(间隔 2 s),
|
||||
`MT_DIAG_AFTER_MS` 改延时,`MT_DIAG_SHOT=<png>` 在最后一次存一张实机帧。
|
||||
|
||||
**实机复验**(真服 192.168.21.203,classic,2026-09-08):
|
||||
- 修前 `./script/live_smoke_test.sh --no-build --no-mutations --no-reconnect` →
|
||||
`FAIL: 主角色 VID 有效` / `FAIL: 主角色不存在`;`NETTRACE: MAIN_LOST was=25910 now=0 reason=despawn`。
|
||||
- 修后同一命令 → `LIVE_SMOKE RESULT: PASS`,`main_vid=25933 pos_cm=(963444.0, 266221.0, 0.0) entities=19`。
|
||||
- 40 秒实机连打诊断:`model_built=true`、`main_attempts=1`、`player_pos=(418.44, 177.67, 614.21)`
|
||||
(站在 C1 地形上,不再是 y=0),16 次采样全程稳定;`MT_DIAG_SHOT` 存下的帧是正常的
|
||||
地形 + 建筑 + NPC + 主角模型 + HUD + 小地图。
|
||||
|
||||
未覆盖:主角材质在俯视机位下偏暗的观感问题——那属于光照/色调,受 PARITY-GAP §0 门禁约束,
|
||||
不在这次修复范围内。
|
||||
|
||||
- R2-01 的 GLB 对照、R0 完整视觉基线与区域阈值、R4 选角/快捷栏、R5 最终包验收、R6 性能,均未开始。
|
||||
|
||||
Reference in New Issue
Block a user