Files
mtgodot-poc/audit/contracts/quest.quest-event-navigation.md
T
shenleiandClaude Opus 5 a989b8f19b chore: delete 106 unreachable *_system.gd and their parity tests
These files were loaded only by their own test_*_parity.gd and never by
the runtime (transitively checked, incl. client_phase_integration_system).
Removes 76 manifest implementation/evidence references across 18
contracts; contract docs get a note that conclusions based on them are
void. Live systems kept: chest, consumable, item_attr, metin_socket,
whisper_chat.

Full headless suite: no new failures (7 failing + 5 headless-hanging
render tests are identical before the deletion).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 17:51:52 +09:00

230 lines
24 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.
# quest.quest_event_navigation
## Scope
审计 `GC_QUEST_INFO` 任务日志、任务计时/计数、任务信件按钮、`GC_SCRIPT` NPC 对话与 `CPythonEventManager` 事件标签、确认/输入/选择、任务地图信号、Atlas/小地图导航、剧情镜幕、相机/淡入淡出和副本结果提示。判断标准是当前端是否与 40250 的协议字段、任务生命周期、事件处理顺序、脚本按钮语义和地图/过场副作用统一;本地生物学家、悬赏、活动调度等脚本 helper 的独立测试不等于 40250 客户端 parity。
## Reference call chain
- `UserInterface/PythonNetworkStreamPhaseGame.cpp`
- `RecvQuestInfoPacket` 先按 `QUEST_SEND_IS_BEGIN` 判定 BEGIN/UPDATE/END,再按 flag 读取 title、clock name/value、counter name/value、icon;BEGIN 注册新任务并记录开始时间,UPDATE 只更新带 flag 的字段,END 删除指定任务,最后刷新任务 UI。
- `RecvScriptPacket` 读取动态 `GC_SCRIPT`,交给 `CPythonEventManager::RegisterEventSetFromString`,然后以 server skin 打开 QuestDialog;`RecvQuestConfirmPacket` 将 message、timeout、request PID 交给 UI。
- `SendScriptAnswerPacket` 使用 8-bit answer;`SendScriptButtonPacket` 使用 unsigned 32-bit index,任务日志点击通过 `-2147483648 + questIndex` 形成 unsigned wire value。
- `UserInterface/PythonQuest.cpp`
- `CPythonQuest::MakeQuest/RegisterQuestInstance/SetQuestClockValue` 都会更新 `iStartTime`;`GetQuestLastTime` 用 `iStartTime + iClockValue - currentSecond`,因此任务窗口关闭时计时仍继续。
- `CPythonQuest` 保持任务实例容器的顺序,`GetQuestIndex` 将 UI 行号映射到真实 quest index。
- `UserInterface/PythonEventManager.cpp` / `PythonEventManager.h`
- `ProcessEventSet` 逐命令处理 42 类标签:LETTER/DELAY/ENTER/WAIT/NEXT/DONE/QUESTION、图片、QUESTBUTTON、地图信号、相机、FADE/WHITE、ITEM/MOB、WINDOW_SIZE、INPUT、CONFIRM_WAIT、SELECT_ITEM 和 DUNGEON_RESULT。
- `NEXT`/取消发送 answer 254;`DONE` 只执行 `DoneEvent/CloseSelf`,不发送 answer。`RUN_CINEMA` 只在首命令时替换为 `.msc` 事件文件;FADE/WHITE 设置 wait flag,动画结束后由 `EndEventProcess` 解锁。
- `Client/Eternexus/root/uiquest.py` / `interfacemodule.py` / `uicharacter.py`
- QuestDialog 的 skin、按钮和 ESC 语义由 UI 层执行;任务信件点击发送 unsigned script button 后删除信件按钮;任务日志最多显示 5 条并使用原始任务顺序。
- `UserInterface/PythonMiniMap.cpp` / `Client/Eternexus/root/uiminimap.py` / `atlasinfo.txt`
- `ADDMAPSIGNAL` 写入 CPythonMiniMap 信号点并打开 Atlas;`CLEARMAPSIGNAL` 清除信号点;`SETCMAPPOS` 改变 Atlas 中心,不改变玩家世界坐标。
## Current call chain
- native `EntityStore::apply` 已解析 `GC_SCRIPT`、`GC_QUEST_CONFIRM` 和 flag-driven `GC_QUEST_INFO`,`M2Client` 暴露 `script_answer/script_button/script_select_item/quest_input/quest_confirm/quest_cancel/get_quests`,`M2Client::poll` 通过 signal 将脚本、确认和任务变更交给 Godot。
- `project/ui/quest_dialog.gd` 已实现 42 个事件标签的解析/状态机、图片、选择分页、输入、确认等待、地图信号、相机、淡入淡出、电影镜幕和副本结果信号;`project/game_scene.gd` 已连接对话、相机、淡入淡出、选择物品、小地图和 Atlas。
- `project/ui/quest_log.gd` 与 `project/ui/char_status_ui.gd` 提供任务按钮和 5 条分页列表;`project/ui/atlas_ui.gd` 提供地图拼接、拖动、玩家/队伍/公会领地/观察者标记;`project/atlas_navigation_system.gd` 是另一个带硬编码地图/NPC/距离规则的本地 helper。
- `project/biologist_quest_system.gd`、`dynamic_bounty_event_system.gd`、`event_manager_system.gd`、`metin_spawn_event_system.gd` 是本地游戏规则/活动 helper,40250 Windows 客户端没有对应的客户端 authority 实现;它们不能替代任务/事件回包链。
## Branch matrix
| 场景 | 40250 | 当前端 | 结论 |
|---|---|---|---|
| 任务 BEGIN/UPDATE | BEGIN 注册全新实例并记录开始时间;UPDATE 只更新带 flag 字段 | `EntityStore` 的 BEGIN/未知 UPDATE 会记录 `clock_start_ms`,并追加到 `m_quest_order`;普通 UPDATE 保持位置和起点,`clock_value` 更新会重置起点 | PARTIAL:生命周期与本地计时已对齐,CTimer/服务端时间基准仍需端到端验证 |
| 任务 END | `DeleteQuestInstance(index)` 后刷新 UI | `EntityStore::apply` 与 classic parser 都删除 `m_quests[index]`、移除 `m_quest_order` 项并排入一次 `quest_changes` 刷新 | PARTIAL:删除和顺序分支已对齐,BEGIN 时间戳和 UI 重建仍未闭合 |
| 任务倒计时 | `iStartTime` 从 BEGIN 或 clock update 时记录,窗口关闭也继续倒计时 | native 保存 `clock_start_ms`;QuestLog 和 `CharStatusUI` 每次显示按起点重新计算,关闭窗口期间不重置 | PARTIAL:关闭/重开语义已对齐,精确 CTimer 时间基准、过期任务清理和后台恢复仍未端到端验证 |
| 任务列表顺序/索引 | vector 顺序与 `GetQuestIndex(row)` 一致 | `EntityStore::m_quest_order` 按 CPythonQuest vector 语义枚举;UPDATE 保持位置、BEGIN 追加、END 删除 | PARTIAL:native 顺序已对齐,真实 UI 行号/服务器序列仍未端到端验证 |
| 任务日志点击 | `-2147483648 + questIndex` 按 unsigned 32-bit 发 `CG_SCRIPT_BUTTON` | `QuestLog`/`CharStatusUI` 传入负数 sentinel,`M2Client` 按 40250 的范围转换为 `uint32` | PARTIAL:wire sentinel 已对齐,真实 server 回包和任务窗口生命周期仍未测 |
| 任务信件按钮 | 新信件同 index 先删除旧按钮、插入最前;点击发送 script button 并删除按钮 | `QuestLog.recv_quest` 顺序/点击删除逻辑已存在 | PARTIAL:UI 结构接近,但实际任务日志负索引门控仍会阻断另一入口,重连/服务器重新发送未测 |
| GC_SCRIPT 事件标签 | 42 标签按逐命令、等待、锁定和事件回调处理 | parser/state machine 基本覆盖 42 标签;首命令 `RUN_CINEMA` 会读取 `.msc` 并替换事件流,缺失资源不打开空窗口 | PARTIAL:真实 `.msc` 资源序列和中断清理仍未验证 |
| 对话按钮语义 | QUESTION 发送 0-based answer;NEXT/取消发送 254;DONE 只关闭;ESC 根据按钮类型分支 | QUESTION/NEXT/取消、DONE 的 `DoneEvent/CloseSelf` 和 ESC 的 QUESTION/NEXT/CANCEL 分支已对齐 | PARTIAL:真实 server 脚本结束/重复提交/中断时序仍未完全覆盖 |
| 输入/选择/确认 | INPUT→`CG_QUEST_INPUT_STRING`;SELECT_ITEM 打开选择窗;CONFIRM_WAIT 显示倒计时并超时取消;END_CONFIRM_WAIT 关闭 | native/UI 信号和主要控件存在,选择/输入/确认测试通过 | PARTIAL:真实超时、重复提交、断线和 server close 清理未覆盖 |
| 地图信号/Atlas | ADDMAPSIGNAL 同时写小地图 signal point/Atlas waypoint 并打开 Atlas;CLEAR 清除;SETCMAPPOS 调中心 | GameScene 将同一坐标写入 Minimap 与 AtlasUI,Atlas overlay 绘制橙色 waypoint 并支持清除 | PARTIAL:两处消费链已对齐,真实 Atlas 资源、地图切换和多点生命周期仍未端到端验证 |
| 相机/淡入淡出/镜幕 | 相机设置、blend、restore 和 fade wait 由应用/事件窗完成;动画完成后释放 wait | GameScene 已连接 camera/fade,QuestCurtain 可开关 | PARTIAL:没有真实 `.msc`/资源和中断清理回归,skin/Done/ESC 边界与原版不一致 |
| 自定义任务/活动 helper | 40250 客户端只负责显示/请求,规则通常由服务端给回包 | 当前 helper 本地推进生物学家、悬赏、活动掉落和倍率 | GAP(custom authority):本地测试通过不能说明与 40250 Windows 客户端统一 |
## Implementation-equivalence matrix
| 项目 | 状态 | 证据/限制 |
|---|---|---|
| Preconditions | PARTIAL | native 阶段、answer/input 和 script-button sentinel 边界存在;事件窗口/重复提交/服务器任务存在性缺少门控 |
| Branch structure | PARTIAL | 42 标签和 UI 主分支已覆盖;RUN_CINEMA 的缺失资源分支、Atlas 任务信号和部分中断分支已补齐/接通,真实过场消费者仍未验证 |
| Algorithms/formulas | PARTIAL | 事件等待、颜色、相机参数和地图坐标转换大体存在;任务计时缺 reference 的 start-time 算法,硬编码 Atlas/NPC/活动 helper 不是 40250 客户端算法 |
| State transition order | PARTIAL | 参考的 GC 任务包→创建/更新/删除→刷新主顺序已补齐;QuestLog 只在可见时递减,脚本 close/answer 仍有额外本地动作 |
| Constants/units | PARTIAL | 5 条任务、30/16/24 字段、42 标签、5×9/按钮布局、254/255 answer 和 uint32 script-button sentinel 已对齐;skin 和地图信号显示语义未闭合 |
| Timing/event sources | PARTIAL | native signal、毫秒事件等待和 fade callback 存在;任务 start timestamp、真实 `.msc` 事件、超时/断线/重连时序缺证据 |
| Resource/data sources | PARTIAL | quest icon、ITEM/MOB 名称和 minimap DDS 使用资源接口;Atlas/NPC helper 另有硬编码 registry,RUN_CINEMA 仍缺真实 `.msc` 资源与消费者证据 |
| Protocol side effects | PARTIAL | GC_SCRIPT/QUEST_INFO/CONFIRM parser 和基本 CG quest packets 存在;任务按钮 wire、DONE close-only、ESC 的 answer 分支已补,电影文件注册未统一 |
| Interruption/failure/cleanup | GAP | 任务结束删除已覆盖 native 两条解析路径,但地图切换、对话 ESC/DONE、确认超时、fade/cinema 中断、信号清理和任务列表重建没有完整 server-sequence 回归 |
## Regression evidence
- `project/quest_test.gd`、`project/quest_event_test.gd`:通过,覆盖脚本对话、确认、任务按钮和 42 标签/状态字段;使用 fake client/离线 parser,没有真实 `GC_QUEST_INFO` END、unsigned button 和 server sequence。
- `project/test_quest_dialog_parity.gd`:修正 DONE/ESC/CANCEL 断言后 47 项通过;该测试现在确认 `[DONE]` 处理时发 `done_event`、按钮点击只关闭,QUESTION 的 ESC 发送最后选项,CONFIRM_WAIT 的取消发送 254。
- `project/test_quest_hud_parity.gd`:19/19 通过;任务按钮布局和 fake quest array 通过,未覆盖 native unordered map、计时持续性和 `M2Client::script_button` 负数拒绝。
- `project/quest_curtain_test.gd`、`project/atlas_test.gd`、`project/test_atlas_navigation_parity.gd`、`project/test_map_name_shower_parity.gd`、`project/test_minimap_radar_parity.gd`:通过;主要验证本地窗口、硬编码导航 registry、地图标记和假数据,未证明 `ADDMAPSIGNAL` 在 Atlas 的实际资源/生命周期等价。
- `project/test_biologist_quest_parity.gd`:28/28 通过;验证本地生物学家任务 helper,不是 40250 Windows 客户端功能实现。
- `project/test_event_manager_parity.gd`:24/24 通过;验证本地活动倍率/宝盒 helper,40250 客户端没有对应 authority,不能作为 parity PASS。
- `build-debug/extension/net_entity_test`、`build-debug/extension/net_classic_session_test`、`build-debug/extension/net_bounds_test`、`build-debug/extension/net_classic_wire_test`:通过基础 quest/script packet、quest END 删除、vector 顺序/UPDATE 活跃状态和负数 sentinel 检查;任务计时、DONE 语义、RUN_CINEMA 消费者和 Atlas 信号仍未覆盖。
## Deep audit round 2026-09-21
### Fresh regression results
- `godot --headless --path project --script quest_test.gd`、`quest_event_test.gd`:均退出码 0;脚本解析、确认、任务日志和 42 标签本地状态机通过。
- `godot --headless --path project --script test_quest_dialog_parity.gd`:退出码 0,45/45 通过;但 DONE 断言明确要求发送 `script_answer(254)`,与 40250 DONE 只关闭事件的实现相反,因此该条不能作为正确 parity 证据。
- `godot --headless --path project --script test_quest_hud_parity.gd`:退出码 0,19/19 通过;fake quest array 的按钮、分页、计数和时间格式通过,但使用负数 script button 值的 fake client,未经过 native wire gate。
- `godot --headless --path project --script quest_curtain_test.gd`、`atlas_test.gd`、`test_atlas_navigation_parity.gd`、`test_map_name_shower_parity.gd`、`test_minimap_radar_parity.gd`:均退出码 0;窗口、Atlas、地图标记和小地图 helper 通过。
- `godot --headless --path project --script test_biologist_quest_parity.gd`:退出码 0,28/28 通过;只验证本地生物学家任务 helper。
- `godot --headless --path project --script test_event_manager_parity.gd`:退出码 0,24/24 通过;只验证本地活动倍率/宝盒 helper。
- `build/extension/net_entity_test`、`net_classic_session_test`、`net_classic_wire_test`:均退出码 0;基础任务/脚本包通过。
### Static parity findings
- 40250 `GC_QUEST_INFO` BEGIN/UPDATE/END 的 END 会真正删除 quest instance;当前 `EntityStore` 对 END 未删除对应记录,旧任务可能继续出现在 `get_quests` 和日志中。
- 40250 在 BEGIN/clock update 保存 `iStartTime`,窗口关闭时计时仍继续;当前端已在 `QuestInfo::clock_start_ms` 保存起点,并让 QuestLog/CharStatusUI 按起点计算剩余值,仍需验证 Godot 单调时钟与参考 `CTimer::GetCurrentSecond()` 的时间基准和后台行为。
- 40250 任务列表保持 vector 顺序并通过真实 quest index 映射;当前端已加入 `m_quest_order` 并覆盖 BEGIN/UPDATE/END 的位置语义,仍缺真实任务日志回包到 UI 行号的端到端验证。
- 40250 任务日志按钮使用 `-2147483648 + questIndex` 的 unsigned 32-bit wire 值;当前 `M2Client::script_button(int)` 看到负数直接拒绝,虽然 fake UI 测试把负数记录为成功。
- `QuestDialog` 的 DONE、QUESTION/NEXT/CANCEL ESC 和确认取消已按 40250 分支对齐;真实 server 断线、重复输入和事件窗口中断仍缺端到端证据。
- `RUN_CINEMA` 的首命令替换和 `.msc` loose-resource 读取已在 `QuestDialog.begin_script()`;缺失/空文件现在按 40250 注册失败语义不打开空对话框,仍缺真实 `.msc` 资源序列/中断验证。ADDMAPSIGNAL 现在同时进入 Minimap 与 AtlasUI,仍缺真实地图资源和切图生命周期验证。
- 本地 `biologist_quest_system`、活动倍率/宝盒 helper 是服务端规则的复制状态机,不属于 40250 Windows 客户端 authority,不能因 28/28、24/24 通过而标记 parity。
### Round conclusion
本轮仍为 `PARTIAL`:任务/对话/Atlas 基础骨架和多数局部测试可运行,但 END 删除、持续计时、任务按钮 wire、DONE/ESC、电影播放消费者和 Atlas 信号消费存在明确差异,且现有 DONE 测试断言本身与 40250 不一致。本轮未修改实现代码。
## Active UserInterface quest-event bridge review
- `PythonEventManagerMoudle.cpp` 暴露从文件/字符串注册 event set、清理、限制数量、逐行位置/宽度、等待/跳过/结束、handler/answer、可见起始行和 `SendScriptButtonPacket`;位置更新使用事件管理器的坐标变换,不能只用一个对话框文本数组替代。
- `PythonGameEventManagerModule.cpp` 将中心位置注入 `CGameEventManager` 后调用 `Update`,是过场/地图事件的逐帧桥接;`PythonQuest.h` 维护 quest instance 的标题、clock/counter 名和值、图标、创建/删除和索引查询。
- 当前端有脚本解析和 QuestDialog/Atlas 骨架,但 event-set 生命周期、handler 回调、持续计时/逐帧位置、任务实例删除以及日志按钮 wire 值尚未统一;RUN_CINEMA 和地图信号也没有发现完整消费者,合同保持 `PARTIAL`。
## Static review evidence
已将 `PythonEventManagerMoudle.cpp`、`PythonGameEventManagerModule.cpp`、`PythonQuest.h` 纳入 active source 证据;本轮只确认 reference bridge 的完整入口和状态字段,没有把局部脚本测试当作行为等价。
## Status
`PARTIAL`。当前端已经有较完整的任务脚本解析、对话控件、任务 HUD、地图和过场骨架,42 个事件标签与基础 native packet 可工作;但任务生命周期/计时/顺序、ESC 语义、电影事件消费和 Atlas 任务信号存在明确差异,另有多个本地规则 helper 不是 40250 客户端实现。DONE、任务 END 和 script-button wire 已有对应修复。
## Implementation fix round 2026-09-21 — quest END removes the instance
按 40250 `CPythonNetworkStream::RecvQuestInfoPacket()` 的明确分支复刻任务结束语义:
`QUEST_SEND_IS_BEGIN` 旗标存在且后续 `isBegin == 0` 时,参考端将包分类为
`QUEST_PACKET_TYPE_END`,调用 `CPythonQuest::DeleteQuestInstance(index)`,随后刷新任务
列表。当前端此前只把 `begin` 改成 false,`get_quests()` 仍会枚举原有记录,造成已结束
任务继续显示。
现已在 `EntityStore::mut_quest_info`(classic parser)和 `EntityStore::apply`(协议兼容
路径)加入同一删除分支,并保留一次 `quest_changes` 刷新事件。新增的 native 回归先在
修复前确认“结束任务仍存在”失败,修复后 `net_entity_test`、`net_classic_session_test`
和 `git diff --check` 均通过。
合同仍为 `PARTIAL`:任务 BEGIN 的 start timestamp、vector 顺序、负数 script-button
sentinel、DONE/ESC、RUN_CINEMA 消费者、Atlas 信号和断线清理仍未闭合。
## Implementation fix round 2026-09-21 — quest-log script-button sentinel
继续按 40250 `uicharacter.py::QuestButtonClick` 与
`CPythonNetworkStream::SendScriptButtonPacket(unsigned int)` 核对,确认任务日志入口的
`-2147483648 + questIndex` 不是非法参数,而是写入 `uint32` wire 字段的任务索引
sentinel。此前 `M2Client::script_button(int)` 以 `idx < 0` 直接拒绝,导致
`QuestLog`/`CharStatusUI` 的任务日志点击无法到达服务器。
现已允许普通非负 `uint32` 索引,以及 `INT32_MIN .. INT32_MIN + 0xffff` 的任务索引
sentinel;其他负数仍拒绝,避免把任意错误值包装成大整数。`net_bounds_test` 已覆盖
普通端点、sentinel 端点和越界负值,构建与运行通过。
本合同仍为 `PARTIAL`:任务 BEGIN 的 start timestamp、vector 顺序、RUN_CINEMA 消费者、
Atlas 信号和断线清理仍未闭合。
## Implementation fix round 2026-09-21 — CANCEL and ESC answer branches
继续按 40250 `uiquest.py::MakeNextButton` / `OnPressEscapeKey` 修复剩余对话关闭分支:
`BUTTON_TYPE_CANCEL` 的取消按钮发送 `SCRIPT_ANSWER(254)`;ESC 在 QUESTION 时发送最后
一个选项索引,在 NEXT/CANCEL 时发送 254,在 DONE 时只关闭;没有可见答复按钮时才
回落到 native 的 255 cancel sentinel。此前当前端统一调用 `quest_cancel()`,会把
CONFIRM_WAIT 和 QUESTION/NEXT 的关闭语义压成错误的 255/取消路径。
新增回归先确认修复前 QUESTION ESC 与 CONFIRM_WAIT 取消断言失败,修复后
`test_quest_dialog_parity.gd` 47 项、`quest_test.gd`、`quest_event_test.gd` 和
`git diff --check` 通过。
本合同仍为 `PARTIAL`:任务时钟与列表顺序、RUN_CINEMA 资源消费者、Atlas 信号、真实
服务端脚本序列和断线/重连清理仍未闭合。
## Implementation fix round 2026-09-21 — DONE event and close-only button
按 40250 `PythonEventManager::ProcessEventSet(EVENT_TYPE_DONE)` 与
`uiquest.py::MakeNextButton(BUTTON_TYPE_DONE)` 修复当前任务对话时序:`[DONE]` token
被消费时立即触发 `DoneEvent`;随后生成的“关闭”按钮只调用 `CloseSelf()`,不发送
`CG_SCRIPT_ANSWER(254)`。此前当前端把 `done_event` 延迟到按钮点击,并把 DONE 错误地
当成 NEXT/取消发送 254。
新增回归先把测试断言改为参考行为并确认修复前失败;修复后
`test_quest_dialog_parity.gd` 45 项、`quest_test.gd` 和 `git diff --check` 通过。
本合同仍为 `PARTIAL`:ESC 尚未按 CANCEL/DONE/NEXT 分支完全复刻,任务时钟、索引顺序、
RUN_CINEMA 消费者、Atlas 信号和断线清理仍未闭合。
## Implementation fix round 2026-09-21 — quest instance vector order
按 40250 `CPythonQuest::RegisterQuestInstance`、`MakeQuest` 和
`DeleteQuestInstance` 的容器语义修复任务列表顺序:参考端使用 vector 保存任务实例,
普通 UPDATE 不改变位置,未知 UPDATE 在末尾创建,BEGIN 先删除同 index 的旧实例再把新
实例追加到末尾,END 同时删除实例和对应顺序项。当前端此前直接枚举
`unordered_map<uint16_t, QuestInfo>`,相同任务回包在不同运行中可能改变任务行顺序。
现已在 `EntityStore` 增加 `m_quest_order`,classic mutator 与协议兼容 `apply` 两条路径
共用相同的注册/删除规则;`quest_indices()` 只按该顺序返回仍存在的任务。修复前新增的
顺序回归按预期失败,修复后 `net_entity_test`、`net_classic_session_test`、
`net_bounds_test` 和 `git diff --check` 均通过。
本合同仍为 `PARTIAL`:精确时间基准、真实任务日志到 UI 行号映射、
RUN_CINEMA/Atlas 生命周期、服务端脚本序列和断线清理仍未闭合。
## Implementation fix round 2026-09-21 — classic UPDATE keeps the quest active
复核发现 classic parser 的 `mut_quest_info` 与协议兼容 `apply` 对同一 UPDATE 的结果不
一致:前者会把已有任务的 `begin` 暂存字段写成 false,后者保持为有效实例。40250 在
`QUEST_PACKET_TYPE_UPDATE` 中只调用 `MakeQuest`(仅当任务不存在)和各字段 setter,
不会把已存在任务变成结束态;只有 `QUEST_PACKET_TYPE_END` 才删除实例。
现已让 classic mutator 对所有非 END 的任务包保持 `QuestInfo.begin == true`,并增加了
“UPDATE 保持活动实例且更新标题”的回归。修复前该断言失败,修复后
`net_entity_test`、`net_classic_session_test` 和 `git diff --check` 通过。
本合同仍为 `PARTIAL`:任务 start timestamp、真实 UI 行号映射、RUN_CINEMA/Atlas 生命周期、
服务端脚本序列和断线清理仍未闭合。
## Implementation fix round 2026-09-21 — ADDMAPSIGNAL reaches Atlas
按 40250 `PythonEventManager::ProcessEventSet(EVENT_TYPE_ADD_MAP_SIGNAL)` 和
`CPythonMiniMap::AddSignalPoint` 核对,任务脚本坐标不只是圆形小地图的边缘提示;参考端
同时调用 `AddWayPoint(TYPE_WAYPOINT, ...)`,因此 Atlas 也能显示同一个 waypoint,
`CLEARMAPSIGNAL` 会移除两处记录。
当前端的 `GameScene` 现在把 `map_signal_added` 同时转发给 `Minimap` 和 `AtlasUI`,
Atlas overlay 使用与小地图相同的 server-centimetre→world→map 坐标转换绘制橙色标记,
并由 `map_signals_cleared` 同时清理。`atlas_test.gd` 新增添加/清理回归,测试通过。
本合同仍为 `PARTIAL`:真实 Atlas 资源、地图切换后的 signal 生命周期、RUN_CINEMA 真实
`.msc` 序列和断线/中断清理仍未闭合。
## Implementation fix round 2026-09-21 — missing RUN_CINEMA resource
按 40250 `CPythonEventManager::RegisterEventSetFromString()` 的首命令特例和
`CPythonNetworkStream::RecvScriptPacket()` 的返回值判断修复过场脚本失败分支:
`RUN_CINEMA` 指向不存在或空路径时,参考端注册返回 `-1`,不会调用
`OnScriptEventStart()`;当前端现在清理临时 event set、保持窗口关闭且不发出 `opened`,
不再显示空白锁定的任务对话框。有效 `.msc` 仍沿当前 loose-resource 读取路径替换事件流。
`test_quest_dialog_parity.gd` 新增缺失资源回归,当前 49 项全部通过;真实 `.msc` 文件的
完整事件序列、过场播放器消费者和中断清理仍保持 `PARTIAL`。
### 2026-09-22 死代码删除
以下文件只被自身测试引用、运行时从不加载,已删除;本文中以它们为实现或证据的结论作废,相关行为需按 40250 源码单元重新移植:`atlas_navigation_system.gd`, `biologist_quest_system.gd`, `dynamic_bounty_event_system.gd`, `event_manager_system.gd`, `metin_spawn_event_system.gd`, `test_atlas_navigation_parity.gd`, `test_biologist_quest_parity.gd`, `test_event_manager_parity.gd`, `test_map_name_shower_parity.gd`。