Files
mtgodot-poc/audit/contracts/social.chat-text-tail.md
T
shenandshen c93894313a fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧
- inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域
- 包含其他累积的功能开发和测试文件
2026-09-21 16:38:59 -07:00

130 lines
18 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.
# social.chat_text_tail
## Scope
审计公聊、队伍/公会/喊话频道、无 VID 系统消息、私聊收发和私聊窗口、忽略名单、聊天气泡、名字/公会/称号/等级尾标以及尾标的寿命、排布和死亡清理。对比重点是当前端是否复现 40250 的消息类型分支、服务器事件来源、私聊状态机、文本颜色/过滤/表情副作用、尾标覆盖规则和渲染布局;本地有同名 helper 或能显示文本不等于实现统一。
## Reference call chain
- `UserInterface/PythonNetworkStreamPhaseGame.cpp`
- `RecvChatPacket` 校验聊天类型;`CHAT_TYPE_COMMAND` 进入 `ServerCommand`,不进入普通聊天和聊天尾标。
- 有 VID 时,普通/队伍/公会/喊话/客户端私聊从 `"名字: "` 后取正文,执行表情解析、跨帝国文字转换和辱骂过滤;角色不存在时直接忽略;除 `CHAT_TYPE_SHOUT` 外注册 `CPythonTextTail::RegisterChatTail`PC 消息再进入 `CPythonChat::AppendChat`
- 无 VID 时,NOTICE/BIG_NOTICE 分别进入 `BINARY_SetTipMessage`/`BINARY_SetBigMessage`,系统和喊话按专门分支处理,不是简单追加到同一聊天列表。
- `RecvWhisperPacket` 将 mode、发送者和文本交给 `OnRecvWhisper`/错误/系统回调;私聊历史、按钮、闪烁和忽略名单由 `CPythonChat``uiwhisper.py``game.py` 联动。
- `UserInterface/PythonNetworkStreamCommand.cpp`
- `ServerCommand` 作为 `CHAT_TYPE_COMMAND` 的二次命令总线,解析 cube、observer、StoneDetect、stamina、mobile、gift、情感动作和 `dig_motion`,并把副作用交给 UI、角色或效果管理器;命令不进入普通聊天尾标。
- `UserInterface/PythonChat.cpp` / `PythonChat.h`
- 普通聊天维护共享聊天行队列和多个 chat set,支持 view/edit/log 三种布局、滚动步长、最大行数、延迟追加、按频道颜色和清理。
- 40250 颜色是 talking 白、info 淡红、notice/big notice 淡黄、party 青白、guild 淡紫、command/shout 青绿、whisper 绿色;私聊 mode 包含 chat、not-exist、target-blocked、sender-blocked、error、GM 和 system。
- `AppendWhisper` 按目标名维护独立会话;`IgnoreCharacter`/`IsIgnoreCharacter` 只对目标名切换黑名单;`InitWhisper`/`MakeWhisperButton` 恢复已有会话。
- `UserInterface/PythonTextTail.cpp` / `InstanceBaseEffect.cpp`
- `RegisterChatTail``RegisterInfoTail` 共用每 VID 一条尾标;聊天尾标为白色、`bNameFlag=true`,信息尾标为淡红、`bNameFlag=false`,新消息覆盖旧消息并重置 `now + 5000ms`
- 聊天气泡会强制显示角色名字,并把名字尾标移动到气泡下方 17px;公会名、称号、等级按实际字体度量排在名字两侧/上下;角色死亡时 `DetachTextTail`
- 公会名只在 guild id 非零时创建,名称查询失败回退 `Noname`;称号/等级受 `EnablePKTitle`、alignment grade、level 和挂马高度影响。
- `Client/Eternexus/root/game.py` / `uiwhisper.py`
- `OnRecvWhisper` 按官方 whisper mode 分流到 `AppendWhisper`、系统错误文本和界面 `RecvWhisper` 通知;聊天输入、好友/目标板和独立私聊窗口都能打开同一目标会话。
- 私聊窗口发送只有网络请求成功后才应关闭/追加本地回显;忽略按钮通过 `/ignore <name>` 与服务端/客户端状态同步。
## Current call chain
- `EntityStore::mut_chat``mut_whisper` 和 parser 已保存 `GC_CHAT`/`GC_WHISPER` 为 chat queue`M2Client` 发出 `chat``whisper_received` 信号,基础 classic 动态包布局和长度限制存在;`GC_WHISPER` 缺少固定 type/name 字段时会拒绝,不再静默吞包。
- `project/ui/chat_ui.gd` 只有四个本地 tab(全部/私聊/系统/战斗),按 Godot `RichTextLabel` 重建整段文本;公共输入支持普通、公会、队伍、喊话和 `/w`,但普通/喊话发送失败没有统一错误回显,私聊窗口也无视 `client.whisper()` 返回值而立即本地回显并关闭。
- 当前入站私聊 `_on_whisper` 已按 40250 区分 `0=CHAT``1=NOT_EXIST``2=TARGET_BLOCKED``3=SENDER_BLOCKED``4=ERROR``5=GM``0xFF=SYSTEM`;1..4 的空文本会生成本地化错误兜底文案。live 会话已接入,颜色/字体和服务端错误正文仍未完整闭环。
- `project/whisper_chat_system.gd` 实现了独立的本地 conversation/最小化/闪烁/忽略 helper`ChatUI.setup` 现在把它接入 `M2Client.whisper_received`;live 私聊仍没有完整的独立窗口历史渲染、任务栏按钮、ignore 命令和发送结果状态。
- `project/chat_tail.gd` 已复现单 VID 覆盖、5000ms 生命周期、聊天/信息颜色和名字跟随规则;`project/net_world.gd``Label3D` 气泡、名字和子标签实现,死亡/过期会清理节点。
- `project/text_tail.gd``text_tail_arrange.gd``name_tail_layout.gd` 已实现部分颜色、称号、等级、公会名、挂马高度和像素近似排布;但运行时仍是 Godot billboard/估算字符宽度,不是 40250 的 `CGraphicText` 实际字体度量和 `CPythonTextTail` 渲染对象。
- `ClassicParser::on_gc(GC_CHAT)` 现按 40250 先拒绝非法 chat type,并在非系统消息的 VID 不存在于 `EntityStore` 时消费但不产生 chat eventlive `project/net_world.gd::_on_chat` 仍没有复现表情解析、跨帝国转换、辱骂过滤和无 VID NOTICE/BIG_NOTICE 专用 UI。
## Branch matrix
| 场景 | 40250 | 当前端 | 结论 |
|---|---|---|---|
| 普通/队伍/公会聊天 | 解析类型、角色和前缀,过滤/表情后写聊天行,必要时注册气泡 | 基础 `GC_CHAT` queue、UI tab 和气泡存在 | PARTIAL:过滤/表情/未知实体/真实显示布局缺失 |
| 喊话 | 公共聊天颜色和公共区显示,不能注册角色聊天尾标 | 当前写入公共 tab;`ChatTail` 也能保留,但 net_world 连接路径未证明按 ref 排除 | PARTIAL |
| 无 VID INFO/NOTICE/BIG_NOTICE | 专用 tip/big notice 分支,按类型显示 | 统一塞入 System/All `RichTextLabel`,没有专用 tip/big notice | GAP |
| COMMAND | 进入 `ServerCommand`,不显示普通聊天/气泡 | native 有部分 command 解析;非已识别 command 仍缺完整 side effect 矩阵 | PARTIAL |
| 表情/帝国文字/辱骂过滤 | 先解析表情或转换/过滤,再显示文本/动画 | 有发送表情 token,但没有入站 `ParseEmoticon`、帝国转换和辱骂过滤链 | GAP |
| 私聊正常收发 | mode/name/text 进入独立目标会话,按钮/历史/通知联动 | packet queue、独立输入和 live conversation 接入存在;窗口历史/按钮/通知仍不完整 | PARTIAL |
| 私聊错误/GM/system mode | 0..5/0xFF 具有不同错误/字体/颜色和通知 | mode 数值与错误/GM/system 分支已对齐;颜色、字体和 live 通知仍不等价 | PARTIAL |
| 忽略目标 | `IgnoreCharacter` 按名称切换,收到私聊不进入会话并有服务端 ignore 命令 | live ChatUI 收到普通/GM 私聊前经过会话 ignore 判断;仍无 UI 忽略按钮/服务端命令连接 | PARTIAL |
| 聊天气泡 | 每 VID 单槽覆盖,白/淡红,5000ms,聊天气泡强显名字并上移名字 17px | 单槽、颜色、生命周期和本地布局 helper 存在 | PARTIAL:实际字体/屏幕投影/已知实体和资源表现不同 |
| 名字/称号/等级/公会名 | 由角色同步、alignment/guild/item/挂马更新,死亡 detach,真实字体度量排布 | 快照+Label3D 子标签和近似字符宽度存在 | PARTIAL:权威更新源和渲染度量不完全同链 |
| 换图/重连/死亡/重复消息 | CPythonChat/TextTail 清理与重新注册,旧会话状态按客户端生命周期处理 | 本地节点过期/死亡清理存在;chat queue、私聊会话和重连恢复未端到端验证 | PARTIAL |
## Implementation-equivalence matrix
| 项目 | 状态 | 证据/限制 |
|---|---|---|
| Preconditions | PARTIAL | native 长度/阶段边界存在;缺入站 actor/type/name、ignore、输入门控和真实 UI 状态前置条件 |
| Branch structure | PARTIAL | 普通/私聊/尾标主干和 live ignore 分支存在,但无 VID notice、表情/过滤和完整私聊通知分支 |
| Algorithms/formulas | PARTIAL | 5000ms 生命周期和 17px 名字偏移有静态镜像;频道颜色、前缀、字符宽度、会话/通知算法存在明确差异 |
| State transition order | GAP | 参考是网络回包→过滤/表情/尾标→聊天/窗口/提示;当前 conversation 已随 live signal 更新,但本地回显/窗口通知和服务端结果顺序仍不同 |
| Constants/units | PARTIAL | EChatType 和尾标 5000ms/17px/颜色部分存在;多个公共/私聊颜色与 40250 不同,字体宽度是 7px 估算 |
| Timing/event sources | PARTIAL | 气泡过期和 UI 信号存在;延迟聊天、真实服务器命令、重连/后台通知和私聊闪烁未接通 |
| Resource/data sources | GAP | 40250 使用角色/公会/本地化/字体资源及服务器消息;当前标题本地化可注入但 chat 文本/颜色/宽度多为脚本常量和 Godot 默认字体 |
| Protocol side effects | PARTIAL | classic CG_CHAT/CG_WHISPER 与 GC queue 有;入站类型副作用、SendSequence/错误文案、ignore 命令和 notice/big notice UI 未完整复现 |
| Interruption/failure/cleanup | GAP | 私聊发送失败仍本地显示并关闭;会话/忽略/聊天行重连清理、未知 VID、重复/乱序回包和专用提示清理缺端到端证据 |
## Regression evidence
- `project/chat_test.gd`:公共/私聊输入路由、拾取/钓鱼消息、气泡和名字/子标签排布通过;使用 fake client,未验证真实 `GC_CHAT` 分支和发送失败。
- `project/chat_tail_test.gd``RegisterChatTail`/`RegisterInfoTail`、单 VID 覆盖和 5000ms 生命周期通过。
- `project/text_tail_test.gd``project/text_tail_arrange_test.gd``project/name_tail_layout_test.gd``project/guild_mark_tail_test.gd`:静态颜色、称号/等级、公会名、排列和 guild mark 通过;未验证真实字体资源和 C++ 尾标对象。
- `project/test_whisper_chat_parity.gd`:26/26 通过,覆盖会话、闪烁、忽略和历史;`project/chat_test.gd` 另验证 live ChatUI 的普通/GM 入站信号进入同一 conversation。
- `project/chat_mobile_ui_test.gd``project/test_chat_hyperlink_dice_parity.gd`:移动聊天窗口、私聊输入和额外链接/dice helper 通过;不能证明 40250 的入站消息分支。
- `build/extension/net_entity_test``build/extension/net_classic_session_test`GC_CHAT/GC_WHISPER 解析和发送包基础通过;session 回归覆盖未知 VID、非法 type、已知 VID 的 chat event 矩阵;`project/chat_test.gd` 另覆盖 whisper mode 1/5/0xFF 的错误、GM、system 显示映射,但仍没有完整 0..5/0xFF live 回调、失败回显和重连测试。
## Deep audit round 2026-09-21
### Fresh regression results
- `godot --headless --path project --script chat_test.gd``chat_tail_test.gd``text_tail_test.gd``text_tail_arrange_test.gd`:均退出码 0;公共/私聊路由、气泡覆盖、5000ms 生命周期、颜色和尾标排布通过,但使用的是脚本/fake client 与近似字体度量。
- `godot --headless --path project --script name_tail_layout_test.gd``guild_mark_tail_test.gd`:均退出码 0;名字/公会标记和尾标布局 helper 通过。
- `godot --headless --path project --script test_whisper_chat_parity.gd`:退出码 0,26/26 通过;覆盖独立的 `WhisperChatSystem` 会话、闪烁、忽略和历史;`chat_test.gd` 已补 live `ChatUI` 入站网络状态验证。
- `godot --headless --path project --script chat_mobile_ui_test.gd``test_chat_hyperlink_dice_parity.gd`:均退出码 0;移动 UI、链接/骰子 helper 通过。
- `build/extension/net_entity_test``build/extension/net_classic_session_test`:均退出码 0GC_CHAT/GC_WHISPER 基础解析和发送包通过,未覆盖完整消息分支矩阵。
### Static parity findings
- 40250 `RecvWhisperPacket` 的 mode 是 `0=CHAT``1=NOT_EXIST``2=TARGET_BLOCKED``3=SENDER_BLOCKED``4=ERROR``5=GM``0xFF=SYSTEM`;当前 `ChatUI::_on_whisper``1` 显示为系统、`2` 显示为 GM,且其余错误/屏蔽分支没有对应 UI 语义。
- 当前 `ChatUI``client.whisper()` 返回前就本地追加回显并关闭私聊窗口;网络发送失败时仍显示成功,违背 40250 的请求结果驱动状态转换。
- `WhisperChatSystem` 已有独立会话/忽略/闪烁实现,live `ChatUI` 现在把普通/GM `M2Client.whisper_received` 接入该状态源;独立窗口渲染、任务栏按钮和服务端 ignore 命令仍未闭环。
- 40250 `RecvChatPacket` 对未知 VID 的普通消息直接忽略,并处理表情、跨帝国转换、辱骂过滤以及无 VID 的 NOTICE/BIG_NOTICE 专用提示;当前 `_on_chat` 对未知 VID 仍能显示原文,NOTICE/BIG_NOTICE 统一进入 RichTextLabel,未接专用提示和过滤链。
- 40250 聊天气泡/尾标依赖 `CGraphicText` 实际字体、角色/公会/称号/等级权威同步并在死亡时 detach;当前 Label3D/脚本近似布局虽有局部测试,但没有真实字体资源、网络更新和重连/死亡/重复消息端到端证据。
### Active command-bus source review
- 本轮逐项核对 `PythonNetworkStreamCommand.cpp` 与当前 `EntityStore::apply_server_command``M2Client::poll` 信号链:当前端已经覆盖 `cube`、observer、StoneDetect、stamina、mobile、gift、combo、block mode、lover、password/refine/private-shop 以及 emotion/dig 的主要事件枚举,`net_entity_test` 对其中一组事件通过。
- 仍存在参考语义差异:40250 未识别/参数数量错误命令会走日志或特定 UI 回调;当前端对命令的统一 token/参数校验、效果资源创建、所有 emotion/observer/gift 回调和命令失败清理没有完整矩阵。`ClientCommand()` 在参考版本本身返回 false,不能把当前端已有 server-command helper 测试解释为完整 GM/命令 parity。
### Round conclusion
本轮仍为 `PARTIAL`:已按 40250 收口 native `GC_CHAT` 的非法类型/未知 VID 丢弃分支,修正 ChatUI 的 whisper mode 映射与空错误文案兜底,并把 live 普通/GM whisper 接入独立会话和 ignore 判断;窗口通知、NOTICE 分支、表情/过滤/跨帝国文字和真实尾标渲染仍未与 40250 统一。
### Active EterLib text-render review
- `EterLib/GrpText.cpp`/`GrpTextInstance.cpp`/`TextBar.cpp`/`TextTag.cpp` 以及 `GrpFontTexture.cpp``GrpMarkInstance.cpp` 共同负责真实字体资源、字宽/行数/字符位置、光标/描边/颜色、标记和池化对象生命周期;这正是 40250 聊天气泡、名字/公会/称号尾标的渲染基础。
- 当前 `Label3D``RichTextLabel` 和脚本宽度估算已覆盖局部可见结果,但没有复用同等字体纹理度量、字符定位、mark 生命周期和资源失败分支。已有布局测试使用 helper/fake client,不能替代真实字体资源与网络角色更新。
- 因此本轮完成上述 EterLib 文本核心静态核对;尾标的单槽/5000ms/17px 行为仍有镜像证据,但渲染资源和清理链不一致,合同继续为 `PARTIAL`
## Active UserInterface chat and text-tail review
- `InsultChecker.cpp/h` 维护可清空/追加的禁词集合,按 locale-aware case-insensitive 比较处理多字节字符;`FilterInsult``*` 替换完整词,`IsInsultIn` 只报告命中,不能用 ASCII `find` 近似替代。
- `PythonChatModule.cpp` 暴露 chat set 的创建、位置/高度/步长/模式、延迟消息、忽略角色、whisper 会话、whisper 清理和 item hyperlink 解析;hyperlink 通过 `CItemManager` 查 item 数据,不是只显示原字符串。
- `PythonTextTailModule.cpp` 的生产顺序是 `UpdateAllTextTail``UpdateShowingTextTail``RegisterCharacterTextTail``RegisterChatTail`/`RegisterInfoTail`、标题/拾取/排列和投影位置查询;`Pick`、隐藏/显示和角色删除清理是同一状态机的一部分。
- 当前端聊天/尾标 helper 已有局部测试,live ChatUI 已将普通/GM whisper 接入独立会话和 ignore 判断;仍未形成 locale 禁词、多字节边界、chat-set 排列、item hyperlink、未知/删除 VID、投影失败和真实字体资源的同一调用链,已有 Godot 宽度估算和本地文本状态不能证明 40250 等价,合同保持 `PARTIAL`
## Implementation fix round 2026-09-22
- `project/ui/chat_ui.gd` 已按 `PythonChat.h`/`game.py` 修正 whisper mode`1..4` 作为错误系统消息,`5` 作为 GM`0xFF` 作为 system;空错误正文使用本地化兜底文案。
- `extension/src/net/classic/classic_parser.cpp` 已按 `RecvWhisperPacket` 的固定头前置条件,将缺少 type/name 的 `GC_WHISPER` body 从静默成功改为失败;`net_classic_session_test` 增加对应直接 parser 回归。
- `project/chat_test.gd``project/test_whisper_chat_parity.gd``project/chat_mobile_ui_test.gd``project/test_chat_hyperlink_dice_parity.gd` 与 native session 回归通过;live conversation/ignore 接入已有证据,完整通知/颜色、忽略命令和公共聊天副作用仍未闭环。
## Static review evidence
已将 `InsultChecker.cpp/h``PythonChatModule.cpp``PythonTextTailModule.cpp` 纳入 active source 证据;这些文件对应的过滤、chat-set、whisper、hyperlink、尾标更新和拾取分支仍记录为实现差异或未验证分支。
## Status
`PARTIAL`。当前端已有聊天协议骨架、公共输入、私聊基础发送、聊天气泡和名字尾标的可测试镜像,但并非与 40250 统一实现:live 私聊已接入独立会话,但窗口历史/任务栏通知/服务端 ignore 命令仍未闭环;公共颜色/notice 分支不一致、入站表情/过滤/跨帝国文本缺失,且尾标渲染仍使用 Godot billboard 与宽度估算。本轮已修正 whisper mode 映射、空错误文案、截断 whisper 拒绝逻辑并接通 live conversation/ignore 判断,其他差异继续保持 PARTIAL。