client_main: default route switches to port (MT_PORT_PATH)
Without MT_PORT_PATH the app runs 40250 system.py on the Python UI and 3D surfaces; MT_PORT_PATH=legacy keeps the GDScript AppFlow. The smoke/playable live tests stay on legacy, MT_TEST_MODE=python_ui remains a port alias, and an unset switch falls back to legacy (with a warning) when the Python host or the 40250 pack is missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2ead95756e
commit
a49942a1a1
@@ -538,3 +538,4 @@
|
||||
{"time": "2026-09-24T12:00:00Z", "event": "port_round", "unit": "batch 3-a: CPythonSkill + skill module + EterBase/Poly", "ported": ["UserInterface/PythonSkill.cpp", "EterBase/Poly/Base.cpp", "EterBase/Poly/Poly.cpp", "EterBase/Poly/SymTable.cpp", "EterBase/Poly/Symbol.cpp"], "adapted": ["PythonSkill.cpp: icon handle functions (Py_BuildPointer), skillGetGradeData (varargs struct)", "PythonItemModule.cpp: itemGetIconInstance/itemDeleteIconInstance (Py_BuildPointer)"], "divergent": [], "needs_live": [], "tests": ["port.login_flow: skill 1 name/type/cooltime after LoadLocaleData; grade icon + icon instance in game", "port.login_live 192.168.21.203 passes", "ctest 19/19, python_game_render_test PASS", "port_map check 0 errors"], "todo": ["EffectLib", "CMapOutdoor", "default route switch"]}
|
||||
{"time": "2026-09-24T14:00:00Z", "event": "port_round", "unit": "batch 3-b: EffectLib + EterBase/Utils platform bodies", "ported": ["EffectLib/*.cpp (17 units, 251 functions)"], "adapted": ["EffectLib/Type.h: comment line-splice fix", "EterBase/Utils.cpp (platform): 23 functions, 40250 bodies with POSIX swaps", "ScriptLib/Resource.cpp (platform): mde/mdatr resource types"], "divergent": [], "needs_live": [], "tests": ["port.utils: path/split/argv helpers", "port.login_flow: level_up.mse CreateEffect -> alpha-blended levelup_1 particle draws"]}
|
||||
{"time": "2026-09-24T16:00:00Z", "event": "port_round", "unit": "batch 3-c: CMapOutdoor + PythonBackground + minimap", "ported": ["GameLib/{Area,AreaTerrain,DungeonBlock,MapBase,MapManager,MapOutdoor*,MapType,MonsterAreaInfo,Property*,Snow*,Terrain*}.cpp", "PRTerrainLib/{Terrain,TextureSet}.cpp", "UserInterface/PythonBackground(+Module).cpp", "EterLib/ColorTransitionHelper.cpp"], "adapted": ["CMapManager::BeginEnvironment: uintptr_t tag cast", "CTerrainImpl::LoadWaterMap: 32-bit long file layout", "EterBase/FileDir, TempFile (platform)", "MapOutdoorCharacterShadow texture create/release (platform file)"], "divergent": ["HTP/STP terrain render + RenderWater no-op (Godot draws terrain)", "BeginRenderCharacterShadowToTexture returns false"], "n_a": ["GameLib/AreaLoaderThread.cpp (dead in 40250)"], "needs_live": [], "tests": ["port.login_flow: IsMapOutdoor, miniMap atlas loaded, GetCurrentMapName", "python_game_render_test: minimap tiles through filter mask (9)", "port.login_live 192.168.21.203 passes", "ctest 19/19", "port_map check 0 errors"], "todo": ["weapon attach draws on the ground (since P3-b)", "default route switch"]}
|
||||
{"time": "2026-09-24T17:00:00Z", "event": "route_switch", "unit": "client_main default route -> port", "detail": "MT_PORT_PATH unset/port runs python_ui_surface + python_3d_surface (40250 system.py); MT_PORT_PATH=legacy runs AppFlow; smoke/playable test modes stay legacy; unset falls back to legacy when Metin2PythonHost or the 40250 pack is missing", "tests": ["client_main.tscn instantiated per route: default/port -> PythonUISurface+Python3DSurface with UI commands, legacy -> AppFlow+NetTrace"]}
|
||||
|
||||
+7
-4
@@ -97,14 +97,16 @@ extension/third_party/cpython-2.7.18/ # 静态库(2P 批次
|
||||
|
||||
### 迁移方式:始终保持可玩
|
||||
|
||||
旧 GDScript 路径和新的 ported 路径在切片接通前共存,由构建/运行开关选择(例如 `MT_PORT_PATH=legacy|port`,默认
|
||||
`legacy`)。规则:
|
||||
旧 GDScript 路径和新的 ported 路径共存,由运行开关 `MT_PORT_PATH=legacy|port` 选择(`project/client_main.gd`)。
|
||||
2026-09-24 起默认 `port`;未设开关而缺 `Metin2PythonHost` 或 40250 `Client/pack` 时退回 legacy 并告警,显式 `port`
|
||||
则直接报错退出;`MT_TEST_MODE=smoke/playable`(AppFlow 真服测试)固定走 legacy,`MT_TEST_MODE=python_ui` 保留为
|
||||
`port` 的别名。规则:
|
||||
|
||||
- 新单元先编译进 `port_logic`,但**只有被新路径的运行时调用到**,才算实现("运行时不可达不算实现"同样适用于新代码);
|
||||
没有接通前,port-map 状态保持 `TODO`,`note` 写"已照抄,未接通"。
|
||||
- 每个接通步骤的提交同时:切换调用方到新路径、删除被取代的旧逻辑、跑一遍能覆盖该路径的运行测试(离线 FakeClient
|
||||
或真服 e2e)。
|
||||
- 每次接通都同时跑 `legacy` 和 `port` 两套路由的相关回归;共享 extension 的改动不能破坏默认的 legacy 路径。
|
||||
- 每次接通都同时跑 `legacy` 和 `port` 两套路由的相关回归;共享 extension 的改动不能破坏 legacy 路径。
|
||||
- `main` 在任意提交上都能进游戏走动;`legacy` 路径在 2V3 完成、默认值切到 `port` 并稳定后整体删除。
|
||||
|
||||
### 批次 2A:基础
|
||||
@@ -447,8 +449,9 @@ extension/third_party/cpython-2.7.18/ # 静态库(2P 批次
|
||||
批次 3-a CPythonSkill(2026-09-24):`UserInterface/PythonSkill.cpp`(含 skill 模块)与 `EterBase/Poly/`(技能公式解析器)原样移植,删除 pending 替身与 skill 模块桩,`initskill()` 按 40250 顺序在 `initfly()` 之后,实例按 `m_pySkill` 位置进 PythonBoot 的 `GameSingletons`;平台 `PythonApplication.cpp` 里重复的 `SKILL_EFFECT_UPGRADE_ENABLE` 删掉(40250 定义在 PythonSkill.cpp),`LoadLocaleData` 的 skilldesc/skilltable 现在真正载入。改动:技能图标/图标实例句柄用 `Py_BuildPointer`/`PyTuple_GetPointer`(item 模块的 `GetIconInstance/DeleteIconInstance` 同改),`skillGetGradeData` 把非平凡结构传进 varargs(clang 拒绝),改为显式读同样的前 4 字节(脚本不调用)。`port.login_flow`:选角阶段 skill 1 = Three-Way Cut、ACTIVE、冷却 12;进游戏后主动技能只有分等级图标(`GetIconImage` 为 0,与 40250 相同),`GetIconImageNew/GetIconInstanceNew` 可用;真服同样通过。
|
||||
批次 3-b EffectLib(2026-09-24):`EffectLib/` 18 个 .cpp 原样移植(EffectManager、EffectData/Instance、EffectMesh、粒子系统、SimpleLight 等),删除 `platform/EffectLib` 生成的桩骨架;唯一改动是 `Type.h` 里 `BILLBOARD_TYPE_2FACE` 行尾注释的 `\` 被 clang 当作续行吞掉下一行,去掉反斜杠。`platform/EterBase/Utils.cpp` 剩下的 21 个桩按 §3 照抄 40250 函数体(`GetOnlyPathName`/`IsGlobalFileName` 缺失会让 .msf 以相对名挂的 .mse 丢目录,106 个 RegisterEffect 报错),Win32 调用换成 POSIX 的记 ADAPTED;平台 `CPythonResource` 补上 40250 的 `mde`(CEffectMesh)和 `mdatr`(CAttributeData)资源类型。`port.utils` 覆盖路径/分词/命令行辅助函数;`port.login_flow`:在主角脚下 `CreateEffect(level_up.mse)`,RenderGame 的 `CEffectManager::Render` 录出 levelup_1 贴图的 alpha 混合粒子绘制(dust、dropitem 同样出现)。40250 声明但未定义 `GetRenderingEffectCount`,测试不用。
|
||||
批次 3-c CMapOutdoor(2026-09-24):`GameLib/` 的 Area、AreaTerrain、DungeonBlock、MapBase、MapManager、MapOutdoor(含 IndexBuffer/Load/Quadtree/Render/Update)、MapType、MonsterAreaInfo、Property/PropertyLoader/PropertyManager、SnowEnvironment/SnowParticle、TerrainDecal/Patch/Quadtree,`PRTerrainLib/` Terrain、TextureSet,`UserInterface/PythonBackground(+Module)`,`EterLib/ColorTransitionHelper` 原样移植,删除 pending 替身与 background 模块桩(含 GameplayModuleConstants 里的常量桩);`EterBase/FileDir`、`TempFile` 在 platform 下照抄 40250 函数体(Win32 查找/临时文件走 common 垫片,记 ADAPTED)。`TEMP_CAreaLoaderThread` 在 40250 已全部注释掉,.cpp 不编(N_A)。手改两处:`CMapManager::BeginEnvironment` 的 `ApplyLight` 标签指针经 `uintptr_t` 转 DWORD;`CTerrainImpl::LoadWaterMap` 按 32 位读 water.wtr 的 `long` 再逐个扩宽(LP64 下 `long` 是 8 字节,否则报 FILE DATA SIZE 16392 != 16400)。平台文件:HTP/STP 地形绘制与 `RenderWater` 为空(地形仍由 Godot 适配层画,§3),STP 的 Initialize/Create/Destroy、水面贴图载入、角色阴影贴图创建照抄;`BeginRenderCharacterShadowToTexture` 返回 false(记 DIVERGENT)。录制设备补齐 `CreateTexture/VertexBuffer/IndexBuffer/DepthStencilSurface`、`Get/SetRenderTarget`、`LightEnable`、`Clear`(CPU 表面/贴图),离屏渲染目标的绘制丢弃,light0 只在启用且为方向光时记录。小地图:`CPythonMiniMap` 在正交投影下画 minimap.dds 瓦片并以 stage 1 相机空间坐标 × `D3DTS_TEXTURE1` 采样 `minimap_image_filter.dds` 做圆形遮罩——录制设备把「正交投影 + 未变换顶点」的绘制按四边形转成 UI 命令(带 mask 贴图与四角 mask uv),`UIRenderAddImage` 按 D3DTS_WORLD 平移、按 TFACTOR 调色(标记点),`python_ui_surface.gd` 用按序的子 canvas item 画 UI 流,带遮罩的图各用一个 shader 材质。`port.login_flow`:Warp 后 `IsMapOutdoor()`、`miniMap.GetAtlasSize()` 已载入、`background.GetCurrentMapName()` 非空;`python_game_render_test` 新增「minimap tiles drawn through the filter mask」(9 块)。已知未修:P3-b 起主角武器画在地上(挂点未跟骨骼),另行处理。
|
||||
默认路由切换(2026-09-24):`client_main` 不设 `MT_PORT_PATH` 时走 port 路由(`python_ui_surface.gd` + `python_3d_surface.gd` 跑 40250 system.py),`MT_PORT_PATH=legacy` 回到 GDScript `AppFlow`,规则见 §2「迁移方式」。已验证:实例化 `client_main.tscn`,默认/`port` 起 PythonUISurface + Python3DSurface 并产出 UI 命令,`legacy` 起 AppFlow + NetTrace。下一步:port 路由稳定后删除 legacy 宿主与开关;修武器挂点。
|
||||
|
||||
每个切片都必须同时跑 legacy/port 双路回归;只有 2V3 稳定并把默认路由切到 `port` 后,才能删除总开关和剩余 legacy 宿主。
|
||||
每个切片都必须同时跑 legacy/port 双路回归;默认路由已切到 `port`(2026-09-24);port 路由稳定后删除总开关和剩余 legacy 宿主。
|
||||
|
||||
## 5. 数据源切换:m2dev assets → 40250
|
||||
|
||||
|
||||
+43
-19
@@ -1,6 +1,9 @@
|
||||
# client_main —— 打包客户端的入口场景根(main_scene = res://client_main.tscn)。
|
||||
#
|
||||
# 只做一件事:起 AppFlow(登录 → 选人 → 进游戏 串场控制器)。
|
||||
# 默认走 port 路由(PORT-PLAN §2 总开关):40250 system.py 在内嵌解释器里跑,界面画在
|
||||
# python_ui_surface.gd、RenderGame 的 3D 绘制画在 python_3d_surface.gd。
|
||||
# MT_PORT_PATH=legacy 回到旧的 GDScript AppFlow(登录 → 选人 → 进游戏 串场控制器);
|
||||
# MT_TEST_MODE=smoke/playable 是 AppFlow 的真服测试,固定走 legacy。
|
||||
# 资源目录由 AssetRoot 决定:默认 res://../assets(开发树),打包后设环境变量
|
||||
# MT_ASSETS 指向真实资源目录即可(见 project/asset_root.gd)。
|
||||
extends Node
|
||||
@@ -35,29 +38,50 @@ func _ready() -> void:
|
||||
printerr("%d check(s) failed" % fail)
|
||||
get_tree().quit(1 if fail else 0)
|
||||
return
|
||||
# MT_TEST_MODE=python_ui:40250 system.py 的界面直接画在 Godot Canvas 上(PORT-PLAN 2V0-f),
|
||||
# Logo → 登录 → 选人 → 进游戏,RenderGame 的 3D 绘制由 python_3d_surface.gd 画出(2V2-e),
|
||||
# 方向键/鼠标点地走动走 40250 原路径(2V3);app.Exit() 退出进程。
|
||||
if OS.get_environment("MT_TEST_MODE") == "python_ui":
|
||||
AssetPack.ensure()
|
||||
var surface: Control = preload("res://python_ui_surface.gd").new()
|
||||
surface.name = "PythonUISurface"
|
||||
add_child(surface)
|
||||
# The UI surface runs CPythonApplication::Process in its _process; the 3D surface reads that
|
||||
# frame's draws after it, so it comes second in the tree.
|
||||
var world: Node3D = preload("res://python_3d_surface.gd").new()
|
||||
world.name = "Python3DSurface"
|
||||
add_child(world)
|
||||
var err: String = surface.run_app()
|
||||
if err != "":
|
||||
printerr("python_ui: " + err)
|
||||
get_tree().quit(1)
|
||||
return
|
||||
if OS.get_environment("MT_TEST_MODE") == "forest_render":
|
||||
var forest := ForestMapRenderTest.new()
|
||||
forest.name = "ForestMapRenderTest"
|
||||
add_child(forest)
|
||||
return
|
||||
# port 路由:40250 system.py 的界面直接画在 Godot Canvas 上(PORT-PLAN 2V0-f),
|
||||
# Logo → 登录 → 选人 → 进游戏,RenderGame 的 3D 绘制由 python_3d_surface.gd 画出(2V2-e),
|
||||
# 方向键/鼠标点地走动走 40250 原路径(2V3);app.Exit() 退出进程。
|
||||
# MT_TEST_MODE=python_ui 是切换默认值之前的入口,保留为 MT_PORT_PATH=port 的别名。
|
||||
var mode := OS.get_environment("MT_TEST_MODE")
|
||||
var route := OS.get_environment("MT_PORT_PATH")
|
||||
if mode == "python_ui":
|
||||
route = "port"
|
||||
elif mode == "smoke" or mode == "playable":
|
||||
route = "legacy"
|
||||
if route != "legacy":
|
||||
if route != "" and route != "port":
|
||||
push_warning("[client] 未知的 MT_PORT_PATH=%s,按 port 处理" % route)
|
||||
if _start_port_route(route != ""):
|
||||
return
|
||||
_start_legacy_route()
|
||||
|
||||
# Returns false when the port route cannot start and the default (unset MT_PORT_PATH) should fall
|
||||
# back to legacy; an explicit MT_PORT_PATH=port quits with the error instead.
|
||||
func _start_port_route(explicit: bool) -> bool:
|
||||
AssetPack.ensure()
|
||||
if not explicit and not (ClassDB.class_exists("Metin2PythonHost") and AssetRoot.pack_ready()):
|
||||
push_warning("[client] port 路由缺 Metin2PythonHost 或 40250 Client/pack(%s),退回 legacy AppFlow" % AssetRoot.client_path())
|
||||
return false
|
||||
var surface: Control = preload("res://python_ui_surface.gd").new()
|
||||
surface.name = "PythonUISurface"
|
||||
add_child(surface)
|
||||
# The UI surface runs CPythonApplication::Process in its _process; the 3D surface reads that
|
||||
# frame's draws after it, so it comes second in the tree.
|
||||
var world: Node3D = preload("res://python_3d_surface.gd").new()
|
||||
world.name = "Python3DSurface"
|
||||
add_child(world)
|
||||
var err: String = surface.run_app()
|
||||
if err != "":
|
||||
printerr("port: " + err)
|
||||
get_tree().quit(1)
|
||||
return true
|
||||
|
||||
func _start_legacy_route() -> void:
|
||||
# 打包 app 从 Finder/open 启动时没有 shell 环境;40250 服务器使用 classic
|
||||
# DH2/CTR 协议,因此默认固定到 classic,同时保留 MT_PROTOCOL 覆盖能力。
|
||||
if OS.get_environment("MT_PROTOCOL").is_empty():
|
||||
|
||||
Reference in New Issue
Block a user