diff --git a/.github/workflows/emu-build-all-linux.yml b/.github/workflows/emu-build-all-linux.yml index 0a018e7d3..ff71143b5 100644 --- a/.github/workflows/emu-build-all-linux.yml +++ b/.github/workflows/emu-build-all-linux.yml @@ -3,8 +3,46 @@ name: "Build emu (Linux)" on: workflow_call: # needed since it allows this to become a reusable workflow + inputs: + matrix_prj: + required: false + type: string + default: '["api_regular","steamclient_regular","api_experimental","steamclient_experimental","tool_lobby_connect","tool_generate_interfaces","test_gamepad_linux"]' + matrix_arch: + required: false + type: string + default: '["x64","x86"]' + matrix_cfg: + required: false + type: string + default: '["debug","release"]' + emu_build_sha: + required: false + type: string + default: "" + continue_on_error: + required: false + type: boolean + default: true workflow_dispatch: # allows manual trigger + inputs: + matrix_prj: + required: false + type: string + default: '["api_regular","steamclient_regular","api_experimental","steamclient_experimental","tool_lobby_connect","tool_generate_interfaces","test_gamepad_linux"]' + matrix_arch: + required: false + type: string + default: '["x64","x86"]' + matrix_cfg: + required: false + type: string + default: '["debug","release"]' + continue_on_error: + required: false + type: boolean + default: true permissions: contents: "write" @@ -31,26 +69,14 @@ jobs: needs: ["deps"] runs-on: "ubuntu-24.04" if: ${{ !cancelled() }} - continue-on-error: true + continue-on-error: ${{ inputs.continue_on_error }} strategy: fail-fast: false matrix: - prj: [ - # regular api - "api_regular", - "steamclient_regular", - # api + client (experimental) - "api_experimental", - "steamclient_experimental", - # tools - "tool_lobby_connect", - "tool_generate_interfaces", - # tests - "test_gamepad_linux", - ] - arch: ["x64", "x86"] - cfg: ["debug", "release"] + prj: ${{ fromJSON(inputs.matrix_prj) }} + arch: ${{ fromJSON(inputs.matrix_arch) }} + cfg: ${{ fromJSON(inputs.matrix_cfg) }} steps: # clone branch @@ -91,7 +117,7 @@ jobs: working-directory: "${{ github.workspace }}" run: | sudo chmod 777 ./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5 - ./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5 --file=premake5.lua --genproto --emubuild=${{ github.sha }} --os=linux gmake + ./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5 --file=premake5.lua --genproto --emubuild=${{ inputs.emu_build_sha || github.sha }} --os=linux gmake # mandatory Linux packages - name: "Install required packages" @@ -123,7 +149,7 @@ jobs: - name: "Upload target package" uses: actions/upload-artifact@v7 with: - name: "emu-linux-${{ matrix.prj }}-${{ matrix.cfg }}-${{ matrix.arch }}-${{ github.sha }}" + name: "emu-linux-${{ matrix.prj }}-${{ matrix.cfg }}-${{ matrix.arch }}-${{ inputs.emu_build_sha || github.sha }}" path: "build/linux" if-no-files-found: "error" compression-level: 9 diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index 273946f20..2f70ec3cf 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -16,6 +16,14 @@ on: required: false type: string default: '["debug","release"]' + emu_build_sha: + required: false + type: string + default: "" + continue_on_error: + required: false + type: boolean + default: true workflow_dispatch: # allows manual trigger inputs: @@ -31,6 +39,10 @@ on: required: false type: string default: '["debug","release"]' + continue_on_error: + required: false + type: boolean + default: true permissions: contents: "write" @@ -56,7 +68,7 @@ jobs: needs: ["deps"] runs-on: "windows-2025-vs2026" if: ${{ !cancelled() }} - continue-on-error: true + continue-on-error: ${{ inputs.continue_on_error }} strategy: fail-fast: false @@ -112,7 +124,7 @@ jobs: shell: "cmd" working-directory: "${{ github.workspace }}" run: | - "${{env.THIRD_PARTY_BASE_DIR}}\common\win\premake\premake5.exe" --file=premake5.lua --genproto --emubuild=${{ github.sha }} --dosstub --winrsrc --winsign --os=windows vs2026 + "${{env.THIRD_PARTY_BASE_DIR}}\common\win\premake\premake5.exe" --file=premake5.lua --genproto --emubuild=${{ inputs.emu_build_sha || github.sha }} --dosstub --winrsrc --winsign --os=windows vs2026 # build target - name: "Build target" @@ -125,7 +137,7 @@ jobs: - name: "Upload target package" uses: actions/upload-artifact@v7 with: - name: "emu-win-${{ matrix.prj }}-${{ matrix.cfg }}-${{ matrix.arch }}-${{ github.sha }}" + name: "emu-win-${{ matrix.prj }}-${{ matrix.cfg }}-${{ matrix.arch }}-${{ inputs.emu_build_sha || github.sha }}" path: "build/win" if-no-files-found: "error" compression-level: 9 diff --git a/.github/workflows/emu-pull-request.yml b/.github/workflows/emu-pull-request.yml index b494b296c..81277189d 100644 --- a/.github/workflows/emu-pull-request.yml +++ b/.github/workflows/emu-pull-request.yml @@ -50,3 +50,66 @@ jobs: matrix_prj: '["api_experimental"]' matrix_arch: '["x64"]' matrix_cfg: '["release"]' + emu_build_sha: ${{ github.event.pull_request.head.sha }} + continue_on_error: false + + emu-linux-release: + name: "linux" + if: ${{ !cancelled() && github.event_name == 'pull_request' }} + uses: "./.github/workflows/emu-build-all-linux.yml" + with: + matrix_prj: '["api_experimental"]' + matrix_arch: '["x64"]' + matrix_cfg: '["release"]' + emu_build_sha: ${{ github.event.pull_request.head.sha }} + continue_on_error: false + + gc-verification: + name: "gc verification" + runs-on: "ubuntu-24.04" + if: ${{ !cancelled() && github.event_name == 'pull_request' }} + + steps: + - name: "Checkout branch" + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: "Clone third-party deps (deps/common)" + uses: actions/checkout@v6 + with: + ref: "third-party/deps/common" + path: "third-party/deps/common" + + - name: "Install required packages" + shell: "bash" + run: | + sudo apt update -y + sudo apt install -y build-essential python3 protobuf-compiler libprotobuf-dev libcurl4-openssl-dev libmbedtls-dev libopus-dev portaudio19-dev + + - name: "Run full GC verification" + shell: "bash" + working-directory: "${{ github.workspace }}" + run: bash tools/run_gc_verification.sh --full --base-sha "${{ github.event.pull_request.base.sha }}" + + gc-tsan: + name: "gc thread sanitizer" + runs-on: "ubuntu-24.04" + if: ${{ !cancelled() && github.event_name == 'pull_request' }} + + steps: + - name: "Checkout branch" + uses: actions/checkout@v6 + + - name: "Install Clang" + shell: "bash" + run: | + sudo apt update -y + sudo apt install -y clang + + - name: "Run GC ThreadSanitizer tests" + shell: "bash" + working-directory: "${{ github.workspace }}" + env: + CXX: clang++ + run: bash tools/run_gc_tsan_tests.sh diff --git a/.monkeycode/MEMORY.md b/.monkeycode/MEMORY.md index 0807224bf..6050d938f 100644 --- a/.monkeycode/MEMORY.md +++ b/.monkeycode/MEMORY.md @@ -19,11 +19,12 @@ Agent 在任务执行过程中发现的条目应遵循以下格式: [项目知识摘要] - Date: [YYYY-MM-DD] - Context: Agent 在执行 [具体任务描述] 时发现 -- Category: [代码结构|代码模式|代码生成|构建方法|测试方法|依赖关系|环境配置] +- Category: [运维部署|构建方法|测试方法|排错调试|工作流协作|环境配置] - Instructions: - [具体的知识点,逐行描述] ## 去重策略 + - 添加新条目前,检查是否存在相似或相同的指令 - 若发现重复,跳过新条目或与已有条目合并 - 合并时,更新上下文或日期信息 @@ -31,489 +32,42 @@ Agent 在任务执行过程中发现的条目应遵循以下格式: ## 条目 -[Dota practice lobby 成员同步规则] -- Date: 2026-05-10 -- Context: Agent 在修复 Dota2 LAN practice lobby 加入后成员互相不可见、加入者默认占位和加入首包闪退时发现 -- Category: 代码模式 -- Instructions: - - practice lobby 的 `24/26` 中 `2004 CSODOTALobby` 必须包含所有房间成员,并同步 `member_indices`。 - - `2016 CSODOTAServerStaticLobby` 必须为房主和加入者都写入 static member,否则 UI 可能看不到对方。 - - 加入者初始状态应为 `DOTA_GC_TEAM_PLAYER_POOL slot=0`,不能默认复用房主的天辉 slot。 - - generic Steam lobby 成员列表是跨实例同步 Dota practice lobby 成员的来源,成员变化后需要推送新的 `26`。 - - 成员换槽位的 `team/slot/hero/connected` 不能只写进本进程 shared state;跨机器同步必须通过 generic lobby member data 传播,并且读取 generic 快照时不能用本地旧远端成员状态覆盖 member data。 - - owner 自己换槽位时也要读取 owner 的 generic lobby member data 并同步到 `owner_team/owner_slot`,否则非房主客户端会继续用默认 owner 天辉一号位覆盖房主的实际槽位。 - - 用 run-callback 轮询 generic lobby member data 时不要先 restore shared state;否则 shared state 的旧成员状态可能与 generic member data 来回覆盖,造成重复 `26`。 - - 房主离开多人 practice lobby 时,官方房主侧链路是 `7040 -> 25 -> 7272 -> 7014`;不能先发 `26` 并等待 lobby list 才退房,否则 UI 可能无反应。 - - 房主离开后 generic lobby owner 会转给其他成员;新 owner 客户端必须把 Dota owner metadata 更新为自己的 SteamID/account/name,保持原 Dota lobby id 和房间名不变,并向成员推送新的 `26`。 - - 房主直接关闭 Dota2 时其它客户端只会走 generic lobby 的低层 `DISCONNECT` 路径;该路径也必须在移除旧 owner 后把 generic lobby owner 转给剩余成员,否则 Dota 层不会采纳新房主。 - - 房主直接关闭 Dota2 时不能只依赖低层 `DISCONNECT` 一定触发;Dota lobby 快照刷新路径也必须检查 generic owner 是否仍在 generic 成员列表中,缺失时从剩余成员修复 owner。 - - Dota lobby 快照从 generic 成员列表重建成员时,不能无条件把 `GBE_local_lobby.owner_steam_id` 回灌进成员列表;否则旧 owner 已离开时会形成幽灵房主并阻止 UI 采纳新 owner。 - - `7044` 加入首包使用 `GBE_GetDotaGenericLobbySnapshots()` 读取 generic lobby,而不是只走 `GBE_CaptureCurrentDotaLobbyState()`;owner 缺失修复和旧 owner 防回灌必须同时覆盖 snapshot 读取路径。 - - 玩家在英雄池/slot 阶段时仍处于 practice lobby,可接收房主关闭后的 owner 移交;generic 成员/owner 变化通知不能在 `state=2` 时被跳过。 - - 超过两个真实成员在房间里时,房主离开后的 generic owner 应从剩余真实成员里随机选择,不能固定转给索引 0 或任何空槽位。 - - practice lobby 的 `7009 -> 7010` 房间频道响应必须按当前 lobby snapshot 写入所有真实成员的 SteamID;只写本机成员会导致频道显示人数正确但远端玩家 ID 缺失。 - - 官方 `7044 -> 24` 加入首包的 SO 顺序是 `2004, 2015, 2013, 2014, 2016`;不能只按双人样本写死 `2`,应按实际成员数 `N` 同步生成 `2004.field120`、`2004.field121`、`2015.field1`、`2014.field1`、`2016.field1`。 - - 多成员 join 首包必须保持各 SO 对象成员基数一致;只 patch `2004/2016` 而不更新 `2015/2014` 会造成客户端在读取 `24` 时闪退风险。 - - 房主侧 `7009 -> 7010` 可能早于 generic lobby 成员变更回调执行;构建 `7010` 前需要先驱动 matchmaking callbacks/刷新 generic 成员快照,否则房主频道响应可能仍只有 1 个成员。 - - practice lobby 聊天发言使用 Dota GC `7273`,官方公开 proto 字段形状包含 `field1 account_id`、`field2 channel_id`、`field3 persona_name`、`field4 text`、`field5 timestamp` 等;不能把 `7273` 落到 no replay template,否则频道内发言不会显示。 - - Dota `7273 CMsgDOTAChatMessage.field1` 在公开 proto 中是 `uint32 account_id`,不是 fixed64 SteamID;跨实例转发时消息体必须写 account id,否则客户端可能无法把发言映射到 `7010` 成员名。 - - Dota `7273` 需要本地回显并跨实例广播给同一 generic lobby 的其它客户端;接收端应把完整 `7273` GC 消息体排入 Dota incoming 队列。 - - practice lobby 的 `7273.field2 channel_id` 是每个客户端本地频道 id;跨实例转发远端 `7273` 时必须重写成本机 `GBE_local_lobby.chat_channel_id`,否则日志显示已入队但 UI 不显示发言。 - - 客户端自己发出的 `7273` 请求没有 sender 字段时,Dota 本地 UI 会自行显示本机消息;不要 synthetic 本地回显,否则会额外出现 `{Lobby}: 内容`。 - - 跨实例转发远端 `7273` 时,仅靠 `field1 account_id` 与 `7010` 成员表仍可能让 UI 回退显示 `{Lobby}`;转发给对端的 GC `7273` 需要补 `field3 persona_name`。 - - 如果 generic lobby 成员变化发生在本机已加入聊天频道之后,需要补发刷新后的 `7010` 聊天频道成员表;否则接收端虽然能显示远端 `7273` 内容,但因频道成员表缺少远端 SteamID/persona,会回退显示为 `lobby: 内容`。 - - 官方 practice lobby `7010 CMsgDOTAJoinChatChannelResponse` 的成员项只需要 `field1 fixed64 steam_id`、`field2 persona_name`、`field3 channel_user_id=0`、`field4 status=0`,且本机成员排在前面。 - - practice lobby 的远端 persona 需要通过 generic lobby member data 同步;只依赖 `Steam_Friends::GetFriendPersonaName()` 可能返回 `Unknown User`,导致频道成员列表和发言名退化为 account id 或 `lobby`。 - - 初次 Dota ClientWelcome 的 SO type `2002 CSODOTAGameAccountClient` 中 `field72 player_behavior_score_last_report` 是行为分;交流分字段不在当前 go-dota2/SteamKit 公开 `CSODOTAGameAccountClient` 定义中,不能把无关字段误当交流分。 - - `8096 CMsgPlayerConductScorecard` 中 `field17 raw_behavior_score` 和 `field18 old_raw_behavior_score` 需要返回高行为分;只返回 `field21 behavior_rating=Good` 会让客户端按默认 `0` 处理原始行为分。 - - `7451 CMsgServerToGCRequestBatchPlayerResourcesResponse.Result` 中 `field14 comm_score` 是交流分、`field15 behavior_score` 是行为分,房间内聊天/暂停限制可能读取这里,不能只返回 `account_id`。 - - `2002 CSODOTAGameAccountClient` 的 `field20/21/86/122` 是文字/语音/公开/新玩家聊天封禁时间,构造登录 SO 时应归零以避免模板残留导致聊天受限。 - - 官方 `playerbehostafterhostshutdown` 抓包中,房主直接关闭后的玩家侧 `26` 不压缩成员数组;旧房主 index 保留为空槽 `steam_id=0`。 - - 同一官方样本中 `2004.field121` 只包含剩余真实成员 index,`2004.field123` 包含旧房主空槽 index,例如 `member_indices=[1]`、`free=[0]`。 - - 构建 owner-transfer `26` 时必须保留旧成员槽位,不能让 `GBE_BuildDotaLobbyMembers` 把新房主重新插到 index 0 或丢弃空槽。 - - 官方 `hostlobbykickplayer` 抓包中,房主踢人请求是 `7081 k_EMsgGCPracticeLobbyKick`,请求体 `field3 account_id` 指向被踢玩家;房主侧随后收到 `26`,被踢玩家槽位变为 `steam_id=0`,`member_indices` 只保留房主 index,`free_member_indices` 包含被踢玩家 index。 - - 官方 `steambekicked` 抓包中,被踢玩家侧先收到 `25 CacheUnsubscribed` 和 `7102 GCPopup(field1=1)`,随后客户端自行发送 `7272 LeaveChatChannel`,GC 再回 `7014 OtherLeftChannel`。 - - 实现房主踢人时不能复用普通 `LEAVE` 广播语义让所有客户端移除 `source_id`;房主需要移除目标成员并定向通知目标离开 generic lobby,Dota 层再按官方链路推送 `26` 或 `25/7102/7014`。 - - 被踢检测不能在 `7044` join 早期触发;加入方可能已经构造本地 Dota lobby 但尚未收到 generic lobby join 成功回调,此时不在 generic 成员快照中不代表被踢。应等 practice lobby chat channel 建立后再把本地不在 generic lobby 视为被踢。 - - generic Steam lobby owner 处理 `Lobby_Messages::JOIN` 时需要把更新后的 lobby 快照回发给加入者;否则加入者本地 Steam matchmaking 可能一直不知道自己已在 generic lobby 中,Dota 层会把异步 join 窗口误判为被踢。 - -### 用户指令与执行约束 - -[抓包分析任务保持只读] -- Date: 2026-05-01 -- Context: 用户要求分析 `/workspace/steamhoststart-hero/` 抓包文件并明确禁止修改文件 -- Instructions: - - 在抓包分析类任务中默认保持只读,不修改业务文件。 - - 输出结论时按文件顺序逐个分析,不跳号。 - -[未知 GC 消息排查优先级] -- Date: 2026-04-28 -- Context: 用户要求在分析未知 GC 消息时约束排查方式 -- Instructions: - - 遇到未知的 GC 消息时,优先参考 SteamKit 和 go-dota2。 - - 也可以直接去 GitHub 搜索相关消息定义或实现。 - - 不要在缺少依据时自行猜想消息含义或处理方式。 - -[GC 抓包解析工具偏好] -- Date: 2026-05-03 -- Context: 用户要求后续优先使用 SteamKit 自带的 nethook2 与 nethookanalyzer2 精确解析 GC 消息结构和内容 -- Instructions: - - 后续分析 Steam / Dota GC 抓包时,优先使用 `SteamKit/resources` 中的 `nethook2` 抓包工具与 `nethookanalyzer2` 解析工具。 - - 在需要精确展开 GC 消息结构、字段和值时,优先以 nethookanalyzer2 的解析结果为准。 - -[gbe_fork 构建方式] -- Date: 2026-05-03 -- Context: Agent 在执行 `gbe_fork` 的 Start Game 对齐修正时发现 -- Category: 构建方法 -- Instructions: - - `gbe_fork` 使用 `premake5.lua` / `premake5-deps.lua` 生成构建文件,而不是根目录 `CMakeLists.txt`。 - - Linux 下依赖构建入口参考 `README.md` 中的 `./third-party/common/linux/premake/premake5 --file=premake5-deps.lua ... gmake2`。 - -[gbe_fork 主页状态排查约束] -- Date: 2026-05-03 -- Context: 用户要求排查 `gbe_fork` 中“已进游戏但主页/资料页状态不对”时的对比口径 -- Instructions: - - 排查 `gbe_fork` 的主页/资料页状态问题时,不能只看 `26`、`CSODOTALobby.game_state`、`7034`。 - - 必须同时对比 `7501` 从 `FINDING_MATCH` 切到 `PRIVATE_LOBBY` 的时间点。 - - 必须同时对比 `766` persona 回显的时间点。 - - 必须核对该切换点发生在 `lobby.state=RUN` 之后且处于 `GAME_IN_PROGRESS` 之前还是之后。 - -[官方 Start Game rich presence 顺序] -- Date: 2026-05-03 -- Context: Agent 在对照 `/workspace/steamhoststartgame/` 的 `7501` 与 `766` 抓包时发现 -- Category: 代码模式 -- Instructions: - - 官方 practice lobby `Start Game` 的 Steam rich presence 顺序是 `INIT -> FINDING_MATCH(SERVERSETUP) -> FINDING_MATCH(RUN) -> PRIVATE_LOBBY(RUN)`。 - - `025_out_7501` 时 lobby 已经是 `RUN`,但状态仍是 `#DOTA_RP_FINDING_MATCH`。 - - `030_out_7501` 才切到 `#DOTA_RP_PRIVATE_LOBBY`,并且紧跟着 `031_in_766` persona 回显同样的 `PRIVATE_LOBBY`。 - - `025_out_7501` 与 `030_out_7501` 之间的关键窗口是 `026_in_5453(inner 7014)`、`027/028_in_766(FINDING_MATCH,RUN)`、`029_in_5453(inner 26)`。 - -[主页状态分析排除手动切页] -- Date: 2026-05-03 -- Context: 用户纠正 dashboard 切出是手动操作,只是为了观察主页状态 -- Instructions: - - 分析主页从“主机载入中”变为“断开、返回游戏”时,不能把 `ChangeGameUIState` 当成自动状态变化证据。 - - `ChangeGameUIState` 在这类日志里可能只是用户手动切到 dashboard 查看主页状态的结果。 - - 后续只要日志里的 `ChangeGameUIState -> DOTA_GAME_UI_STATE_DASHBOARD` 是用户手动切页,就默认忽略,不再把它当作异常切回或 lifecycle 断点证据。 - -[主页状态优先级] -- Date: 2026-05-03 -- Context: 用户要求后续修正只关注主页状态转变,不再看档案页状态显示 -- Instructions: - - 后续只关注 dashboard 主页从“主机载入中”到“断开、返回游戏”的切换,不再以档案页状态显示作为目标。 - - 目标是进入英雄选择时,dashboard 主页就应同步变为“断开、返回游戏”。 - -[有下一步就直接继续] -- Date: 2026-05-04 -- Context: 用户要求在已有明确下一步时直接继续推进,只有在不确定时才停下来澄清 -- Instructions: - - 如果我已经有明确的下一步,应直接继续执行,不要停在征求许可。 - - 只有在存在关键不确定性或缺少必要信息时,才停下来向用户请求澄清。 - -[提交前只做语法检查] -- Date: 2026-05-22 -- Context: 用户要求后续修复提交前不要跑本地构建,只检查语法错误,通过后直接更新远端已有 PR -- Instructions: - - 提交前优先做单文件语法检查,不运行完整本地构建。 - - 语法检查通过后,直接推送到现有 PR 对应分支。 - -[PR 提交后无需持续关注 CI] -- Date: 2026-05-12 -- Context: 用户要求后续提交 PR 后不必一直关注 CI -- Instructions: - - 后续提交或更新 PR 后,不需要持续等待或 watch CI 完成。 - - 推送 PR 后最多查看一次当前状态即可,后续以用户要求为准。 - -[Dota2 闪退修复必须全面审计] -- Date: 2026-05-08 -- Context: 用户反馈多次单点补丁后仍闪退,要求全面审计代码 -- Instructions: - - 排查 Dota2 practice lobby 闪退时,不要继续只根据日志末端打一处补丁。 - - 必须先系统审计 GC 生命周期、client/server coordinator 初始化释放顺序、abandon teardown 状态机、shared/local lobby 写点和历史提交差异,再做成组修改。 - - 输出或提交前要明确说明审计覆盖面、证据和仍存在的风险。 - -[Dota2 abandon persona 构建与最终收束] -- Date: 2026-05-08 -- Context: Agent 在修复 `bf2c89a0` 后第一局断开仍闪退时发现 -- Category: 代码模式 -- Instructions: - - `bf2c89a0` 后日志已不再出现 `GC_POLL` re-init,但 `7035 -> 25 -> 7010 -> 7010 -> 7272 -> 7014` 后仍缺少 final persona/rich presence/state teardown。 - - abandon persona 模板使用的 donor fixed64 SteamID 与 launch persona 不同;若只 patch `GBE_kOldDotaSteamIdFixed64`,`766` persona 构建会失败且不会入队。 - - stale pre-postgame `7272` 分支不能只回复 `7014` 后直接 return;它还必须执行最终 INIT/no-lobby persona、rich presence 和 shared/local lobby 收束。 - -### 官方抓包与稳定机制 - -[官方 abandon 收尾链] -- Date: 2026-05-03 -- Context: Agent 在对照 `/workspace/dota2hoststart-abandon/` 与 `/workspace/steamhoststart-abandon/` 官方同步抓包时发现 -- Category: 代码模式 -- Instructions: - - 官方房主放弃游戏的 Dota GC 收尾链是 `7035 -> 25 -> 7010 -> 7010`,随后客户端再发 `7272`,GC 回 `7014`。 - - `7035` 之后 Steam rich presence 仍短暂保持 `#DOTA_RP_PRIVATE_LOBBY`,但会去掉 `lobby` 字段,只保留 `party_state: IN_MATCH`。 - - 离开 postgame chat 后,Steam rich presence 会回到 `#DOTA_RP_INIT`,因此本地 lobby/match 状态不能在 `7272` 之后继续保留。 - - 官方 persona 收尾顺序是 `085_in_766 PRIVATE_LOBBY(with lobby) -> 086_in_766 PRIVATE_LOBBY(no lobby) -> 092_in_766 INIT`,需要和 `7501` 的 rich presence 切换一起对齐。 - -[ConnectedPlayers send_reason 官方枚举] -- Date: 2026-05-03 -- Context: Agent 在继续排查 `gbe_fork` 第二次启动卡英雄选择时查阅 `SteamKit` 与 `go-dota2` 的 `CMsgConnectedPlayers` proto 定义后发现 -- Category: 代码模式 -- Instructions: - - `CMsgConnectedPlayers.SendReason` 的官方枚举里,`2` 是 `GAME_STATE`,`4` 是 `PLAYER_CONNECTED`,`10` 是 `GAMESTATE_TIMEOUT`。 - - 分析或门控 prelaunch `7034` 时,不能把 `send_reason=4` 误判为 game-state 推进信号。 - -[开始游戏问题按流程排查] -- Date: 2026-05-03 -- Context: 用户要求分析 `second.zip` 时纠正排查视角 -- Instructions: - - 排查“没英雄可选”时,应优先把问题视为 `Start Game` 流程本身仍不完整,而不是围绕“第一局/第二局”做局部修补。 - - 即使日志来自断开后重新建房,也要先检查 lobby 生命周期和开始游戏主流程是否自洽,再判断是否与前一局残留有关。 - -[second.zip 官方 Start Game 主线] -- Date: 2026-05-03 -- Context: Agent 在解析 `/workspace/second_zip` 的官方参考样本时发现 -- Category: 代码模式 -- Instructions: - - 官方 Start Game 主线是 `7041 -> 26 SERVERSETUP(match_id) -> 7501/766 FINDING_MATCH SERVERSETUP -> 26 SERVERSETUP(server_id) -> 5429 -> 26 RUN(connect) -> 26 RUN(WAIT_FOR_PLAYERS_TO_LOAD) -> 7501/766 PRIVATE_LOBBY RUN`。 - - `5429 TicketAuthComplete` 位于 `RUN/connect` 之前,而不是只在更晚的 pregame/private-lobby 阶段出现。 - - `7501/766` 是跟随 `24/26` 状态骨架变化的外围回显,核心对齐对象仍应是 `24/26` 的 lobby state、server_id、connect、game_state 链。 - -[AP Start Game HERO_SELECTION 后续推进] -- Date: 2026-05-09 -- Context: Agent 在修复 Dota2 AP Start Game 第二局英雄选择 UI 卡住时发现 -- Category: 代码模式 -- Instructions: - - AP practice lobby 在 `RUN/HERO_SELECTION(game_state=2)` 后,不能只等待后续 `7034 game_state=3` 或 `send_reason=10` 才推进 `STRATEGY_TIME(game_state=3)`。 - - 当前日志显示客户端可在 `game_state=2` 后长时间停留于 `WAIT_FOR_PLAYERS_TO_LOAD`,直到后续交互或延迟请求才收到 `game_state=3`。 - - 修正这类问题时应优先保持 `7034` connected players 正常回复,同时主动排官方后续 `26` 状态链,避免改 game mode 或 UI 层。 - -[按官方抓包一次性收敛] -- Date: 2026-05-04 -- Context: 用户对继续依赖“补发”或“门控”修窗口表示不满,要求直接按官方抓包主线与数据结构修正 -- Instructions: - - 不要继续通过额外补发消息或增加门控条件来维持窗口时序。 - - 优先按官方抓包的消息主线、对象集合、对象顺序和关键字段形状一次性收敛实现。 - - runtime builder 和条件补偿逻辑只能作为失败兜底,不能继续作为默认主路径。 - -[second.zip 关键26对象差异] -- Date: 2026-05-03 -- Context: Agent 在对比 `032/038/040/049` 四条官方 `26` 与当前实现时发现 -- Category: 代码模式 -- Instructions: - - 官方 `26 RUN(connect)` 仍然保留 `2015.extra_startup_messages[8869]`,直到 `26 RUN(WAIT_FOR_PLAYERS_TO_LOAD)` 才缩回只含空 member 的 `2015`。 - - `2016` 不能只同步 `steam_id`;其中与账号绑定的嵌套 `account_id` 数据也需要本地化,否则容易形成“外层是本地玩家,内层仍是 donor 账号”的假状态。 - -[second.zip 官方2016对象稳定字段] -- Date: 2026-05-03 -- Context: Agent 在用 `/workspace/inspect_dota_gc_capture.go` 解析 `/workspace/second_zip/032/038/040/049` 四条官方 `26` 时发现 -- Category: 代码模式 -- Instructions: - - 官方 `2016 / CSODOTAServerStaticLobby` 在 `032/038/040/049` 四个关键包里内容基本稳定,不会随着 `SERVERSETUP -> RUN(connect) -> WAIT_FOR_PLAYERS_TO_LOAD` 而被清空。 - - 该对象至少稳定包含 `all_members[].disabled_random_hero_bits`、`all_members[].banned_hero_ids`,以及 `lobby_event_points[].account_points[].account_id` 这类账号绑定嵌套数据。 - - `lobby_event_points[].account_points[].periodic_resources` 也会一起出现,因此修正 donor 账号残留时不能只看顶层 member 字段。 - -[Start Game 关键26必须全本地化] -- Date: 2026-05-03 -- Context: 用户要求继续修正 `gbe_fork` 的 Start Game 主流程时补齐官方关键 `26` 缺失结构与 donor 残留字段 -- Instructions: - - 修正 Start Game 时,不能只继续分析;要把和官方关键 `26` 对比后缺失的结构一并补齐。 - - 生成的关键 `26` 要尽量与官方 `032/038/040/049` 保持相同对象要素和顺序。 - - `26` 中各对象内容必须使用本地数据,不能继续夹带 donor 或抓包模板中的旧账号数据。 - -[Steam侧与Dota2进程侧24/26不可混淆] -- Date: 2026-05-03 -- Context: 用户说明 `dota2hoststartgame` 是开始游戏后 Dota2 启动出的另一个服务器进程与 GC 的会话 -- Instructions: - - 分析 `24/26` 时必须先区分会话来源:Steam 侧会话与 Dota2 进程侧会话不能直接混用。 - - `dota2hoststartgame` 中的 `24/26` 只有在确认语义和时序与 Steam 侧一致后,才能作为同类对照样本使用。 - - 如果两侧 `24/26` 的结构、时序或用途不同,后续分析和修正时必须分别处理,避免混淆。 - -[避免本机构建并允许恢复外部子模块] -- Date: 2026-05-04 -- Context: 用户要求恢复 `third-party/common/linux` 并约束后续执行方式 -- Instructions: - - 将 `third-party/common/linux` 保持为仓库记录的原始状态,不要把本地改动带入提交。 - - 以后不要在当前机器上执行本地编译或构建验证;构建真值以用户实测日志或远端 CI 为准。 - -[运行期 7034 不能只回 26] -- Date: 2026-05-04 -- Context: Agent 在分析用户上传的“英雄选择界面有了但没有英雄可选”新日志时发现 -- Category: 代码模式 -- Instructions: - - 运行期 `7034` 请求即使会触发 `26` 的 lobby state 推进,也仍然需要继续返回真正的 `7034 connected players` 回复。 - - 如果只发送 `26` 而不回 `7034`,Dota server 会在英雄选择阶段持续缺少 connected player 和 draft seat 视图,容易表现为英雄列表为空。 - -[官方 Start Game 的 slot=4 并非异常] -- Date: 2026-05-04 -- Context: Agent 在解析 `/workspace/second_zip` 与 `/workspace/steamhoststartgame` 的官方 `7047/26` 启动样本并对照当前失败日志时发现 -- Category: 代码模式 -- Instructions: - - `/workspace/second_zip` 的官方成功链包含 `7047(team=0, slot=4)`,后续 `028/032/038/040/049` 多条官方 `26` 也持续保持 `2004.all_members[0].slot = 4`,因此 `slot=4` 本身不能再当作启动失败根因。 - - `/workspace/steamhoststartgame` 这套官方样本没有 `7047`,说明启动期是否出现 `7047` 取决于前序客户端操作链,不能把它当成必经主线。 - - 当前更应优先防止 owner 身份在 server 实例里退回到 `settings->get_local_steam_id()` 并污染成 gameserver steam id,同时避免 server-side synthetic `24` 的 `2016` 因缺少真实 `account_id` 而过瘦。 - -[ServerWelcome 后的 24 应优先复用 launch cache donor] -- Date: 2026-05-04 -- Context: Agent 在复查 `gbe_fork/.monkeycode/MEMORY.md` 的旧结论并回看当前 `handle_dota_client_message()` 的 `ServerWelcome -> CacheSubscribed` 实现时发现 -- Category: 代码模式 -- Instructions: - - `4511/ServerWelcome` 后的关键 `24 / CacheSubscribed` 在官方链路里不应长期停留在 scratch builder 路径;launch 已开始时应优先复用官方 launch cache donor 模板,再按当前运行态重写字段。 - - 若 `match_id != 0` 但 `server_id == 0`,优先尝试 launch cache prelude donor;若 `server_id != 0`,优先尝试完整 official launch cache donor。 - - 只有 donor 路径构造失败时,才回退到当前态直构 `24`,避免继续用结构过瘦的 synthetic cache 去撞官方状态机。 - -[Start Game 启动链避免额外旁路补偿] -- Date: 2026-05-05 -- Context: Agent 在按 `/workspace/steamhoststart-abandon/` 与 `/workspace/dota2hoststart-abandon/` 继续收敛 `gbe_fork` 的 Start Game GC 流程时发现 -- Category: 代码模式 -- Instructions: - - `7041` 之后的主线应围绕当前权威 lobby `24/26` 对象推进,而不是继续回放 donor `stage1..4` 启动模板。 - - 启动期不要再通过 `on_client_connected` synthetic `7034`、`4506` 直接推 RUN、hero-selection 额外补 `26`、late steam chain 强塞 private-lobby persona 这类旁路补偿维持时序。 - - `RUN(connect)` 的推进点应优先贴近官方 `5429 -> 26 RUN(connect)` 骨架,再由后续真实 `7034` 继续驱动 `WAIT_FOR_PLAYERS_TO_LOAD` 与更晚状态。 - -[Start Game 剩余 peripheral builder 仅服务 abandon persona] -- Date: 2026-05-05 -- Context: Agent 在清理 `gbe_fork/dll/steam_game_coordinator.cpp` 的失效启动模板常量后发现 -- Category: 代码模式 -- Instructions: - - `GBE_BuildDotaPracticeLobbyLaunchPeripheralMessage()` 与 `GBE_BuildDotaPersonaStatePeripheralMessage()` 目前保留的有效用途是 abandon/postgame persona 构造,不再承担 Start Game 默认主路径。 - - `5501/5575/779` 对应的 Start Game 启动模板常量和 `stage1..4` donor `26` 模板已经全部停用并移除;后续若再出现 Start Game 时序问题,应优先检查权威 `24/26` 与真实 `7034` 主线,而不是恢复这批外围模板。 - -[官方 PRIVATE_LOBBY rich presence 切换早于英雄选择] -- Date: 2026-05-05 -- Context: Agent 在继续对照官方 Start Game 样本与 `GBE_ReapplyDotaPracticeLobbyLaunchRichPresence()` 时发现 -- Category: 代码模式 -- Instructions: - - 官方 `7501/766` 从 `FINDING_MATCH RUN` 切到 `PRIVATE_LOBBY RUN` 的时间点早于英雄选择,不应等到 `game_state >= 2` 才切换。 - - 本地 rich presence 在 launch 运行期应从 `WAIT_FOR_PLAYERS_TO_LOAD` 起就允许呈现 `PRIVATE_LOBBY RUN`,避免把 `PRIVATE_LOBBY` 延后到 hero selection 之后。 - -[SetRichPresence 不会自动生成 766 persona] -- Date: 2026-05-05 -- Context: Agent 在继续排查 `gbe_fork` Start Game 期间 `7501/766` 外显链时查看 `dll/steam_friends.cpp` 与官方样本后发现 -- Category: 代码模式 -- Instructions: - - `Steam_Friends::SetRichPresence()` 只会更新本地 rich presence 数据并触发 `FriendRichPresenceUpdate_t` / `PersonaStateChange_t` callback,不会自动向 Dota 的 GC 消息队列注入 `766` persona 回显。 - - 如果要对齐官方 Start Game 的 `7501 -> 766` 外显链,需要在状态主线切换点显式排入匹配官方形状的 `766`,不能只依赖本地 `SetRichPresence()`。 - -[完整 abandon teardown 不能在 25 后立即 reset] -- Date: 2026-05-07 -- Context: Agent 在排查 dashboard 主页点击“断开连接”后游戏闪退时发现 -- Category: 代码模式 -- Instructions: - - 完整 `7035` abandon teardown 路径已经排入 `25 + 7010 + 7010` 并切换到 postgame chat 后,不能设置 `GBE_pending_reset_after_cache_unsubscribed` 在客户端取走 `25` 后立刻 `ResetGCMemory`。 - - 过早 reset 会在客户端继续发送 `7272` 前清掉 postgame/pre-postgame chat 状态,导致无法稳定完成官方 `7272 -> 7014` 收尾链,可能引发点击断开后的客户端闪退。 - - `GBE_pending_reset_after_cache_unsubscribed` 仅适合保留给未进入完整 postgame teardown 的 current-game disconnect 兜底路径。 - -[完整 abandon teardown 不能由 polling 重新初始化 GC] -- Date: 2026-05-08 -- Context: Agent 在排查第一局断开连接后 `7272 -> 7014` 已返回但随后 GC re-init 闪退时发现 -- Category: 代码模式 -- Instructions: - - 完整 `7035` abandon teardown 不能在 `25` 后 reset,也不能在 pre-postgame `7272 -> 7014` 被取走后立即 `ResetGCMemory("7035_abandon_after_7014", ...)`。 - - `7014` 后客户端/服务器侧可能马上执行 gameserver shutdown;如果此时由 `RunCallbacks()` 或 `IsMessageAvailable()` 因 `gc_initialized=false` 自动 `initialize_gc()`,会在 teardown 末尾重新拉起 Dota GC 并触发闪退。 - - polling 入口不应自动初始化 Dota2 GC;只保留真实 `SendMessage_()` 或 gameserver 显式初始化路径拉起 GC,完整 abandon 的 `7014` 仅确认离开 pre-postgame channel,不再作为 reset 触发点。 - - `GBE_pending_reset_after_cache_unsubscribed` 仅保留给未进入完整 postgame teardown 的 current-game `25` 消费后兜底 reset。 - -[第二局 GAMESTATE_TIMEOUT 需窄条件推进英雄选择] -- Date: 2026-05-07 -- Context: Agent 在排查第一局断开后第二次开始游戏无英雄可选、dashboard 仍显示建房设置时发现 -- Category: 代码模式 -- Instructions: - - 第二局 Dota server 可能发送 `7034`,其中 `send_reason=10(GAMESTATE_TIMEOUT)` 但 `game_state=0`,此时本地 lobby 仍停在 `RUN / WAIT_FOR_PLAYERS_TO_LOAD`。 - - 若 launch server setup 已完整同步且本地正处于 `state=RUN, game_state=WAIT_FOR_PLAYERS_TO_LOAD`,应把该 timeout 当作进入 `HERO_SELECTION` 的信号,回发带 `drafts=1` 的 connected players 视图。 - - 该推进必须保持窄条件,不能把 prelaunch 或非 wait-for-players 阶段的 `send_reason=10` 直接当作 hero selection,且同一个 timeout 包不应从 wait-for-players 连跳到 strategy time。 - -[官方 Dota chat channel id 区间] -- Date: 2026-05-09 -- Context: Agent 在排查 `43c8f8b7` 仍在 `7014` 后闪退时对比官方 `steamhoststart-abandon` 抓包发现 -- Category: 代码模式 -- Instructions: - - 官方 abandon 抓包中普通 lobby chat leave 使用的 channel id 位于 `0x62e000` 附近,例如 `6481464` 和 `6481871`。 - - 官方 postgame `7010` 使用的 channel id 位于 `0x62f000` 附近,例如 `6487736`。 - - 本地构造 Dota lobby/postgame chat channel 时应保持该区间形状,避免生成过低的 `0x1xxxx` 或 `0x10xxxx` channel id。 - -[Dota2 CM 断开稳定字段对齐] -- Date: 2026-05-09 -- Context: 用户反馈 `fc95ea2a fix(gc): align Dota CM lobby object fields` 后 CM 模式断开连接不会闪退,并上传实测日志 -- Category: 代码模式 -- Instructions: - - CM practice lobby 的稳定修复点包括:`CSODOTAServerStaticLobbyMember` 在 `game_mode=2` 时不写 `disabled_random_hero_bits` field `16`,仍保留四个 `banned_hero_ids=0` field `19`。 - - `CSODOTALobby` 需要补齐官方 AP/CM 都存在的 field `103=0`、`104=0`,并在 `RUN` 且 `WAIT_FOR_PLAYERS_TO_LOAD` 之后补 field `65=0`。 - - 最新 CM 实测日志显示断开链路完成 `7035 -> 25 -> 7010 -> 7272 -> 7014`,并在 `7014_pre_postgame_retrieved` 后 reset,未再出现断开闪退。 - -[Dota2 玩家交换英雄界面账号名查询] -- Date: 2026-05-09 -- Context: Agent 在解析用户上传的 `steamplayerswaphero.zip` 英雄选择界面玩家交换英雄抓包时发现 -- Category: 代码模式 -- Instructions: - - 玩家交换英雄界面会触发资料/公会/账号查询链,已知包含 `8729 -> 8730`、`8886 -> 8887`、`7534 -> 7535`、`8673 -> 8674`、`7197 -> 7198`。 - - 抓包新增确认 `2581 k_EMsgClientToGCLookupAccountName -> 2582 k_EMsgClientToGCLookupAccountNameResponse`,请求 field `1` 是 account_id,响应 body 包含 field `1` account_id 与 field `2` account_name。 - - 当前实现应对 `2581` 返回本地账号名,避免交换英雄界面查询其他玩家名称时缺响应。 - -[Dota2 正常结束比赛 GC 收尾链] -- Date: 2026-05-10 -- Context: Agent 在解析用户上传的 `dota2hostfinishgame.zip` 与 `steamplayerfinishgame.zip` 正常结束抓包时发现 -- Category: 代码模式 -- Instructions: - - 正常结束比赛会出现 `7381 k_EMsgGCGameMatchSignOutPermissionRequest -> 7382 k_EMsgGCGameMatchSignOutPermissionResponse`,响应至少需要 `permission_granted=1`。 - - Dota2 server 侧随后发送 `7004 k_EMsgGCGameMatchSignOut`,官方链路会回 `7005`,并推进 `2004` 到 `RUN/POST_GAME` 后再到 `POSTGAME/POST_GAME`,其中 POSTGAME 形状包含 field `70=2` 与 field `111=duration`。 - - 正常结束不应完全复用 abandon 的立即 postgame `7010` 行为;应先完成 `7005`、POSTGAME `26`、`25`,后续离开旧 chat channel 的 `7272` 再补 postgame `7010` 并回 `7014`。 - - Steam 侧正常结束后可能发送 `7082 k_EMsgGCSubmitPlayerReportV2`,应回 `7083` 且 `enum_result=1` 表示成功。 - -[Dota2 正常结束后主页状态收束] -- Date: 2026-05-10 -- Context: Agent 在分析用户上传的正常结束后主页仍显示游戏中的 `gbe_gc_debug.log` 与 `console.log` 时发现 -- Category: 代码模式 -- Instructions: - - 正常结束成功进入总结页后,日志显示 `7004 -> 7005 -> POSTGAME 26 -> 25` 完成,但 Dota server 不一定会继续发送 `7272`,因此不能只依赖 `7272/7014` 来恢复主页 INIT 状态。 - - 正常结束路径应在 `25 k_ESOMsg_CacheUnsubscribed` 被取走后执行最终收束:清理 shared/local lobby、清空 settings lobby,并向 Steam/client GC 侧推 INIT persona/rich presence。 - - Steam/client GC 侧必须保留 postgame chat channel 状态,直到后续旧频道 `7272` 到来后才能按官方链路补 `7010` 并回 `7014`;不能在 normal signout finalizer 中直接清空 client 侧 `GBE_local_lobby`。 - - 正常结束的 `7272` 会落在 Steam/client coordinator 上,而 postgame channel 是 Dota2/server coordinator 在 `7004` 后生成的;finalizer 需要把 server 侧 postgame lobby snapshot 同步给 client 侧,否则 client 只知道旧普通频道,只会回 `7014` 而不会补 `7010`。 - - postgame chat channel 应使用 `GBE_GenerateDotaPostGameChatChannelId()` 的 `0x62f...` 区间;普通 lobby chat 才使用 `GBE_GenerateDotaChatChannelId()` 的 `0x62e...` 区间。 - - abandon 路径仍应保持等待 `7272 -> 7014` 的收束方式,避免破坏已稳定的断开链路。 - -[Dota2 practice lobby 搜索加入离开链] -- Date: 2026-05-10 -- Context: Agent 在解析用户上传的 `searchlobby.zip`、`joinlobby.zip`、`playerleavelobby.zip` 官方抓包时发现 -- Category: 代码模式 -- Instructions: - - 搜索 practice lobby 的主线是 `8011 k_EMsgGCLobbyList -> 8012 k_EMsgGCLobbyListResponse`,同时客户端会发送 `7111 k_EMsgGCFriendPracticeLobbyListRequest`,官方可回空 body 的 `7112`。 - - `8012` 顶层包含 field `11` fixed64 `UINT64_MAX`,lobby entries 位于 repeated field `1`;friend practice lobby list 的 `7112` response 可为空。 - - 加入 practice lobby 的主线是 `7044 k_EMsgGCPracticeLobbyJoin -> 24 k_ESOMsg_CacheSubscribed -> 7113 k_EMsgGCPracticeLobbyJoinResponse`,`7113` 成功结果 field `1=0`,并镜像请求 job 到 target job。 - - 离开 lobby 的主线不是立即 `25`;官方在 `7040` 后先推一次 `26`,客户端随后发送 `8011/7111` 刷新列表,GC 再回 `25 + 8012 + 7112`,之后才处理旧 chat channel 的 `7272 -> 7014`。 - -[Dota2 practice lobby 邀请链] -- Date: 2026-05-13 -- Context: Agent 在解析用户上传的 `hostinviteplayerlobby.zip`、`playerbeinvitedlobby.zip` 官方抓包以及好友列表邀请无反应实测日志时发现 -- Category: 代码模式 -- Instructions: - - 房主邀请 practice lobby 成员的官方主线是 `4512 k_EMsgGCInviteToLobby -> 4502 k_EMsgGCInvitationCreated -> 26 -> 7013 k_EMsgGCOtherJoinedChannel`。 - - `4512` 请求体 field `1` 是被邀请玩家 fixed64 SteamID,field `2` 是 client version;`4502` 响应 field `1` 是 group/lobby id,field `2` 是被邀请玩家 fixed64 SteamID,field `3` 是 user_offline。 - - 被邀请方接受邀请主线是 `24(2011 CSODOTALobbyInvite) -> 4513 k_EMsgGCLobbyInviteResponse -> 24(full lobby) -> 26(remove 2011) -> 25(owner_soid type=4/id=本机 SteamID) -> 7009/7010`。 - - `4513` 接受邀请时应复用 practice lobby join 的 `24` 初始化路径,但不能回 `7113`,因为官方接受邀请链路没有 `7113`。 - - 邀请对象 `2011 CSODOTALobbyInvite` 归属于 owner soid `type=4/id=被邀请玩家 SteamID`,移除邀请后发送的 `25` 也必须使用 `type=4`,不能误用 practice lobby 的 `type=3/lobby_id`。 - - 普通 `Steam_Matchmaking::InviteUserToLobby()` 只会发送 Steam 侧 lobby invite;Dota 客户端的好友列表邀请还需要被邀请方收到 GC `24` 中的 `2011 CSODOTALobbyInvite`,否则 UI 没有 Dota 邀请反应。 - - 发送 Dota 2011 邀请时还应定向同步 generic lobby snapshot 给被邀请方,否则其接受 `4513` 时可能只能看到 2011 invite object,但无法按 Dota lobby id 找到并加入底层 generic lobby。 - - 被邀请方可在普通 `Friend_Messages::LOBBY_INVITE` 回调中根据已同步的 generic lobby metadata 本地合成 `24(2011)`;房主侧应先发送 generic lobby snapshot,再发送普通 lobby invite,避免接收侧查不到 Dota lobby id。 - - 官方 `CSODOTALobbyInvite.field6` invite gid 是 fixed64,但不同接受/拒绝样本与 `field1` lobby id 的差值不稳定;不要把某个样本差值当作协议常量。 - - 官方 `CSODOTALobbyInvite.field4` 是 repeated `LobbyMember`,不是 room name;成员子消息至少包含 `name` 和 `steam_id`,当前样本里先展示的是邀请发起者而不是被邀请者。 - - 官方拒绝邀请链路是 `24(2011) -> 4513 accept=false -> 26(remove 2011) -> 25(owner_soid type=4/id=本机 SteamID)`,没有 full lobby、没有 `7009/7010`;`4513 accept=false` 必须走邀请 SO 清理,不应强行加入。 - - 官方接受邀请 `4513 accept=true` 会带 `custom_game_crc=0` 和 `custom_game_timestamp=0`;官方拒绝邀请 `4513 accept=false` 只带 `lobby_id`、`accept=false`、`client_version`。 - -[Dota2 2011 邀请去重] -- Date: 2026-05-13 -- Context: Agent 在修复 Dota2 practice lobby 二次邀请不再弹 UI 时发现 -- Category: 代码模式 -- Instructions: - - `CSODOTALobbyInvite.field6 invite_gid` 不能对同一个 lobby 固定复用;重新邀请同一玩家时需要生成新的 gid,否则客户端可能把它当成已处理过的同一邀请对象。 - - 2011 邀请对象的 `group_id/lobby_id` 可以保持不变,但 `invite_gid` 应随每次邀请变化,并且最好在 GC 日志里打印出来方便核对重复邀请链路。 - - 官方多次邀请抓包中,`24(2011) CMsgSOCacheSubscribed` 顶层带 `field3 fixed64 cache version`,通常为 `invite_gid + 2`;`26(remove 2011)` 顶层也带新的 `field3 fixed64 cache version` 和 `field6 owner_soid(type=4/id=被邀请者)`。 - - 重新邀请链路中的 SO cache version 应跨 `24` 和 `26` 单调推进,不能只让 `invite_gid` 变化但让顶层订阅/删除消息缺少或回退 version。 - -[Dota2 LAN 调试当前约束] -- Date: 2026-05-17 -- Context: 用户在 Dota2 LAN practice lobby 调试中纠正过时方向并确认证书问题已自行解决 -- Instructions: - - 继续在 `gbe_fork` 的 `260503-fix-dashboard-hero-select-homepage` 分支开发,不要切回 main/master;不本地编译,以用户实测日志和远端 CI 为准。 - - 严格按官方抓包和实测日志收敛,不靠 synthetic/UI 补丁;有明确下一步就直接继续。 - - 用户已解决证书问题;不要继续实现或测试 synthetic cert、CA、`GetCertAsync` failure、Dota datagram ticket 等证书/auth 方向。 - - 不协助 closed-source `steamnetworkingsockets.dll` 逆向、运行期内存 patch、config/auth gating 绕过;继续只做 emu 侧公开接口、HTTP hook、CM/GC 协议模拟和必要清理。 - - `networking_sockets_lib/steamnetworkingsockets.cpp` 只是薄 shim,不能替代 closed-source `steamnetworkingsockets.dll`,不要把它当主方案。 - -[Dota2 LAN 直连 endpoint 约束] -- Date: 2026-05-17 -- Context: Agent 在多轮 LAN practice lobby 连接实测后更新旧的 `server_id` 结论 -- Category: 代码模式 -- Instructions: - - LAN practice lobby 当前优先保持 `server_id=0` 或不写 field 6,避免 peer 通过 lobby `server_id` / `GetLobbyGameServer` fallback 回到 `[A:]` / `[G:]` endpoint。 - - peer 连接目标应走干净单个 `connect ip:27015`,例如 `172.19.60.90:27015`;不要拼接重复 endpoint。 - - 组网软件 LAN 场景继续发布 4508 `public_ip` / 虚拟地址,例如 `172.19.x.x:27015`,不要改成物理 LAN `private_ip`,例如 `192.168.x.x:27015`。 - - 有效 `[G:1:]` 会进入 closed-source `P2P steamid` 路径,离线 LAN 下超时;`[I:*:]` / invalid SteamID account ID 变体也已被实测排除。 - -[Dota2 LAN 7034 connected 状态不能被过期 generic member data 降级] -- Date: 2026-05-17 -- Context: Agent 在分析 host 卡在 `Loaded/expected players: 1/2` 和 peer `connection state 1` 时发现 -- Category: 代码模式 -- Instructions: - - host 端 `7034` 请求可携带 peer 的 connected player 信息,server GC 会据此把远端成员标记为 connected。 - - 已启动 LAN lobby 中,generic lobby member data 可能仍保留 peer 端早先发布的 `gbe_dota_member_connected=0`,后续 snapshot 重建如果直接读取该值,会把 server 已确认的运行期 connected 状态降级为 disconnected。 - - 修复方向是在 launched LAN member preservation 路径中保留已确认 connected 的 remote member,不让过期 generic member data 使 `7034` 响应继续输出 `connected=1 disconnected=1`。 - -[Dota2 practice lobby 断线重连官方链] -- Date: 2026-05-18 -- Context: Agent 在解析 `dotahostplayerdisconnectandreconnect`、`steamhostplayerdisconnectandreconnect`、`steamplayerdisconnectandreconnect` 官方抓包并修复 7034/26 时发现 -- Category: 代码模式 +[维护性优先的重构准则] +- Date: 2026-07-06 +- Context: 用户评价 Dota GC 后续重构计划时提出 - Instructions: - - practice lobby 运行期玩家断开时,Dota 侧主线是 `7034 CMsgConnectedPlayers` 携带 `disconnected_players`、`send_reason=PLAYER_DISCONNECTED_NOCONSEQUENCES`、`game_state=HERO_SELECTION`、`building_state=19138340`,随后 GC 推 `26`。 - - 断开后的 `26 type=2004 CSODOTALobby` 不移除断开成员;成员仍在 `all_members/member_indices` 中,但写 `leaver_status=DOTA_LEAVER_DISCONNECTED` 和 `leaver_actions=0`。 - - 玩家重连时,`7034` 携带 `connected_players`、`send_reason=PLAYER_CONNECTED`;随后 `26 type=2004` 保留同一成员 team/slot,并移除 leaver 字段。 - - 运行期 `7034` 触发 `26` lobby update 后仍必须继续返回真正的 `7034 connected players` job reply,不能提前 return。 - - `26 type=2016 CSODOTAServerStaticLobby` 的 `lobby_event_points` 应为所有真实成员写 account_points;event 19 非 owner 的 `owned=false`,event 26/39/56 全成员 `owned=true`,event 56 `normal_points=1000`、`event_level=1`。 - - event 19 的 periodic resources 按官方样本写 resource 15 `remaining/max=10/10`、resource 28 `remaining/max=1000/1000`,不要继续写全 0。 + - 重构应以便于更新维护、降低真实维护风险为目标。 + - 不为了美观、表面整洁或单纯移动代码而重构。 -[Dota2 VAC 重连日志切分约束] -- Date: 2026-05-18 -- Context: 用户补充玩家重连提示 VAC 后才让房主直接退出 +[提交闸口自动提交] +- Date: 2026-07-07 +- Context: 用户在 Dota GC 小步开发流程中补充协作偏好 - Instructions: - - 分析玩家重连 VAC 弹窗时,必须把 VAC 弹窗之后的房主主动退出、generic owner transfer、CacheUnsubscribed `25` 等日志与 VAC 根因切分开。 - - 只有 VAC 弹窗之前发生的玩家侧/房主侧 GC、Steam lobby、network 状态变化,才能作为重连 VAC 根因证据。 + - 当一个小边界改动已完成、验证通过且到达提交闸口时,直接提交该小步,不再等待额外提交授权。 -[Dota2 VAC 主界面切换触发约束] -- Date: 2026-05-18 -- Context: 用户补充玩家从游戏界面切到主界面时就会弹 VAC,断开连接后点击重连又弹 VAC +[Dota GC 验证闸口] +- Date: 2026-07-29 +- Context: Agent 在执行 Dota GC 架构收口和测试护栏补强、复现 GitHub Actions GC 验证失败时发现 +- Category: 测试方法 - Instructions: - - 分析 VAC 时必须同时检查“玩家从游戏界面切回主界面立即弹 VAC”和“断开后点击重连再次弹 VAC”两段链路。 - - 不要只把重连按钮作为唯一触发点;优先定位切回主界面时客户端请求的 lobby/game/session 状态是否已经被 emu 错误改写。 + - Dota GC 相关改动完成后运行 `tools/run_gc_verification.sh --full` 作为完整验证闸口。 + - 该命令覆盖 GC offline tests、审计、diff --check 与生产 translation unit 检查;通过不等于真协议验收。 + - 完整 GC 验证需要 `protobuf-compiler` / `protoc`,并需要 CI 中的 `third-party/deps/common` 依赖分支可用。 -[Git 提交身份偏好] -- Date: 2026-05-06 -- Context: 用户要求后续使用指定 Git 提交身份 +[Dota GC 文档入口与真相表] +- Date: 2026-07-13 +- Context: 用户要求生成防多 Agent 遗忘的重构记忆文档 +- Category: 工作流协作 - Instructions: - - 后续需要提交时,使用提交作者名称 `OpenCode` 和邮箱 `OpenCode@opencode.com`。 - - 不要修改 git config;用单次提交环境变量或命令参数应用该身份。 + - 唯一状态入口:`docs/gc/CURRENT.md`;任务队列:`docs/gc/ACTIVE_QUEUE.md`。 + - 改 emsg 路由必须同步 `docs/gc/MESSAGE_ROUTING_INVENTORY.md`;新消息只进 production registry。 + - 改 hero/wearable/showcase 必须同步 `docs/gc/HOST_AUTHORITY.md`,并跑 dual_gc H1–H5 与相关 GOLDEN_PATHS。 + - 行为改动在 PR/说明中点名 `docs/gc/GOLDEN_PATHS.md` 的 PathID;协作流程见 `docs/gc/AGENT_PLAYBOOK.md`。 + - `follow-up-task-list.md` / `next-agent-task-list.md` / 长篇 delivery 仅作历史档案,不作权威入口。 -[抓包解析必须完整展开] -- Date: 2026-05-13 -- Context: 用户纠正 Dota2 invite 构造失败是因为抓包解析遗漏结构和内容 +[默认工作目录] +- Date: 2026-07-22 +- Context: 用户在当前会话中指定项目操作目录 +- Category: 工作流协作 - Instructions: - - 后续解析抓包数据时,必须把消息结构和字段内容都完整展开,不能只看字段号、长度或摘要。 - - 构造消息前必须核对字段语义、嵌套子消息、owner/key、对象类型、字段值和官方样本内容,避免遗漏导致构造消息不可用。 - - 对 SO/GC 消息尤其要解析 repeated/object_data 等嵌套结构,确认字段内容后再编码实现。 + - 后续项目操作默认以 `/workspace/gbe_fork` 作为工作目录。 diff --git a/.monkeycode/docs/ARCHITECTURE.md b/.monkeycode/docs/ARCHITECTURE.md new file mode 100644 index 000000000..487645c83 --- /dev/null +++ b/.monkeycode/docs/ARCHITECTURE.md @@ -0,0 +1,278 @@ +# GBE Fork Architecture + +## Overview + +GBE Fork is a C++ Steam emulator fork with platform-specific build targets, Steam API implementations, loaders, tools, and protocol helpers. The Dota Game Coordinator subsystem emulates selected GC request, lobby, custom-game, inventory, chat, replay, and reconnect behavior. + +The Dota GC architecture uses explicit ownership and narrow decision boundaries while preserving existing Steam-facing APIs and wire behavior. Production runtime ownership is rooted in `Steam_Client`. Each client or gameserver role receives its own coordinator, reconnect adapter, serialized connection state, and lifecycle executor, while both roles share one application-owned lobby Store. + +The maintenance model is: + +```text +wire request + -> request context and routing + -> handler + -> pure planner or transition + -> ordered typed effects + -> coordinator-owned executor + -> Store, queue, callback, or network adapter +``` + +## Technology + +- Primary language: C++17. +- Build generation: Premake. +- Linux production build: GNU Make through repository-provided Premake. +- Windows production build: Visual Studio/MSBuild through repository-provided Premake. +- Focused verification: Bash, Python 3, GCC or Clang. +- Concurrency verification: Clang ThreadSanitizer. +- CI: GitHub Actions with Windows, Linux, GC verification, and TSAN jobs. +- Dependencies: Git Submodules under `third-party/`. + +## Repository Structure + +```text +gbe_fork/ +|-- dll/ # Steam API and production implementation +| |-- steam_client.cpp # Production application composition root +| |-- steam_game_coordinator.cpp +| |-- gbe_dota_*.{h,cpp} # Dota GC domains, adapters, and helpers +| `-- dll/ # Public and internal class headers +|-- tools/ # Focused tests, replay tools, and verification scripts +|-- docs/gc/ # Maintained GC reference contracts and history +|-- .monkeycode/docs/ # Maintenance Wiki +|-- .monkeycode/specs/ # Feature specifications and completed task records +|-- .github/workflows/ # Production and GC CI gates +|-- post_build/ # Runtime examples and release guidance +|-- third-party/ # Git Submodules and build dependencies +`-- premake5.lua # Project and target definitions +``` + +## Production Ownership + +`Steam_Client` is the production composition root. It owns the shared lobby backing state, Store, runtime state, networking, callbacks, reconnect adapters, serialized sockets, lifecycle executors, and client/gameserver coordinators. + +```mermaid +flowchart TD + Client["Steam_Client"] --> Store["Shared lobby Store"] + Client --> Runtime["Dota RuntimeState"] + Client --> ClientCallbacks["Client callbacks"] + Client --> ServerCallbacks["Gameserver callbacks"] + Client --> ClientAdapter["Client reconnect adapter"] + Client --> ServerAdapter["Gameserver reconnect adapter"] + ClientAdapter --> ClientSerialized["Client serialized sockets"] + ServerAdapter --> ServerSerialized["Gameserver serialized sockets"] + Client --> ClientExecutor["Client lifecycle executor"] + Client --> ServerExecutor["Gameserver lifecycle executor"] + Store --> ClientGC["Client Steam_Game_Coordinator"] + Store --> ServerGC["Gameserver Steam_Game_Coordinator"] + ClientExecutor --> ClientGC + ServerExecutor --> ServerGC +``` + +The logical lifecycle order is: + +```text +Settings -> Network -> Callbacks -> Store -> Services -> Coordinator +``` + +`gbe::dota::LocatorBindingGuard` publishes the application-owned Store and runtime state after base services exist and before either coordinator is constructed. Partial binding and constructor failure roll back automatically. Destruction follows the reverse dependency order, with both coordinators destroyed before the guard is reset. + +The standalone `gbe::dota::CompositionRoot` in `dll/gbe_dota_composition_root.*` is a focused ownership model used by offline tests. Production currently performs equivalent assembly directly in `Steam_Client`; maintainers should treat `Steam_Client` as the runtime authority. + +## Coordinator Responsibilities + +Each `Steam_Game_Coordinator` owns role-local state and queues: + +- `GBE_LocalLobby`. +- Pending and incoming GC messages. +- Monotonic lobby generation counter. +- Deferred teardown and lifecycle slots. +- Launch, replay, and role-local transient state. + +The coordinator receives explicit references to the shared Store, typed handler registry, lifecycle executor, settings, networking, storage, and callback infrastructure. + +Client and gameserver local lobby state remain separate. Shared coordination occurs through the application Store, using immutable snapshots and generation-aware commits. + +Generic lobby metadata capture first creates a pure `GenericLobbyCapturePlan` from raw lobby metadata, then applies state, runtime identity, options, and custom-game fields to the Local lobby in one boundary. The coordinator retains stale-state diagnostics, member refresh, protocol effects, and shared Store synchronization decisions. + +Cache, replay, and lobby-details payload consumers use `GBE_CaptureCurrentDotaLobbySnapshotForPayload()` as their common pure projection entry. It starts from a Local copy, applies generic capture and member/runtime merge rules to that copy, and preserves Local state, generic metadata, shared Store, and owner repair/adopt behavior. + +`GBE_SyncCapturedDotaLobbyState()` is the explicit coordinator boundary for host capture propagation. The 7009 join-chat path is the generic metadata host-sync path: it completes Local capture, lets the host publish through the generation-gated publish facade, then emits 7010. Member-change, 7034 runtime-member refresh, and launch-state push capture paths are client observe paths; cache, replay, and details use the pure payload projection facade. `audit_generic_metadata_capture_modes` protects this mode contract. + +Incremental client restore composes `SourceAwareSharedRuntimeRestorePlan` before synchronizing Local generation, then applies `state`, `game_state`, `launch_phase`, `room_name`, `connect`, `match_id`, `server_id`, and `game_start_time` in one pure boundary. A same-generation Local generic capture owns its launch/runtime or runtime-identity field group; shared snapshot values remain the source for ordinary client observation, while full server adopt keeps its established path. `audit_local_shared_merge_inventory` keeps the documented Local/shared merge restore entrypoints, owners, and field-group labels aligned with comment-stripped function definitions in their production owner. + +The 8052 custom-game started-loading lifecycle path uses `LocalLifecyclePreWrite` as its action boundary. Direct and wrapped requests share the same action sequence: pre-write Local lifecycle fields first, then attempt the runtime lobby details update, with fallback shared publish and details update still guarded by the runtime queue result. + +Postgame teardown records old chat channel suppression as explicit Local tombstone state: active flag, channel id, and generation. The tombstone helper is the shared matching boundary for old-channel 7272 handling and queued 7014 retrieval finalization, while current postgame channel leave clears the tombstone in one helper call. + +## Request Routing + +Direct and wrapped GC requests are normalized into `DotaGcRequestContext`. The context records the inner message ID, request body, source and target jobs, request path, and wrapped session metadata. + +```mermaid +sequenceDiagram + participant Game as Dota Client + participant GC as Steam_Game_Coordinator + participant Router as Dota Request Router + participant Registry as Typed Handler Registry + participant Handler as Domain Handler + participant Executor as Lifecycle Executor + + Game->>GC: SendMessageToGC + GC->>Router: Parse direct or wrapped request + Router->>Registry: Find entry by message and path + Registry->>Handler: Invoke typed adapter + Handler->>Handler: Build context and pure plan + Handler->>Executor: Execute ordered typed effects + Executor->>GC: Queue response or apply side effect + GC-->>Game: GC message availability and retrieval +``` + +The production typed registry and dispatcher live in `dll/gbe_dota_post_login_dispatcher.cpp`. Production builds and the offline handler harness compile this same implementation. Each entry defines: + +- Message ID. +- Direct, wrapped, or dual request mode. +- Wrapped-session forwarding policy. +- Lifecycle risk class. +- Adapter and handler identity. +- High-risk smoke or replay fixture metadata. + +Special handlers with custom context shaping remain explicit outside the table until their routing contract can be unified safely. Direct post-login registry misses run through `GBE_HandleDotaDirectConditionalFallback()` before template replay; that boundary owns `8744` observe-only logging and `5410`/`5432` late-steam conditional consume behavior. Wrapped post-login registry misses run through `GBE_HandleDotaWrappedHardMiss()` and remain a logged hard stop with no template replay or SetTeamSlot fallback. Template replay owns only the `TEMPLATE_ONLY` whitelist; registry-owned defensive fallbacks for `8879`, `8095`, `8009`, and `7091` route through `GBE_TryHandleDotaRegistryDefensiveTemplateReplay()` before the template-only switch. The wrapped post-login production parser remains `extract_wrapped_post_login_request()`; `GBE_ExtractWrappedDotaDirectContext` is a `LEGACY_UNUSED` inline parser guarded from production `.cpp` call sites by audit. `audit_registry_inventory_guard` keeps registry emsg, HandlerId, modes, and lifecycle aligned with routing inventory; `audit_template_only_inventory_guard`, `audit_direct_conditional_fallback_routing`, `audit_wrapped_hard_miss_routing`, `audit_registry_defensive_template_routing`, and `audit_legacy_wrapped_parser_guard` keep helpers, switches, parser usage, and routing inventory aligned. + +## Handler And Effect Boundaries + +Handlers perform protocol parsing, context mapping, planner invocation, response adaptation, and structured logging. Pure planners and transitions decide state changes and action ordering. Executors own observable side effects. + +Typical side effects include: + +- GC response or notification pushes. +- Shared lobby publication. +- Settings and rich-presence updates. +- Generic lobby operations. +- Callback queue insertion. +- Steam network connection attempts. +- Delayed runtime updates and teardown finalization. + +`GBE_DotaActionList` is the ordered contract between planning and execution for multi-effect paths. Action ordering is protocol behavior and must remain stable. + +## Shared Lobby Store + +`gbe::dota_lobby_state::Store` wraps one shared `GBE_SharedDotaLobbyState` and the existing recursive process mutex. Its public behavior is value-based: + +- `snapshot()` returns an immutable copy. +- `publish()` commits a complete value. +- Monotonic publish rejects older generations. +- `compare_update()` accepts only the expected generation. +- `compare_clear()` clears only the expected generation and preserves that generation as a tombstone. +- A tombstone rejects same-generation republish and permits a newer generation to replace it. +- `clear()` remains available for test and process-level forced reset. + +Every business operation captures one complete snapshot and derives all decisions from that version. Store mutators transform a copied candidate and perform no network, callback, logging, filesystem, or coordinator effects. + +```mermaid +flowchart LR + ClientLocal["Client local lobby"] --> Publish["Generation-aware publish"] + ServerLocal["Gameserver local lobby"] --> Publish + Publish --> Store["Application Store"] + Store --> ClientSnapshot["Client immutable snapshot"] + Store --> ServerSnapshot["Gameserver immutable snapshot"] + ClientSnapshot --> RestoreClient["Client restore and replay"] + ServerSnapshot --> RestoreServer["Gameserver restore and replay"] +``` + +## Lifecycle State Machine + +The core lifecycle vocabulary is dependency-light and `constexpr`: + +- States: `Idle`, `Created`, `Joined`, `Setup`, `Loading`, `Loaded`, `Running`, `PostGame`. +- Events include create, join, setup, loading, loaded, run, postgame, leave, abandon, reset, generation, reconnect, runtime, and teardown categories. +- Compile-time checks classify all state/event pairs. + +```mermaid +stateDiagram-v2 + [*] --> Idle + Idle --> Created: Create + Idle --> Joined: Join + Created --> Setup: Setup + Joined --> Setup: Setup + Setup --> Loading: Loading + Loading --> Loaded: Loaded + Loaded --> Running: Run + Running --> PostGame: PostGame + Created --> PostGame: Leave or Abandon + Joined --> PostGame: Leave or Abandon + Setup --> PostGame: Leave or Abandon + Loading --> PostGame: Leave or Abandon + Loaded --> PostGame: Leave or Abandon + PostGame --> Idle: Reset +``` + +The state machine authorizes transitions and emits typed effect requests. Existing planners and executors remain responsible for payloads and production side effects. + +## Generation And Asynchronous Safety + +Generation is the process-local lobby lifecycle epoch. Create, join, leave, reset, and recovery boundaries advance it monotonically. The counter saturates at `uint64_t` maximum and never wraps. + +Asynchronous work captures generation at creation and validates it at the final execution boundary: + +- Delayed GC messages capture lobby ID and generation. +- Deferred lifecycle slots capture lobby ID and generation. +- Reconnect callbacks carry generation execution guards. +- Runtime updates carry the current generation. +- Store writes reject stale generations. + +This prevents a delayed operation from an old lifecycle from modifying a new lifecycle that reused the same wire lobby ID. + +## Reconnect Architecture + +Reconnect uses three narrow ports: + +- Context provider. +- Direct connector. +- Callback queue. + +`GBE_DotaReconnectNetworkAdapter` implements these ports for production. Client and gameserver roles each own an independent adapter and serialized connection state. + +Reconnect context priority is: + +```text +Shared -> Recent -> Local -> GenericRecovery +``` + +The orchestration is divided into pure preparation and external execution: + +```mermaid +sequenceDiagram + participant Serialized as Serialized Sockets + participant Provider as Context Provider + participant State as Instance Reconnect State + participant Connector as Direct Connector + participant Queue as Callback Queue + + Serialized->>Provider: Read reconnect context + Serialized->>State: Prepare generation and dedup plan + Note over Serialized,State: global mutex then instance mutex + Serialized->>Serialized: Release instance and global locks + Serialized->>Connector: ConnectByIPAddress when planned + Serialized->>Queue: Queue guarded engine callback when planned +``` + +The dedup identity is generation, server ID, and endpoint. External network and callback effects execute after both locks are released. + +## Message Retrieval As A Lifecycle Boundary + +Some teardown effects finalize when the client retrieves a protocol-visible message. For example, retrieval of selected unsubscribe or postgame messages can consume a generation-guarded deferred slot and complete cleanup. + +Maintainers must preserve this observable boundary. Moving finalization from retrieval time to queue time can change protocol order and allow stale cleanup to affect a replacement lobby. + +## Architecture Authority + +Current architecture decisions are protected by: + +- `tools/_audit_gc_refactor.py`. +- `tools/test_audit_gc_refactor.py`. +- `tools/run_gc_verification.sh`. +- `tools/run_gc_tsan_tests.sh`. +- `docs/gc/architecture-investment-gates.md`. +- `.monkeycode/specs/gc-refactor-next-phase/tasklist.md`. diff --git a/.monkeycode/docs/DEVELOPER_GUIDE.md b/.monkeycode/docs/DEVELOPER_GUIDE.md new file mode 100644 index 000000000..fce10ac85 --- /dev/null +++ b/.monkeycode/docs/DEVELOPER_GUIDE.md @@ -0,0 +1,171 @@ +# GC Developer Guide + +## Prerequisites + +GC focused development requires: + +- Bash. +- Python 3. +- A C++17 compiler. +- Git with the repository Submodules initialized. +- Clang for ThreadSanitizer. + +The root `README.md` contains full Windows and Linux dependency setup for production builds. + +## Initial Setup + +Initialize Submodules before building: + +```bash +git submodule update --init --recursive --depth 1 +``` + +Confirm the working compiler: + +```bash +c++ --version +python3 --version +``` + +## Standard Workflow + +Use the fast gate while iterating: + +```bash +bash tools/run_gc_verification.sh --fast +``` + +Use the full gate before handing off a completed GC change: + +```bash +CXX=c++ bash tools/run_gc_verification.sh --full --base-sha origin/dev +``` + +Run TSAN for changes involving shared state, callbacks, reconnect, generation, queues, locks, or object lifetime: + +```bash +CXX=clang++ bash tools/run_gc_tsan_tests.sh +``` + +Use a Clang full pass when changing headers, template-heavy code, or compiler-sensitive control flow: + +```bash +CXX=clang++ bash tools/run_gc_verification.sh --full --base-sha origin/dev +``` + +## Change Workflow + +For a production bug: + +1. Capture the message ID, request path, lobby ID, generation, server ID, and operation sequence. +2. Reproduce the failure in the smallest suitable test layer. +3. Add a failing regression test. +4. Make the smallest change in the owning layer. +5. Run the focused test and fast verification. +6. Run full verification before delivery. +7. Add TSAN when the change crosses an asynchronous or synchronization boundary. + +## Owning Layer Selection + +| Change | Preferred owner | +| --- | --- | +| Wire parsing or wrapping | Request router or wire helper | +| Message-to-handler mapping | Canonical typed registry | +| Request-specific orchestration | Domain handler | +| State decision or action ordering | Pure planner or lifecycle transition | +| Shared lobby mutation | Store or Store-facing coordinator operation | +| Multiple observable side effects | Typed action list and lifecycle executor | +| Steam network call | Reconnect or network adapter | +| Callback delivery | Callback queue and execution guard | +| Delayed lifecycle work | Coordinator queue with generation capture | +| Diagnostic classification | Typed diagnostic event model | + +## Adding A Post-Login Message + +1. Determine direct, wrapped, or dual mode. +2. Define wrapped-session behavior. +3. Add a typed registry entry to the canonical production table. +4. Reuse an existing adapter or add a narrow adapter. +5. Keep payload parsing and response construction in the appropriate domain handler/helper. +6. Add a high-risk fixture when the entry mutates lobby or lifecycle state. +7. Run `gbe_dota_handler_registry_test`, the handler suite, replay where applicable, and Audit 4. + +The canonical table, adapters, and dispatcher are in `dll/gbe_dota_post_login_dispatcher.cpp`. The handler harness compiles this production TU through `tools/gbe_dota_handler_test/test_wrapper.cpp`. + +Avoid a parallel message switch or a second table. Audit 4 and Audit 14 treat the typed registry as the canonical mapping source. + +## Adding A Lifecycle Transition + +1. Extend the typed state/event vocabulary. +2. Add or update the pure transition function. +3. Classify every affected state/event pair. +4. Return typed effects without performing side effects. +5. Map effects to existing planner/action/executor behavior. +6. Add example tests for accepted, ignored, duplicate, and rejected cases. +7. Extend properties and the differential reference model. +8. Add a production-path fake or handler regression. +9. Refresh `docs/gc/architecture-investment-inputs.json` for a major state-space expansion. + +## Adding Asynchronous Work + +Every asynchronous operation that can observe or mutate lobby state must: + +1. Capture lobby ID and generation at queue time when state identity requires both. +2. Copy payload and guard metadata into the queue entry. +3. Validate generation at the final execution boundary. +4. Reject stale work before state mutation or callback delivery. +5. Preserve queue ordering for protocol-visible work. +6. Add a leave/rejoin or replacement-generation regression. + +## Shared Store Changes + +- Capture one complete immutable snapshot per business operation. +- Use generation-aware publish or compare/update for lifecycle-sensitive writes. +- Use `compare_clear(expected_generation)` for production cleanup and keep the tombstone generation monotonic. +- Keep `clear()` for test or process-level forced reset. +- Keep Store mutators free from external side effects. +- Publish reconnect context only after the shared-state commit succeeds. +- Preserve client participant checks during shared-state adoption. +- Add focused Store tests and bounded concurrency coverage. + +## Locator Lifetime Changes + +- Keep Store and runtime-state publication owned by `gbe::dota::LocatorBindingGuard`. +- Create the guard after base dependencies and before coordinator construction. +- Reset the guard after both coordinators are destroyed. +- Run `gbe_dota_locator_test`, full verification, and TSAN for lifetime changes. + +## Reconnect Changes + +- Keep source mapping in `gbe_dota_reconnect_context.*`. +- Keep decision/state mutation in reconnect prepare. +- Keep `ConnectByIPAddress` and callback queueing in the adapter/effect phase. +- Preserve the lock order documented in `docs/gc/concurrency-ownership.md`. +- Keep the dedup key composed of generation, server ID, and endpoint. +- Add focused connector/callback fakes and generation regressions. + +## New Source Files + +Production files under `dll/` normally enter production Premake targets through `common_files`. Offline tests use explicit source lists. + +When adding a testable production `.cpp` file: + +1. Add it to each affected command in `tools/run_gc_offline_tests.sh`. +2. Add it to relevant Premake test targets in `premake5.lua`. +3. Keep test-only wrappers and stubs out of production targets. +4. Add headers to Premake target lists for project visibility where appropriate. +5. Run Audit 6 to verify source-list inclusion. + +## Production Builds + +Production PR gates build `api_experimental` x64 release on Windows and Linux. The repository-provided Premake binaries and Submodules are the authoritative CI environment. + +The bundled Linux Premake currently requires GLIBC 2.38. Environments with older glibc can still run focused offline, audit, replay, and TSAN gates; production Linux integration remains the blocking CI job. + +## Documentation Discipline + +- Update `ARCHITECTURE.md` when ownership or runtime flow changes. +- Update `GC_MAINTENANCE.md` when an invariant or audit boundary changes. +- Update `TESTING.md` when scripts, targets, counts, or CI jobs change. +- Update `TROUBLESHOOTING.md` when diagnostic fields or operational paths change. +- Keep completed task lists as historical records. diff --git a/.monkeycode/docs/GC_ARCH_REFACTOR_TASKLIST.md b/.monkeycode/docs/GC_ARCH_REFACTOR_TASKLIST.md new file mode 100644 index 000000000..57f62aef1 --- /dev/null +++ b/.monkeycode/docs/GC_ARCH_REFACTOR_TASKLIST.md @@ -0,0 +1,111 @@ +# GC 架构收口任务清单 + +## 目标 + +- 让 `Steam_Game_Coordinator` 回到 GC 基础设施角色。 +- 让 Dota lobby 生命周期进入独立的 Dota domain。 +- 让高风险副作用通过统一 action/executor 承接。 +- 让后续 GC 维护显著变轻。 + +## 执行原则 + +- 按小步渐进执行,每轮只改一条高风险路径。 +- 每轮完成后运行 `tools/run_gc_verification.sh`。 +- 优先收敛职责边界,后做物理文件拆分。 +- 保持协议顺序、payload 输出和 replay 行为稳定。 + +## 第一轮 + +- [x] 1. 将 `Steam_Game_Coordinator` 明确为 GC 基础设施入口 + - [x] 收敛职责说明,后续 handler 只负责消息入口和流程编排。 + - [x] 让 Dota lobby 生命周期逻辑进入 planner/domain 层。 + +- [x] 2. 改造 `GBE_PushDotaLaunchStateToClientPeer` 为样板路径 + - [x] 建立完整 launch push context,集中收集 source lobby、target、shared snapshot、captured lobby、last pushed game state。 + - [x] 将多段 `LaunchStatePushPlanInput` 填充收敛为 context -> planner 入口。 + - [x] 让 planner 返回 skip reason、payload build request、action sequence。 + - [x] 将 launch push 动作接入统一 `GBE_DotaActionList` 或等价统一 action 模型。 + - [x] 保持顺序为 `RecordCacheSubscription -> PushCacheSubscribed -> PushDetailsUpdate -> ReapplyRichPresence -> SetLastGameState`。 + - [x] 继续评估阶段性 skip 判断,合并 capture 后重复 planner 调用,同时保持日志粒度。 + +- [x] 3. 为 launch push 补强测试护栏 + - [x] 增加 planner 单测,覆盖 valid push、suppressed source lobby、invalid target、suppressed shared lobby、no captured lobby、duplicate game state。 + - [x] 增加 context mapping 和统一 action list 顺序单测。 + - [x] 增加 smoke test,断言 launch push 的 action sequence 与 skip path 行为。 + - [x] 为 handler smoke test 增加 launch push 专用 seam,复用真实 planner/action-list 并覆盖 launch push executor 顺序。 + - 评估结果:直接纳入 `gbe_dota_lobby_launch_coordinator.cpp` 会与现有 handler smoke stub 中的 launch/postgame/response helper 定义大面积重叠,需要单独改造 wrapper 结构。 + +- [x] 4. 改造 `GBE_HandleDotaPracticeLobbyCreateRequest` + - [x] 建立 create lobby context,集中 request、pre-reset lobby、custom game、generic lobby 可用性、settings 状态读取。 + - [x] 使用现有 reset plan 决定 previous cache unsubscribed,并把 25 纳入 create action list。 + - [x] 抽出 reset plan,决定 `ResetGCMemory` 和 previous cache unsubscribed。 + - [x] 抽出 create lobby state plan,集中生成新的 local lobby 状态。 + - [x] 将 `PushCacheUnsubscribed`、publish、record cache subscription、push 24、push 7055 收敛到统一 create action list。 + - [x] 将 `ResetGCMemory`、`CreateGenericLobby` 收敛到 action list。 + - [x] 保持 create path 顺序为 `25 -> 24 -> 7055`。 + +- [x] 5. 为 create lobby 补强测试护栏 + - [x] 增加 create action list 单测,覆盖可选 25 以及 `24 -> 7055` 顺序。 + - [x] 增加 smoke test,覆盖真实 handler 的 `25 -> 24 -> 7055`。 + - [x] 验证 record cache subscription 在 push 前。 + - [x] 验证 custom game create 的状态归一化。 + +- [x] 6. 改造 `GBE_HandleDotaPracticeLobbyJoinRequest` + - [x] 建立 join lobby context,集中 request lobby id、pass key、matched generic lobby、当前 local lobby、settings sync 状态读取。 + - [x] 抽出 join lobby merge plan,决定 JoinLobby、SyncSettingsLobby、pass key 更新、local lobby 合并。 + - [x] 将 JoinLobby、SyncSettingsLobby、publish、record cache subscription、push 24、push 7113 收敛到 join action list。 + - [x] 保持 join path 顺序为 `24 -> 7113`。 + +- [x] 7. 为 join lobby 补强测试护栏 + - [x] 增加 action list 单测,覆盖 direct join、matched generic lobby、无 join response 的 `24` only 顺序。 + - [x] 保持 smoke test 覆盖 matched generic lobby 和 `24 -> 7113` 顺序。 + - [x] 增加 smoke test,覆盖 empty local lobby、pass key。 + +- [x] 8. 第一轮检查点 + - [x] 运行 `tools/run_gc_verification.sh`。 + - [x] 确认 launch/create/join 三条路径通过 full verification。 + - [x] 确认新增逻辑没有扩大 handler 直接写 `GBE_local_lobby` 和直接执行副作用的范围。 + +## 第二轮 + +- [x] 9. 收敛 `gbe_dota_lobby_flow` 的功能混杂 + - [x] 拆出 `gbe_dota_lobby_launch_flow.{h,cpp}`,承载 launch push context、planner、skip reason、payload build request。 + - [x] 拆出 `gbe_dota_lobby_member_flow.{h,cpp}`,承载 member 查找、upsert 和 LAN launch remote member 判断。 + - [x] 拆出 `gbe_dota_lobby_payload_flow.{h,cpp}`,承载 cache/details payload routing、authoritative payload data 选择规则。 + - [x] 拆出 `gbe_dota_chat_flow.{h,cpp}`,承载 chat display name 和 join chat member list。 + - [x] 更新测试源列表和审计白名单,保持 `tools/run_gc_offline_tests.sh` 与审计脚本通过。 + +- [x] 10. 改造 abandon / signout / postgame 生命周期收尾 + - [x] 复用 7035 abandon context/decision,收敛 arcade launch failure 和 current-game disconnect 的 `DiscardLaunchMessages`、`SetPendingReset`、`MarkAbandonedSuppressed`、`PushCacheUnsubscribed` 到 action list。 + - [x] 收敛 7004 normal signout 的 postgame 后 `PushCacheUnsubscribed`、`SetPendingNormalSignoutFinalize` 到 action list。 + - [x] 收敛 7040 leave 的 `MarkAbandonedSuppressed`、`PushCacheUnsubscribed`、`ResetGCMemory/LeaveGenericLobby` 到 action list。 + - [x] 收敛 postgame teardown 的 `PushCacheUnsubscribed`、`QueuePostGameJoin`、`ClearPendingReset` 到 action list。 + - [x] 收敛 player postgame observation cleanup 的 `ClearRichPresence`、`ResetLaunchPeripheral`、`ClearDotaLobbyRuntimeState`、`PushCacheUnsubscribed`、`ClearSettingsLobby` 到 action list。 + - [x] 收敛 normal signout finalize 的 `ClearSettingsLobby`、`ResetLaunchPeripheral`、`ClearDotaLobbyRuntimeState`、`ClearRichPresence`、client `PushCacheUnsubscribed` 到 action list。 + - [x] 收敛 abandon finalize after 7014 的 `ResetGCMemory/LeaveGenericLobby` 到 action list。 + - [x] 建立 teardown plan,统一表达 shared state clear、local lobby clear 的状态转移。 + - [x] 保持 25、7010、shared state clear、generic lobby leave 的时序稳定。 + +- [x] 11. 为 teardown 路径补强测试护栏 + - [x] 扩展 `game_flow` replay fixture,覆盖 abandon、normal signout、lobby leave、postgame channel leave、postgame 7014、postgame 7010 的 wire 摘要。 + - [x] 增加 action-list 单测,覆盖 cache unsubscribe、pending reset、postgame join、shared state clear、normal signout finalize、abandon finalize 的 action 顺序。 + - [x] 确认 handler smoke 覆盖 host/client shared state:server-owned postgame 保留、mismatched server lobby 清理、host-client arcade 优先级。 + +- [x] 12. 第二轮检查点 + - [x] 运行 `tools/run_gc_verification.sh`。 + - [x] 确认拆分后无 header zombie declaration、无 source-list inclusion 问题、无 replay 行为回归。 + +## 收尾验收 + +- [x] 13. 完成架构收口验收 + - [x] handler 主要承担 `parse -> context -> planner -> payload builder -> executor -> log`。 + - [x] launch/create/join/teardown handler 中直接写 `GBE_local_lobby` 的新增逻辑显著减少。 + - [x] launch/create/join/teardown handler 中直接 `push_incoming_now`、publish、generic lobby 操作显著减少。 + - [x] Dota lobby domain 负责业务决策,coordinator/executor 负责执行副作用。 + - [x] `gbe_dota_lobby_flow` 不再同时承载 launch、chat、member、payload 多类职责。 + +## 建议执行顺序 + +1. 先完成第一轮的 2、3、4、5、6、7、8。 +2. 样板稳定后再完成第二轮的 9、10、11、12。 +3. 最后执行 13 做收尾验收。 diff --git a/.monkeycode/docs/GC_DELIVERY_SUMMARY.md b/.monkeycode/docs/GC_DELIVERY_SUMMARY.md new file mode 100644 index 000000000..b30514c21 --- /dev/null +++ b/.monkeycode/docs/GC_DELIVERY_SUMMARY.md @@ -0,0 +1,311 @@ +# Dota GC Delivery Summary + +## Current Work Branch + +- Workspace: `/workspace/gbe_fork` +- Branch: `trae/agent-inRF11` +- Tracking branch: `origin/trae/agent-inRF11` +- Status: clean except the pre-existing untracked `docs/superpowers/` directory +- Ahead of tracking branch after this documentation update: 12 commits +- Review range: `origin/trae/agent-inRF11..HEAD` +- Latest P5 implementation commit: `d29b7ef1 test(gc): verify lifecycle action ownership` + +## Dev-Based Integration Candidate + +- Worktree: `/tmp/opencode/gbe-migration-check-20260707/dev-worktree` +- Branch: `260707-refactor-gc-dev-baseline` +- Base: `origin/dev` +- Status: clean +- Review range: `origin/dev..260707-refactor-gc-dev-baseline` +- Commits: + - `d9e6e4fc refactor(gc): migrate extraction baseline onto dev` + - `06de534a refactor(gc): migrate lobby lifecycle closure onto dev` + +## Deliverables + +Generated local deliverables are under `/tmp/opencode/gbe-migration-check-20260707/deliverables`. + +- `README.md` +- `dev-migration-pr.md` +- `original-branch-pr.md` +- `dev-migration-stat.txt` +- `dev-migration-commits.txt` +- `original-branch-stat.txt` +- `original-branch-commits.txt` +- `dev-migration-series/` +- `original-branch-series/` + +## Verification Completed + +Current work branch: + +```bash +tools/run_gc_verification.sh +git diff --check origin/trae/agent-inRF11..HEAD +git submodule foreach 'git status --porcelain' +``` + +Dev-based integration candidate: + +```bash +tools/run_gc_verification.sh +git diff --check origin/dev..HEAD +git submodule foreach 'git status --porcelain' +``` + +Both branches passed the verification gate. + +## Recommended Next Commands + +Push the original work branch when preserving the full agent history is desired: + +```bash +git push origin trae/agent-inRF11 +``` + +Push the dev-based migration candidate when opening a PR to `dev`: + +```bash +git push origin 260707-refactor-gc-dev-baseline +``` + +Create the `dev` PR from `260707-refactor-gc-dev-baseline` using the content in `/tmp/opencode/gbe-migration-check-20260707/deliverables/dev-migration-pr.md`. + +## Integration Recommendation + +Use `260707-refactor-gc-dev-baseline` for `dev` integration. It is based directly on `origin/dev`, contains the verified two-commit migration chain, and avoids the unrelated-history review problem on `trae/agent-inRF11`. + +## Next Refactor Baseline + +- P0 behavior baseline: `f9d7bc48 fix(gc): harden reconnect lifecycle behavior` +- Baseline record: `b8c022dc docs(gc): record next refactor baseline` +- Implementation plan: `.monkeycode/specs/gc-refactor-next-phase/tasklist.md` +- Baseline details: `.monkeycode/specs/gc-refactor-next-phase/baseline.md` +- Payload helper assertions: 252 passed +- Handler smoke tests: 68 passed +- Replay fixtures: 7 passed +- GC audit checks: 8 passed with 0 issues + +P1 unified direct and wrapped custom-game lifecycle execution behind a shared coordinator executor. + +P3 moved serialized reconnect connection state into `gbe_dota_serialized_connection_state.{h,cpp}`, removed its `` dependency from the shared reconnect header, and added standalone header compile checks. + +P4 added `gbe_dota_reconnect_context.{h,cpp}` as the canonical reconnect source pipeline. Shared, recent, local, and generic recovery inputs now use one eligibility and endpoint-normalization builder, with explicit source priority and rejection reasons. The next implementation stage is P8, which introduces a testable production reconnect network boundary. + +P8 added `gbe_dota_reconnect_network.{h,cpp}` as the shared production/offline reconnect orchestrator and `gbe_dota_reconnect_network_adapter.{h,cpp}` as the Steam boundary for context lookup, `ConnectByIPAddress`, and `GameServerChangeRequested_t` queueing. `PostConnectionStateMsg()` now delegates reconnect decisions and side effects through injected narrow interfaces while each client or gameserver serialized socket instance retains independent reconnect state and generic recovery probe cache. + +P8 verification passed with 156/156 reconnect network assertions, 252/252 payload helper assertions, 68/68 handler smoke tests, 7 replay fixtures, and 8 audit groups with 0 issues. The next implementation stage is P5, which transactionally models lobby lifecycle effects. + +P5 added `gbe_dota_lifecycle_actions.{h,cpp}` and one coordinator-owned lifecycle executor for custom-game transitions, `7034` runtime/member updates, and teardown action lists. The planner now emits a deterministic state/member/phase/runtime/local/shared/details sequence, while direct and wrapped adapters preserve wire parsing, response construction, session routing, and existing fallback behavior. + +P5 properties cover deterministic action fingerprints across 256 effect combinations, dependency ordering for runtime/local/shared/details actions, and empty-effect no-op execution. Executor tests cover conditional member publish, runtime failure fallback, wrapped response routing, cache-unsubscribed routing, and abort/continue push failure policies. + +P5 verification passed with 156/156 reconnect network assertions, 252/252 payload helper assertions, 70/70 handler smoke tests, 7 replay fixtures, and 9 audit groups with 0 issues. The lifecycle ownership audit keeps planners pure and prevents migrated match/wrapped handlers from directly calling the six executor-owned lifecycle side-effect APIs. The next implementation stage is P6 explicit lobby generation. + +P6.1 added the dependency-free `gbe_dota_lobby_generation.h` domain primitive. Generation zero represents an unallocated process-local lifecycle, and create, join, leave, reset, and recovery boundaries each allocate exactly one strictly newer `uint64` generation. Allocation saturates at `UINT64_MAX` and reports failure while preserving the maximum value, preventing wraparound from reusing stale asynchronous generations. + +P6.1 verification passed through the full GC gate with 156/156 reconnect network assertions, 252/252 payload helper assertions, 70/70 handler smoke tests, 7 replay fixtures, and 9 audit groups with 0 issues. P6.2 will carry this generation into local/shared snapshots and reconnect contexts while retaining `lobby_id` as the wire identity. + +P6.2 added an independent generation field to local/shared lobby state, shared reconnect and scalar snapshots, all reconnect source kinds, recent/reconnect contexts, and serialized connection state. Local-to-shared publish, shared-to-local restore, shared/recent/local/generic source mapping, context construction, scalar snapshot reads, partial shared restore, and the production reconnect execution boundary now preserve generation without treating `lobby_id` as the lifecycle token. + +P6.2 keeps the existing lobby-ID-scoped connection and callback deduplication behavior until P6.4. The serialized state stores both identities, allowing P6.3 stale-task checks and P6.4 generation-scoped deduplication to migrate independently. Full verification passed with 159/159 reconnect network assertions, 257/257 payload helper assertions, 70/70 handler smoke tests, 7 replay fixtures, and 9 audit groups with 0 issues. + +P6.3 connected the generation primitive to production lifecycle commits. The coordinator now owns the monotonic counter, advances Create, Join, Leave, Reset, and Recover exactly at their state commit boundaries, preserves the newly allocated generation across clears, and rejects lifecycle creation when the counter is exhausted. + +Delayed runtime lobby updates and their shared-state publish now capture `lobby_id` and generation when queued, then reject stale work before state application or incoming delivery. Postgame abandon, normal-signout, and deferred-reset slots carry the same identity and return explicit current, stale, or empty consume results, preventing an old task from finalizing a newer lifecycle. + +Reconnect `GameServerChangeRequested_t` callbacks now carry generation through the narrow reconnect queue interface. A type-agnostic callsystem execution guard preserves the predicate through immediate registration and late-registration replay, then checks the current reconnect generation at the actual callback dispatch boundary. P6.4 lobby-ID deduplication semantics remain unchanged. + +P6.3 full verification passed with 160/160 reconnect network assertions, 4/4 callsystem guard assertions, 257/257 payload helper assertions, 73/73 handler smoke tests, 7 replay fixtures, and 9 audit groups with 0 issues. The next implementation stage is P6.4 generation-scoped connection and callback deduplication. + +P6.4 added the typed `gbe::dota_connection::DedupKey` with generation, server ID, and endpoint. Serialized reconnect direct-connect and engine-callback deduplication now reset on generation changes, while lobby ID changes within one generation only synchronize protocol identity. Server and endpoint changes remain distinct connection opportunities within the same generation. + +The lobby-flow `GameServerChangeRequested_t` and `GameRichPresenceJoinRequested_t` callback bundle now uses the same typed generation key. Both callbacks retain a generation execution guard through immediate dispatch and late callback registration, and explicit launch-peripheral resets can re-arm the bundle within the current generation. + +P6.4 full verification passed with 165/165 reconnect network assertions, 4/4 callsystem guard assertions, 257/257 payload helper assertions, 73/73 handler smoke tests, 7 replay fixtures, and 9 audit groups with 0 issues. Production Linux build generation remains unavailable in this environment because the bundled Premake binary requires GLIBC 2.38; existing Linux and Windows production build workflows continue to provide the production build gate. The next implementation stage is P6.5 generation lifecycle tests. + +P6.5 added a production-handler lifecycle regression for immediate `Join -> Leave -> Join` reuse of the same wire lobby ID. The test verifies that the three committed boundaries allocate generations 1, 2, and 3, then confirms that a delayed runtime update captured by the first join is rejected without changing the rejoined lobby or entering the incoming queue. + +Together with the existing registered and late-registration callback guard tests, stale postgame and delayed-runtime tests, reset-generation retention test, and same-ID reconnect dedup tests, P6.5 covers the lifecycle matrix required by task 7.5. Full verification passed with 165/165 reconnect network assertions, 4/4 callsystem guard assertions, 257/257 payload helper assertions, 74/74 handler smoke tests, 7 replay fixtures, and 9 audit groups with 0 issues. The next implementation stage is P6.6 generation properties. + +P6.6 added deterministic property coverage for all three generation invariants. P6-A exercises 64 seeded sequences with 32 mixed Create, Join, Leave, Reset, and Recover boundaries per sequence, requiring each successful allocation to advance exactly one strictly newer generation. P6-B queues an old runtime action across 64 same-lobby-ID lifecycle changes and requires the current lobby state, game state, and incoming queue to remain unchanged. P6-C seeds 64 serialized connection states and requires every generation change to clear retry, payload size, posted server, direct-connect, and callback deduplication state while restoring both connection opportunities. + +P6.6 full verification passed with 165/165 reconnect network assertions, 4/4 callsystem guard assertions, 257/257 payload helper assertions, 75/75 handler smoke tests, 7 replay fixtures, and 9 audit groups with 0 issues. The remaining P6 task is the stage checkpoint in 7.7. + +P6 is complete. The stage checkpoint confirms explicit generation allocation at every lifecycle boundary, generation propagation through reconnect and deferred work, stale execution guards at actual dispatch time, generation-scoped connection deduplication, same-wire-ID lifecycle regression coverage, and deterministic properties P6-A through P6-C. The bundled Linux Premake binary still requires GLIBC 2.38, so Linux and Windows CI workflows remain the production build gate for this environment. The next implementation stage is P7 shared lobby state ownership. + +P7.1 added the dependency-free `gbe::dota_lobby_state::Store` contract around `GBE_SharedDotaLobbyState`. The interface returns immutable value snapshots, accepts complete publish values, clears to a zero state, applies mutations to a copied candidate before commit, and rejects stale generation compare/update operations before invoking their mutator. The store receives its state and recursive mutex dependencies explicitly, allowing production to retain the existing synchronization domain while focused tests use isolated fixtures. + +The new focused store suite covers empty snapshots, complete publish, snapshot copy isolation, multi-field update commits, matching generation updates, stale generation preservation, and complete clear. The header compiles independently, shell and Premake source lists include the production implementation, and full verification passed with 165/165 reconnect network assertions, 4/4 callsystem guard assertions, 257/257 payload helper assertions, 75/75 handler smoke tests, 7 replay fixtures, the store suite, and 9 audit groups with 0 issues. The next implementation task is P7.2 binding production shared state and `global_mutex` behind this store. + +P7.2 bound a single production store instance to the existing `GBE_shared_dota_lobby_state` object and `global_mutex`, preserving the established recursive synchronization domain and avoiding a second lock order. `GBE_ClearSharedDotaLobbyState()` now clears through this store, while the accessor is exposed through a forward-declared internal boundary so unrelated GC translation units do not inherit the store's DTO and mutex dependencies. + +The focused store suite now runs four concurrent readers against 2,000 complete version publishes. Each snapshot must carry matching generation, lobby ID, server ID, endpoint, and cache-service version fields, proving readers observe one committed value version. P7.2 full verification passed with 165/165 reconnect network assertions, 4/4 callsystem guard assertions, 257/257 payload helper assertions, 75/75 handler smoke tests, 7 replay fixtures, the concurrent store suite, and 9 audit groups with 0 issues. The next implementation task is P7.3 migrating shared lobby read paths to one snapshot per business operation. + +P7.3 introduced `GBE_GetSharedDotaLobbyStateSnapshot()` as the complete value-snapshot facade over the production store and migrated shared lobby reads to it. Scalar and reconnect snapshot helpers, arcade detection, client/server restore, direct and wrapped SourceTV/watch/spectate responses, joinable custom modes, normal-signout finalization, owner fallback, and coordinator diagnostics now capture one shared snapshot per business operation and derive every field from that version. + +Production direct field access is now limited to the store backing object, diagnostic backing-address logging, and the publish/runtime write paths reserved for P7.4. Offline wrapper seams return their fixture state by value, keeping the same production read contract without duplicating store behavior. P7.3 full verification passed with 165/165 reconnect network assertions, 4/4 callsystem guard assertions, 257/257 payload helper assertions, 75/75 handler smoke tests, 7 replay fixtures, the store suite, and 9 audit groups with 0 issues. The next implementation task is P7.4 migrating shared lobby writes through generation-aware store operations. + +P7.4 migrated production shared lobby writes behind the store. Complete local-to-shared publish preserves the existing client/server merge rules while committing through a monotonic generation operation that accepts the current or a newer generation and rejects an older lifecycle. Generic-lobby metadata connect/server updates, 4508 runtime connect adoption, and gameserver-derived server ID updates now use generation-aware compare/update and emit explicit stale diagnostics when their captured local generation no longer matches the store. + +Clear remains centralized through the store, and production direct field writes to `GBE_shared_dota_lobby_state` are now eliminated. Focused tests cover current/newer complete publish, stale complete publish, matching compare/update, stale compare/update, and a delayed writer attempting to overwrite a replacement generation. P7.4 full verification passed with 165/165 reconnect network assertions, 4/4 callsystem guard assertions, 257/257 payload helper assertions, 75/75 handler smoke tests, 7 replay fixtures, the store suite, and 9 audit groups with 0 issues. The next implementation task is P7.5 removing the mutable production extern surface and adding an audit against direct shared-state access. + +P7.5 removed the mutable shared lobby extern from the production internal header. The backing `GBE_SharedDotaLobbyState` is now a function-local static owned by `GBE_GetSharedDotaLobbyStateStore()`, so production translation units can only obtain value snapshots or invoke store operations. Existing pointer diagnostics retain a stable identity by logging the store instance address. + +Audit 10 scans every Dota GC production translation unit plus `gbe_dota_gc_internal.h` after comment stripping and fails on any use of the retired `GBE_shared_dota_lobby_state` symbol. Focused offline fixtures keep independent mutable DTOs for test setup. P7.5 full verification passed with 165/165 reconnect network assertions, 4/4 callsystem guard assertions, 257/257 payload helper assertions, 75/75 handler smoke tests, 7 replay fixtures, the store suite, and 10 audit groups with 0 issues. The next implementation task is P7.6 completing the named store unit and concurrency coverage matrix. + +P7.6 completed the named store unit and concurrency coverage matrix. Existing tests cover immutable snapshot copies, complete publish and clear, matching and stale generation compare/update, monotonic complete publish, and delayed stale writers. A new five-writer interleaving commits 2,000 matching-generation scalar and vector updates while rejecting 500 stale-generation mutations, proving matching updates are serialized without lost writes and stale mutators cannot affect the endpoint or member list. + +A second interleaving runs four readers across 1,000 alternating complete publishes and clears. Every observed snapshot must be either the complete zero state or one complete published version, and the final publish must remain intact. The focused store binary passed five consecutive runs, and P7.6 full verification passed with 165/165 reconnect network assertions, 4/4 callsystem guard assertions, 257/257 payload helper assertions, 75/75 handler smoke tests, 7 replay fixtures, the store suite, and 10 audit groups with 0 issues. The next implementation task is P7.7 adding deterministic store properties P7-A through P7-C. + +P7.7 added deterministic coverage for all three shared-store properties. P7-A publishes 32 complete scalar, string, and vector versions for each of 64 seeds and requires every returned snapshot to match exactly one published version. P7-B attempts one older-generation compare/update across 64 distinct current states and requires the mutator to remain uncalled while every tracked field stays unchanged. + +P7-C runs the real payload-helper ID facades over the production `Store` implementation in the offline wrapper. For 64 nonzero lobby and generic-lobby ID pairs, the test clears through `GBE_ClearSharedDotaLobbyState()` and requires both valid-gated ID helpers to return zero. Payload-helper assertions increased from 257 to 449. P7.7 full verification passed with 165/165 reconnect network assertions, 4/4 callsystem guard assertions, 449/449 payload helper assertions, 75/75 handler smoke tests, 7 replay fixtures, the store property suite, and 10 audit groups with 0 issues. The remaining P7 task is the stage checkpoint in 8.8. + +P7 is complete. The stage checkpoint confirms one production shared-lobby store in the existing `global_mutex` synchronization domain, immutable value snapshots for all readers, generation-aware writes for asynchronous producers, complete clear semantics, removal and audit protection of the mutable production global surface, concurrent linearization coverage, and deterministic properties P7-A through P7-C. The bundled Linux Premake binary still requires GLIBC 2.38, so Linux and Windows CI workflows remain the production build gate for this environment. The next implementation stage is P9 typed GC handler registry ownership. + +P9.1 defined the dependency-light typed registry contract in `gbe_dota_handler_registry.h`. Each aggregate entry carries a message ID, a strong direct/wrapped mode mask, a wrapped-session policy, a lifecycle classification, a uniform adapter function pointer, and a stable handler identity. Constexpr helpers centralize request-path matching and wrapped-session forwarding semantics without changing the active dispatcher table. + +An independent header compile test proves the contract is self-contained and validates aggregate initialization plus direct, wrapped, unknown-path, session, and handler identity semantics at compile time. Shell and Premake test source lists include the new contract. P9.1 full verification passed with 165/165 reconnect network assertions, 4/4 callsystem guard assertions, 449/449 payload helper assertions, 75/75 handler smoke tests, 7 replay fixtures, the registry header test, and 10 audit groups with 0 issues. The next implementation task is P9.2 migrating the active post-login dispatch mapping to typed registry entries. + +P9.2 migrated all 24 active post-login mappings to typed registry entries. The 16 shared lobby and chat handlers declare `DirectAndWrapped` mode with wrapped-session forwarding, while the 8 standalone misc handlers declare `Direct` mode with session ignored. Lookup now requires both message ID and request-path support, and the selected entry's session policy controls whether the adapter receives the outer session field. + +Adapter bodies, table scan order, successful dispatch logging, and unknown or mode-mismatched fallback remain unchanged. Audit 4 now parses typed entries while continuing to verify every adapter-to-handler call and every direct-only path guard. P9.2 full verification passed with 165/165 reconnect network assertions, 4/4 callsystem guard assertions, 449/449 payload helper assertions, 75/75 handler smoke tests, 7 replay fixtures, all 24 typed dispatch mappings, and 10 audit groups with 0 issues. The next implementation task is P9.3 deriving audit data from the registry and removing the parallel manual dispatch inventory. + +P9.3 made the production typed registry the sole dispatch inventory. Audit 4 parses all six entry fields directly from `kTable`, then resolves each referenced adapter lambda and extracts its actual `GBE_HandleDota*Request` call. The audit derives its entry count from the registry and rejects partially parsed entries, missing or orphaned adapters, adapters that call multiple or zero request handlers, missing direct-only path guards, direct entries that forward wrapped session metadata, unknown handler identities, and unknown lifecycle classes. + +The two manually maintained Python dispatch lists were removed, eliminating the parallel source of truth. P9.3 full verification passed with 165/165 reconnect network assertions, 4/4 callsystem guard assertions, 449/449 payload helper assertions, 75/75 handler smoke tests, 7 replay fixtures, 24 dynamically audited registry entries, and 10 audit groups with 0 issues. The next implementation task is P9.4 associating high-risk registry entries with smoke or replay fixture identifiers. + +P9.4 added a dependency-light fixture identifier to each typed registry entry. All 13 `LobbyMutation` and `LobbyLifecycle` entries now reference a live `smoke:` or `replay::