2V3-b: click on the ground to walk; read the terrain attribute map

CScreen::SetCursorPosition and CPythonGraphic::SetCursorPosition are the 40250
bodies, so RenderGame builds the pick ray under the cursor each frame.
CPythonBackground::GetPickingPoint* march that ray as
CMapOutdoor::__PickTerrainHeight does, against the native CMapManager height
(PR_FLOAT_TO_INT's x87 rounding becomes floor); object picking waits for
CMapOutdoor. CMapManager::isAttrOn reads the packed <map>/<xxxyyy>/attr.atr
as CMapOutdoor::isAttrOn + CTerrain::isAttrOn do, and CheckAdvancing with its
collision checkers is verbatim (no map objects are registered yet). All of
this stays in the pending/platform layer and is marked ADAPTED.

port.login_flow clicks the ground below the actor after the key walk. The
actor walks to the picked point (334 cm offline), and the fake server sees
FUNC_MOVE, then FUNC_WAIT. It also checks that the actor's cell is free and
that its terrain block has blocked cells. port.login_live against the real
server passed (key 133 cm, click 296 cm), and python_game_render_test.sh
passes offline. MT_TEST_MODE=python_ui now adds the 3D surface, so the port
route can be played by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-23 23:57:34 +09:00
co-authored by Claude Opus 5.5
parent ff0983d3c8
commit 8b4700f008
12 changed files with 627 additions and 46 deletions
+1
View File
@@ -525,3 +525,4 @@
{"time":"2026-09-23T12:54:00Z","event":"port_round","unit":"PORT-PLAN 2V2-e.5: native map height and character light transition adapter","ported":[],"adapted":["CMapManager Warp-facing AtlasInfo/Setting.txt/height.raw/.msenv loader and 40250 triangle height interpolation","CPythonBackground native map selection/local coordinates/character and background SetLight","Python 3D surface consumes native map name/local frame, removes initial terrain elevation shift and rebuilds on map change","EterBase/Utils MIN/MAX/MINMAX and fMIN/fMAX/fMINMAX exact 40250 pure functions"],"divergent":["CMapOutdoor and placed-object height, attributes, fog, minimap and portal lifecycle remain unported; native map functions are marked ADAPTED/TODO rather than PORTED"],"needs_live":["MT_LIVE_* credentials absent; real-server terrain and cross-map warp screenshots pending"],"tests":["script/python_game_render_test.sh offline: PASS (4/4 native light0, finite view, native map, actor foot-ground delta 0.1 cm, textured ground screenshot)","extension/tests/port_utils_test.cpp: PASS","port_map.py check: 0 errors"],"todo":["full CMapOutdoor/MapManager/PythonBackground unit port","real-server and cross-map visual smoke","2V3 player/input/event/movement chain"]}
{"time":"2026-09-23T13:09:00Z","event":"test_run","id":"PORT-PLAN 2V2-e.5","result":"PASS","tests":["script/port_gate.sh macos: PASS (12 port tests; port.login_live skipped without MT_LIVE_*)","script/python_game_render_test.sh: PASS (4/4 native light0, finite camera matrix, 0.1 cm foot-ground difference, 559 terrain texture contrasts)","port_map.py check: 0 errors","git diff --check: PASS"],"reason":"The first sandboxed port gate run denied localhost sockets; loopback-enabled rerun passed. Full CMapOutdoor and live/cross-map evidence remain outstanding."}
{"time": "2026-09-23T14:45:00Z", "event": "port_round", "unit": "PORT-PLAN 2V3-a: player/input/event handler chain and Granny root motion", "ported": ["UserInterface/PythonPlayer.cpp", "UserInterface/PythonPlayerInput.cpp", "UserInterface/PythonPlayerInputKeyboard.cpp", "UserInterface/PythonPlayerInputMouse.cpp", "UserInterface/PythonPlayerEventHandler.cpp", "UserInterface/PythonPlayerSkill.cpp", "UserInterface/PythonPlayerModule.cpp", "UserInterface/GameType.cpp"], "adapted": ["GrannyUpdateModelMatrix constant-extraction root motion from TrackGroups[0] AccumulationFlags/LoopTranslation (libgr2)"], "divergent": ["VDA accumulation not implemented", "CPythonSkill/CItemData/CItemManager/CPythonItem callees remain pending stubs"], "needs_live": [], "tests": ["port.login_flow: DIK_UP walked 108 cm; fake server got CG_MOVE FUNC_MOVE then FUNC_WAIT at moved y", "port.login_live 192.168.21.203: PASS, walked 132 cm", "ctest all: PASS (login_live skipped without MT_LIVE_*)", "port_map.py check: 0 errors"], "todo": ["mouse click-to-move and attack", "CheckAdvancing/map attribute stubs", "delete legacy movement when default route switches to port"]}
{"time": "2026-09-23T15:00:00Z", "event": "port_round", "unit": "PORT-PLAN 2V3-b: click-to-move, terrain picking and attribute map", "ported": [], "adapted": ["CScreen::SetCursorPosition / CPythonGraphic::SetCursorPosition verbatim in platform layer", "CPythonBackground::GetPickingPoint* terrain half via CMapOutdoor::__PickTerrainHeight over native height", "CMapManager::isAttrOn via CMapOutdoor/CTerrain::isAttrOn over packed attr.atr", "CPythonBackground::CheckAdvancing + collision checkers verbatim (no map objects registered yet)"], "divergent": ["object picking and object collision wait for CMapOutdoor"], "needs_live": [], "tests": ["port.login_flow: click walked 334 cm, FUNC_MOVE..FUNC_WAIT; 17814 blocked cells", "port.login_live 192.168.21.203: PASS (key 133 cm, click 296 cm, 11406 blocked cells)", "script/python_game_render_test.sh offline: PASS", "ctest all: 19/19 (login_live skipped without env)", "port_map.py check: 0 errors"], "todo": ["switch client_main default to the port route and delete the legacy GDScript client"]}
+151
View File
@@ -0,0 +1,151 @@
{
"reference": "EterLib/GrpScreen.cpp",
"reference_sha256": "3efb287b5acc846f557ade7b828fa76026699a393407d1f0151f5fd5e1dcbebc",
"priority": "P4",
"contracts": [],
"functions": {
"CScreen::RenderLine3d": {
"status": "TODO"
},
"CScreen::RenderBox3d": {
"status": "TODO"
},
"CScreen::RenderBar3d": {
"status": "TODO"
},
"CScreen::RenderGradationBar3d": {
"status": "TODO"
},
"CScreen::RenderLineCube": {
"status": "TODO"
},
"CScreen::RenderCube": {
"status": "TODO"
},
"CScreen::RenderLine2d": {
"status": "TODO"
},
"CScreen::RenderBox2d": {
"status": "TODO"
},
"CScreen::RenderBar2d": {
"status": "TODO"
},
"CScreen::RenderGradationBar2d": {
"status": "TODO"
},
"CScreen::RenderCircle2d": {
"status": "TODO"
},
"CScreen::RenderCircle3d": {
"status": "TODO"
},
"CScreen::RenderD3DXMesh": {
"status": "TODO"
},
"CScreen::RenderSphere": {
"status": "TODO"
},
"CScreen::RenderCylinder": {
"status": "TODO"
},
"CScreen::RenderTextureBox": {
"status": "TODO"
},
"CScreen::RenderBillboard": {
"status": "TODO"
},
"CScreen::DrawMinorGrid": {
"status": "TODO"
},
"CScreen::DrawGrid": {
"status": "TODO"
},
"CScreen::SetCursorPosition": {
"status": "ADAPTED",
"impl": [
"extension/src/platform/EterLib/GrpScreen.cpp:CScreen::SetCursorPosition"
],
"test": "extension/tests/port_login_flow_test.cpp",
"note": "40250 body verbatim in the platform layer (pick ray through the cursor)."
},
"CScreen::GetCursorPosition": {
"status": "TODO"
},
"CScreen::GetCursorXYPosition": {
"status": "TODO"
},
"CScreen::GetCursorZPosition": {
"status": "TODO"
},
"CScreen::GetPickingPosition": {
"status": "TODO"
},
"CScreen::SetDiffuseColor": {
"status": "TODO"
},
"CScreen::SetClearColor": {
"status": "TODO"
},
"CScreen::SetClearDepth": {
"status": "TODO"
},
"CScreen::SetClearStencil": {
"status": "TODO"
},
"CScreen::ClearDepthBuffer": {
"status": "TODO"
},
"CScreen::Clear": {
"status": "TODO"
},
"CScreen::IsLostDevice": {
"status": "TODO"
},
"CScreen::RestoreDevice": {
"status": "TODO"
},
"CScreen::Begin": {
"status": "TODO"
},
"CScreen::End": {
"status": "TODO"
},
"CScreen::Show": {
"status": "TODO"
},
"CScreen::ProjectPosition": {
"status": "TODO"
},
"CScreen::UnprojectPosition": {
"status": "TODO"
},
"CScreen::SetColorOperation": {
"status": "TODO"
},
"CScreen::SetDiffuseOperation": {
"status": "TODO"
},
"CScreen::SetBlendOperation": {
"status": "TODO"
},
"CScreen::SetOneColorOperation": {
"status": "TODO"
},
"CScreen::SetAddColorOperation": {
"status": "TODO"
},
"CScreen::Identity": {
"status": "TODO"
},
"CScreen::CScreen": {
"status": "TODO"
},
"CScreen::~CScreen": {
"status": "TODO"
},
"CScreen::BuildViewFrustum": {
"status": "TODO"
}
}
}
@@ -0,0 +1,85 @@
{
"reference": "EterPythonLib/PythonGraphic.cpp",
"reference_sha256": "82fc01bd616637f92fcbc17e1eb2c7dd8ab78615695dc2aaabe9e5bf2f204632",
"priority": "P4",
"contracts": [],
"functions": {
"CPythonGraphic::Destroy": {
"status": "TODO"
},
"CPythonGraphic::GetD3D": {
"status": "TODO"
},
"CPythonGraphic::GetOrthoDepth": {
"status": "TODO"
},
"CPythonGraphic::SetInterfaceRenderState": {
"status": "TODO"
},
"CPythonGraphic::SetGameRenderState": {
"status": "TODO"
},
"CPythonGraphic::SetCursorPosition": {
"status": "ADAPTED",
"impl": [
"extension/src/platform/EterPythonLib/PythonGraphic.cpp:CPythonGraphic::SetCursorPosition"
],
"test": "extension/tests/port_login_flow_test.cpp",
"note": "40250 body verbatim in the platform layer (pick ray through the cursor)."
},
"CPythonGraphic::SetOmniLight": {
"status": "TODO"
},
"CPythonGraphic::SetViewport": {
"status": "TODO"
},
"CPythonGraphic::RestoreViewport": {
"status": "TODO"
},
"CPythonGraphic::SetGamma": {
"status": "TODO"
},
"GenScreenShotTag": {
"status": "TODO"
},
"CPythonGraphic::SaveJPEG": {
"status": "TODO"
},
"CPythonGraphic::SaveScreenShot": {
"status": "TODO"
},
"CPythonGraphic::PushState": {
"status": "TODO"
},
"CPythonGraphic::PopState": {
"status": "TODO"
},
"CPythonGraphic::RenderImage": {
"status": "TODO"
},
"CPythonGraphic::RenderAlphaImage": {
"status": "TODO"
},
"CPythonGraphic::RenderCoolTimeBox": {
"status": "TODO"
},
"CPythonGraphic::GenerateColor": {
"status": "TODO"
},
"CPythonGraphic::RenderDownButton": {
"status": "TODO"
},
"CPythonGraphic::RenderUpButton": {
"status": "TODO"
},
"CPythonGraphic::GetAvailableMemory": {
"status": "TODO"
},
"CPythonGraphic::CPythonGraphic": {
"status": "TODO"
},
"CPythonGraphic::~CPythonGraphic": {
"status": "TODO"
}
}
}
+42 -13
View File
@@ -12,13 +12,17 @@
},
"CMapManager::CMapManager": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::CMapManager"],
"impl": [
"extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::CMapManager"
],
"note": "Constructor matches 40250; kept in pending file until CMapOutdoor is ported.",
"test": "project/python_game_render_test.gd"
},
"CMapManager::~CMapManager": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::~CMapManager"],
"impl": [
"extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::~CMapManager"
],
"note": "Calls transition adapter Destroy, which frees pack-backed map state.",
"test": "project/python_game_render_test.gd"
},
@@ -30,25 +34,33 @@
},
"CMapManager::Initialize": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::Initialize"],
"impl": [
"extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::Initialize"
],
"note": "40250 AtlasInfo loading order; pack text parsed in transition adapter.",
"test": "project/python_game_render_test.gd"
},
"CMapManager::Create": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::Create"],
"impl": [
"extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::Create"
],
"note": "No CMapOutdoor allocation yet; Metin2World renders terrain.",
"test": "project/python_game_render_test.gd"
},
"CMapManager::Destroy": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::Destroy"],
"impl": [
"extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::Destroy"
],
"note": "Frees transition map and environment data; CMapOutdoor cleanup remains TODO.",
"test": "project/python_game_render_test.gd"
},
"CMapManager::Clear": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::Clear"],
"impl": [
"extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::Clear"
],
"note": "Clears pack-backed terrain state, not CMapOutdoor.",
"test": "project/python_game_render_test.gd"
},
@@ -60,13 +72,17 @@
},
"CMapManager::LoadMap": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::LoadMap"],
"impl": [
"extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::LoadMap"
],
"note": "Loads Setting.txt, initial height tile and .msenv from 40250 packs for reachable Warp chain; outdoor scene lifecycle remains TODO.",
"test": "project/python_game_render_test.gd"
},
"CMapManager::IsMapReady": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::IsMapReady"],
"impl": [
"extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::IsMapReady"
],
"note": "Reports pack-backed transition map readiness instead of CMapOutdoor::IsReady.",
"test": "project/python_game_render_test.gd"
},
@@ -81,7 +97,9 @@
},
"CMapManager::GetHeight": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::GetHeight"],
"impl": [
"extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::GetHeight"
],
"note": "Uses the 40250 terrain triangle interpolation on packed height.raw; placed-object height remains TODO.",
"test": "project/python_game_render_test.gd"
},
@@ -93,13 +111,17 @@
},
"CMapManager::BeginEnvironment": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::BeginEnvironment"],
"impl": [
"extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::BeginEnvironment"
],
"note": "Applies .msenv material/background light through RecordingDevice; fog and full CMapOutdoor environment remain TODO.",
"test": "project/python_game_render_test.gd"
},
"CMapManager::EndEnvironment": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::EndEnvironment"],
"impl": [
"extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::EndEnvironment"
],
"note": "Restores recorded lighting state; full environment state restoration remains TODO.",
"test": "project/python_game_render_test.gd"
},
@@ -158,7 +180,12 @@
"status": "TODO"
},
"CMapManager::isAttrOn": {
"status": "TODO"
"status": "ADAPTED",
"impl": [
"extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::isAttrOn"
],
"test": "extension/tests/port_login_flow_test.cpp",
"note": "CMapOutdoor::isAttrOn + CTerrain::isAttrOn verbatim over native attr.atr tiles (Setting.txt map size instead of the CTerrain list)."
},
"CMapManager::GetAttr": {
"status": "TODO"
@@ -177,7 +204,9 @@
},
"CMapManager::__LoadMapInfoVector": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::__LoadMapInfoVector"],
"impl": [
"extension/src/platform/pending/GameLib/MapManager.cpp:CMapManager::__LoadMapInfoVector"
],
"note": "Reads packed AtlasInfo bounds with 40250 terrain tile size; parser is transitional.",
"test": "project/python_game_render_test.gd"
}
@@ -48,7 +48,9 @@
},
"CPythonBackground::Initialize": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::Initialize"],
"impl": [
"extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::Initialize"
],
"note": "40250 locale AtlasInfo setup, backed by transitional MapManager loader.",
"test": "project/python_game_render_test.gd"
},
@@ -56,26 +58,45 @@
"status": "TODO"
},
"CPythonBackground::GetPickingPoint": {
"status": "TODO"
"status": "ADAPTED",
"impl": [
"extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::GetPickingPoint"
],
"test": "extension/tests/port_login_flow_test.cpp",
"note": "40250 body over the cursor ray; CMapOutdoor::GetPickingPoint forwarded to GetPickingPointWithRay."
},
"CPythonBackground::GetPickingPointWithRay": {
"status": "TODO"
"status": "ADAPTED",
"impl": [
"extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::GetPickingPointWithRay"
],
"test": "extension/tests/port_login_flow_test.cpp",
"note": "Terrain half only: CMapOutdoor object pick (FGetPickingPoint over area objects) needs the unported CMapOutdoor."
},
"CPythonBackground::GetPickingPointWithRayOnlyTerrain": {
"status": "TODO"
"status": "ADAPTED",
"impl": [
"extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::GetPickingPointWithRayOnlyTerrain"
],
"test": "extension/tests/port_login_flow_test.cpp",
"note": "CMapOutdoor::GetPickingPointWithRayOnlyTerrain/__PickTerrainHeight verbatim over the native CMapManager height; PR_FLOAT_TO_INT as floor."
},
"CPythonBackground::GetLightDirection": {
"status": "TODO"
},
"CPythonBackground::Destroy": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::Destroy"],
"impl": [
"extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::Destroy"
],
"note": "Native map state cleanup; SnowEnvironment destruction remains TODO.",
"test": "project/python_game_render_test.gd"
},
"CPythonBackground::Create": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::Create"],
"impl": [
"extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::Create"
],
"note": "Calls transition MapManager::Create; property and snow initialization remain TODO.",
"test": "project/python_game_render_test.gd"
},
@@ -104,7 +125,12 @@
"status": "TODO"
},
"CPythonBackground::CheckAdvancing": {
"status": "TODO"
"status": "ADAPTED",
"impl": [
"extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::CheckAdvancing"
],
"test": "extension/tests/port_login_flow_test.cpp",
"note": "40250 body and collision checkers verbatim, kept in the pending unit; no map objects are registered with CCullingManager until CMapOutdoor is ported."
},
"CPythonBackground::RenderSky": {
"status": "TODO"
@@ -132,25 +158,33 @@
},
"CPythonBackground::SetCharacterDirLight": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::SetCharacterDirLight"],
"impl": [
"extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::SetCharacterDirLight"
],
"note": "Statement-equivalent 40250 function, kept in pending translation unit until the whole unit is ported; reachable RenderGame draws carry native light0.",
"test": "project/python_game_render_test.gd"
},
"CPythonBackground::SetBackgroundDirLight": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::SetBackgroundDirLight"],
"impl": [
"extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::SetBackgroundDirLight"
],
"note": "Statement-equivalent 40250 light setter, still in transitional pending translation unit.",
"test": "project/python_game_render_test.gd"
},
"CPythonBackground::GlobalPositionToLocalPosition": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::GlobalPositionToLocalPosition"],
"impl": [
"extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::GlobalPositionToLocalPosition"
],
"note": "40250 base subtraction, kept in transitional pending translation unit.",
"test": "project/python_game_render_test.gd"
},
"CPythonBackground::LocalPositionToGlobalPosition": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::LocalPositionToGlobalPosition"],
"impl": [
"extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::LocalPositionToGlobalPosition"
],
"note": "40250 base addition, kept in transitional pending translation unit.",
"test": "project/python_game_render_test.gd"
},
@@ -162,7 +196,9 @@
},
"CPythonBackground::Warp": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::Warp"],
"impl": [
"extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::Warp"
],
"note": "Native AtlasInfo map selection and local coordinates reach pack-backed map; minimap, dungeon portals, target effects and full CMapOutdoor lifecycle remain TODO.",
"test": "project/python_game_render_test.gd"
},
@@ -174,7 +210,9 @@
},
"CPythonBackground::GetWarpMapName": {
"status": "ADAPTED",
"impl": ["extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::GetWarpMapName"],
"impl": [
"extension/src/platform/pending/UserInterface/PythonBackground.cpp:CPythonBackground::GetWarpMapName"
],
"note": "Returns current native Warp map name; still in pending translation unit.",
"test": "project/python_game_render_test.gd"
},