From dda885a17aa8efc4c62ef40945d8898a934eaf31 Mon Sep 17 00:00:00 2001 From: shenlei Date: Mon, 28 Sep 2026 14:22:41 +0900 Subject: [PATCH] render parity unit J: 40250 automatic sight range in CPythonApplication::Process Process now runs 40250's render-time / face-count block and sets view distance set 0 each frame (25600 under 5000 faces, else scaled by render time, or the forced range). SetForceSightRange and SkipRenderBuffering are ported from stubs; the invented SetCursorPosition in UpdateGame is removed. Port-map boilerplate notes replaced; Destroy recorded as DIVERGENT. Co-Authored-By: Claude Opus 5.5 --- audit/history.jsonl | 1 + .../UserInterface/PythonApplication.cpp.json | 27 +++-- .../UserInterface/PythonApplication.cpp | 106 +++++++++++++++++- 3 files changed, 115 insertions(+), 19 deletions(-) diff --git a/audit/history.jsonl b/audit/history.jsonl index 7d4a92ce..9241465e 100644 --- a/audit/history.jsonl +++ b/audit/history.jsonl @@ -567,3 +567,4 @@ {"date": "2026-09-28T04:56:02+00:00", "unit": "GameLib/MapOutdoorRenderHTP.cpp", "action": "unit H1: platform rewrite replaced by the full 40250 source (native-terrain gate only); drops the invented texture-0 fallback splat, restores first-splat stage-1 ALPHAOP DISABLE, GetNumTextures() loop bound, shadow-pass FOGCOLOR white/restore, stage-0 ALPHAOP DISABLE + CLAMP, TEXTURE1 = m_matDynamicShadow, stage-1 SELECTARG1 restore, TEXTURE0 1/640 tiling", "evidence": "ctest (see commit); fake-server frame 900: white far band gone, terrain matches STP-path layout (mean abs diff vs STP ~20/255 from per-texture m_matTransform vs STP's fixed 1/640, as in 40250); HTP pixels NEEDS_LIVE"} {"date": "2026-09-28T04:59:25+00:00", "unit": "GameLib/MapOutdoorWater.cpp", "action": "unit H2: platform rewrite replaced by the full 40250 source (native-terrain gate only): far-fog second water pass restored, invented LIGHTING=FALSE save/restore and empty-texture guard removed", "evidence": "build ok; fake-server spawn frame 900 unchanged (mean abs diff ~0.4/255, no water in view); water visuals NEEDS_LIVE"} {"date": "2026-09-28T05:08:46+00:00", "unit": "EterPythonLib/PythonGraphic.cpp", "action": "unit I: SetGamma, RenderCoolTimeBox, RenderUp/DownButton, GetAvailableMemory ported from 40250 (stubs removed); cooldown fan drawn as polygon UI commands; SetViewport/RestoreViewport documented as ADAPTED (device viewport + UI clip)", "evidence": "port.app_loop drawing_primitives checks fan geometry at fTime 0.25/0.3/1.0 and viewport clip push/pop; cooldown visuals NEEDS_LIVE"} +{"date": "2026-09-28T05:22:26+00:00", "unit": "UserInterface/PythonApplication.cpp", "action": "unit J: Process gains 40250's render-time/face-count block and automatic sight range (SetViewDistanceSet(0)); SetForceSightRange/SkipRenderBuffering ported from stubs; invented SetCursorPosition in UpdateGame removed; boilerplate notes on RenderGame/UpdateGame/Process/Destroy replaced (Destroy marked DIVERGENT: never called, teardown via PythonBoot::Stop)", "evidence": "ctest -j3 35/35; fake-server spawn frame 900 mean abs diff 0.33/255 vs previous (fog range not in view); sight-range visuals NEEDS_LIVE"} diff --git a/audit/port-map/UserInterface/PythonApplication.cpp.json b/audit/port-map/UserInterface/PythonApplication.cpp.json index 9595025e..1ff849da 100644 --- a/audit/port-map/UserInterface/PythonApplication.cpp.json +++ b/audit/port-map/UserInterface/PythonApplication.cpp.json @@ -25,8 +25,8 @@ "impl": [ "extension/src/platform/UserInterface/PythonApplication.cpp:CPythonApplication::SetMinFog" ], - "note": "40250 CPythonApplication lifecycle and state adapted for platform host and singletons", - "test": "extension/tests/port_login_flow_test.cpp" + "test": "extension/tests/port_login_flow_test.cpp", + "note": "40250 body verbatim in the platform layer." }, "CPythonApplication::SetFrameSkip": { "status": "ADAPTED", @@ -73,32 +73,32 @@ "impl": [ "extension/src/platform/UserInterface/PythonApplication.cpp:CPythonApplication::RenderGame" ], - "note": "40250 CPythonApplication lifecycle and state adapted for platform host and singletons", - "test": "extension/tests/port_login_flow_test.cpp" + "test": "extension/tests/port_login_flow_test.cpp", + "note": "40250's !PERF_CHECKER_RENDER_GAME branch line for line, members reached through their singletons; the perf-timing branch (PERF_CHECKER_RENDER_GAME is FALSE) is left out." }, "CPythonApplication::UpdateGame": { "status": "ADAPTED", "impl": [ "extension/src/platform/UserInterface/PythonApplication.cpp:CPythonApplication::UpdateGame" ], - "note": "40250 CPythonApplication lifecycle and state adapted for platform host and singletons", - "test": "extension/tests/port_login_flow_test.cpp" + "test": "extension/tests/port_login_flow_test.cpp", + "note": "40250 body through the singletons, minus the __PERFORMANCE_CHECK__/PERF_CHECKER timing. An invented CScreen::SetCursorPosition in the perspective block was removed (unit J): as in 40250 the pick ray comes from RenderGame's SetCursorPosition." }, "CPythonApplication::SkipRenderBuffering": { "status": "ADAPTED", "impl": [ "extension/src/platform/UserInterface/PythonApplication.cpp:CPythonApplication::SkipRenderBuffering" ], - "note": "40250 CPythonApplication lifecycle and state adapted for platform host and singletons", - "test": "extension/tests/port_login_flow_test.cpp" + "test": "extension/tests/port_login_flow_test.cpp", + "note": "40250 body verbatim in the platform layer." }, "CPythonApplication::Process": { "status": "ADAPTED", "impl": [ "extension/src/platform/UserInterface/PythonApplication.cpp:CPythonApplication::Process" ], - "note": "40250 CPythonApplication lifecycle and state adapted for platform host and singletons", - "test": "extension/tests/port_login_flow_test.cpp" + "test": "extension/tests/port_login_flow_test.cpp", + "note": "Frame order as 40250 (network, account connector, camera, resource, mouse/UI update, specular, culling, Begin/UI/mouse render/End). Left out: guild mark up/download (not ported), lost-device restore, ClearDepthBuffer/Show (host presents), frame-skip timing/Sleep (host paces frames), FPS counters, and the !IsActive() SkipRenderBuffering(3000) (CMSWindow::IsActive is a stub). Unit J restored 40250:741-842: render time, face count, frame buffering average and the automatic sight range (SetViewDistanceSet(0, ...): 25600 under 5000 faces, else MIN_FOG..25600 scaled by render time, or the forced range), with a PORT guard on the face-speed division. Fog/far-clip effect NEEDS_LIVE against the reference frame." }, "CPythonApplication::UpdateClientRect": { "status": "ADAPTED", @@ -285,7 +285,7 @@ "impl": [ "extension/src/platform/UserInterface/PythonApplication.cpp:CPythonApplication::SetForceSightRange" ], - "note": "40250 CPythonApplication lifecycle and state adapted for platform host and singletons", + "note": "40250 body verbatim in the platform layer. m_iForceSightRange lives in the file-static s_iForceSightRange (initial -1 as the 40250 constructor sets) until the application object exists.", "test": "extension/tests/port_login_flow_test.cpp" }, "CPythonApplication::Clear": { @@ -297,12 +297,11 @@ "test": "extension/tests/port_login_flow_test.cpp" }, "CPythonApplication::Destroy": { - "status": "ADAPTED", + "status": "DIVERGENT", "impl": [ "extension/src/platform/UserInterface/PythonApplication.cpp:CPythonApplication::Destroy" ], - "note": "40250 CPythonApplication lifecycle and state adapted for platform host and singletons", - "test": "extension/tests/port_login_flow_test.cpp" + "note": "Empty and never called: the host tears down through the singleton resets in PythonBoot::Stop, not 40250 Main's app->Destroy(). 40250's body (window manager, SaveConfig/SaveInterfaceStatus writing into the client dir, managers incl. m_LightManager.Destroy(), device) is not run; porting it needs Stop to call it with config writes redirected out of the 40250 client dir." } } } diff --git a/extension/src/platform/UserInterface/PythonApplication.cpp b/extension/src/platform/UserInterface/PythonApplication.cpp index 1fc42de9..85fa466c 100644 --- a/extension/src/platform/UserInterface/PythonApplication.cpp +++ b/extension/src/platform/UserInterface/PythonApplication.cpp @@ -70,6 +70,8 @@ float CPythonApplication::GetGlobalTime() { return CTimer::Instance().GetCurrent // PORT: read straight from the timer, as GetGlobalTime above. float CPythonApplication::GetGlobalElapsedTime() { return CTimer::Instance().GetElapsedSecond(); } static bool s_isFrameSkipDisable = false; +// m_iForceSightRange (the 40250 constructor sets -1: automatic sight range). +static int s_iForceSightRange = -1; void CPythonApplication::SetFrameSkip(bool isEnable) { s_isFrameSkipDisable = !isEnable; } // PORT: 40250 creates the Win32 window, the D3D8 device, cursors, sound, keyboard, the net device, // fonts and the IME here. The Godot window and its canvas already exist when the host starts the @@ -261,7 +263,6 @@ void CPythonApplication::UpdateGame() s.SetPerspective(30.0f,fAspect, 100.0f, fFarClip); s.BuildViewFrustum(); - s.SetCursorPosition(ptMouse.x, ptMouse.y, UI::CWindowManager::Instance().GetScreenWidth(), UI::CWindowManager::Instance().GetScreenHeight()); } TPixelPosition kPPosMainActor; @@ -315,8 +316,11 @@ bool CPythonApplication::Process() if (IsNativeTerrainRenderEnabled()) CGrannyMaterial::TranslateSpecularMatrix(g_specularSpd, g_specularSpd, 0.0f); - // PORT: the render block without the lost-device restore, ClearDepthBuffer, Show and the render-time - // statistics (Godot clears and presents the frame). The frame's UI and 3D command lists restart here. + // PORT: the render block without the lost-device restore, ClearDepthBuffer and Show (Godot clears + // and presents the frame), and without the !IsActive() SkipRenderBuffering(3000): the host has no + // Win32 activation (CMSWindow::IsActive is a stub). The frame's UI and 3D command lists restart here. + // 40250 PythonApplication.cpp:681. + DWORD dwRenderStartTime = ELTimer_GetMSec(); CCullingManager::Instance().Update(); UIRenderBeginFrame(); Render3DBeginFrame(); @@ -336,6 +340,93 @@ bool CPythonApplication::Process() } rkGraphic.End(); + + // 40250 PythonApplication.cpp:741-842 (render time, face count, automatic sight range), minus the + // per-second FPS/face counters and the windowed dwBufRenderTime rounding, whose result 40250 never + // uses (its Sleep is commented out). + DWORD dwRenderEndTime = ELTimer_GetMSec(); + + static DWORD s_dwRenderCheckTime = dwRenderEndTime; + static DWORD s_dwRenderRangeTime = 0; + static DWORD s_dwRenderRangeFrame = 0; + + m_dwCurRenderTime = dwRenderEndTime - dwRenderStartTime; + s_dwRenderRangeTime += m_dwCurRenderTime; + ++s_dwRenderRangeFrame; + + if (dwRenderEndTime-s_dwRenderCheckTime>1000) + { + m_fAveRenderTime=float(double(s_dwRenderRangeTime)/double(s_dwRenderRangeFrame)); + + s_dwRenderCheckTime=ELTimer_GetMSec(); + s_dwRenderRangeTime=0; + s_dwRenderRangeFrame=0; + } + + DWORD dwCurFaceCount=rkGraphic.GetFaceCount(); + rkGraphic.ResetFaceCount(); + + if (dwCurFaceCount > 5000) + { + // 프레임 완충 처리 + if (dwRenderEndTime > m_dwBufSleepSkipTime) + { + static float s_fBufRenderTime = 0.0f; + + float fCurRenderTime = m_dwCurRenderTime; + + if (fCurRenderTime > s_fBufRenderTime) + { + float fRatio = fMAX(0.5f, (fCurRenderTime - s_fBufRenderTime) / 30.0f); + s_fBufRenderTime = (s_fBufRenderTime * (100.0f - fRatio) + (fCurRenderTime + 5) * fRatio) / 100.0f; + } + else + { + float fRatio = 0.5f; + s_fBufRenderTime = (s_fBufRenderTime * (100.0f - fRatio) + fCurRenderTime * fRatio) / 100.0f; + } + + // 한계치를 정한다 + if (s_fBufRenderTime > 100.0f) + s_fBufRenderTime = 100.0f; + + m_fAveRenderTime=s_fBufRenderTime; + } + + m_dwFaceAccCount += dwCurFaceCount; + m_dwFaceAccTime += m_dwCurRenderTime; + + // PORT: recording a frame can take under 1 ms, so the integer division is guarded (40250's + // D3D frame never took 0 ms). + if (m_dwFaceAccTime) + m_fFaceSpd=(m_dwFaceAccCount/m_dwFaceAccTime); + + // 거리 자동 조절 + if (-1 == s_iForceSightRange) + { + static float s_fAveRenderTime = 16.0f; + float fRatio=0.3f; + s_fAveRenderTime=(s_fAveRenderTime*(100.0f-fRatio)+max(16.0f, m_dwCurRenderTime)*fRatio)/100.0f; + + + float fFar=25600.0f; + float fNear=MIN_FOG; + double dbAvePow=double(1000.0f/s_fAveRenderTime); + double dbMaxPow=60.0; + float fDistance=max(fNear+(fFar-fNear)*(dbAvePow)/dbMaxPow, fNear); + CPythonBackground::Instance().SetViewDistanceSet(0, fDistance); + } + // 거리 강제 설정시 + else + { + CPythonBackground::Instance().SetViewDistanceSet(0, float(s_iForceSightRange)); + } + } + else + { + // 10000 폴리곤 보다 적을때는 가장 멀리 보이게 한다 + CPythonBackground::Instance().SetViewDistanceSet(0, 25600.0f); + } } return true; } @@ -483,7 +574,11 @@ time_t CPythonApplication::GetServerTime() { return GetPlatformServerTime(); } time_t CPythonApplication::GetServerTimeStamp() { return GetPlatformServerTimeStamp(); } void CPythonApplication::SetConnectData(const char*, int) { MT_PLATFORM_STUB(); } void CPythonApplication::GetConnectData(std::string& ip, int& port) { MT_PLATFORM_STUB(); ip.clear(); port = 0; } -void CPythonApplication::SetForceSightRange(int) { MT_PLATFORM_STUB(); } +// 40250 PythonApplication.cpp:1377. +void CPythonApplication::SetForceSightRange(int iRange) +{ + s_iForceSightRange = iRange; +} int CPythonApplication::CheckDeviceState() { @@ -506,9 +601,10 @@ void CPythonApplication::NotifyHack(const char* c_szFormat, ...) CPythonNetworkStream::Instance().NotifyHack(szBuf); } +// 40250 PythonApplication.cpp:394 void CPythonApplication::SkipRenderBuffering(DWORD dwSleepMSec) { - MT_PLATFORM_STUB(); + m_dwBufSleepSkipTime=ELTimer_GetMSec()+dwSleepMSec; } void CPythonApplication::UpdateClientRect()