diff --git a/audit/history.jsonl b/audit/history.jsonl index 3a81719c..986925b8 100644 --- a/audit/history.jsonl +++ b/audit/history.jsonl @@ -560,3 +560,4 @@ {"date": "2026-09-28T03:54:13+00:00", "unit": "EterLib/GrpScreen.cpp + native D3D8 fixed-function emulation (RecordingDevice / native_render)", "action": "unit A: D3D8 texcoord generation + D3DTS_TEXTUREn in shader (CPU uv baking removed), 8-light D3D lighting (no attenuation clamp, spot theta/2 phi/2, emissive source, NORMALIZENORMALS, inverse-transpose normals), 8-bit alpha test, clears in draw order, dynamic D3D viewport + MinZ/MaxZ, black back-buffer clear, white default diffuse, alpha blend mirrors colour factors; removed sphere-map and multiplicative-shadow hacks; CScreen ms_clearDepth/ms_diffuseColor initialisers fixed to 40250 values; capture v8; --screenshot-out readback", "evidence": "ctest 35/35 (login_live excluded); mt_native_render fake-server 240/900-frame screenshots vs baseline: game frame mean abs diff 0.55/255, char select model now placed by introselect.py grp.SetViewport(270/w,...); Windows reference frame pending (NEEDS_LIVE)"} {"date": "2026-09-28T03:59:30+00:00", "unit": "EterLib/GrpLightManager.cpp", "action": "unit B: platform stub removed, 40250 GrpLightManager.cpp copied verbatim into port/ (port_copy.py); CPythonApplication::Create now calls m_LightManager.Initialize() (40250 :1263) so light IDs start at skip index 1; 40250 never calls FlushLight/RestoreLight, so no device-light change", "evidence": "ctest 35/35 (login_live excluded); fake-server frame 900 vs baseline mean abs diff 0.21/255"} {"date": "2026-09-28T04:02:30+00:00", "unit": "EterLib/LensFlare.cpp + ScreenFilter.cpp + Decal.cpp", "action": "unit C (part 1): platform stubs removed, 40250 sources copied verbatim (GL code is commented out in 40250 too); CMapOutdoor callers were already ported, so lens flare, screen filter and CDecal clipping (TerrainDecal) now run", "evidence": "ctest 35/35 (login_live excluded); fake-server frame 900 vs unit B mean abs diff 0.014/255; flare/filter visuals NEEDS_LIVE"} +{"date": "2026-09-28T04:05:55+00:00", "unit": "EterLib/GrpShadowTexture.cpp + character shadow render target (RecordingDevice)", "action": "unit C part 2 / unit D: GrpShadowTexture stub replaced with verbatim 40250 copy (dead in 40250); CPU shadow-target rasterizer kept (flat TFACTOR pass, output-equivalent) and switched to the D3D top-left fill rule; Vulkan offscreen targets not needed since snow blur is hard-disabled in 40250", "evidence": "ctest 35/35 (login_live excluded); fake-server frame 900 vs previous mean abs diff 0.013/255 (frame noise); shadow parity NEEDS_LIVE"} diff --git a/audit/port-map/EterLib/GrpShadowTexture.cpp.json b/audit/port-map/EterLib/GrpShadowTexture.cpp.json new file mode 100644 index 00000000..868d0e4e --- /dev/null +++ b/audit/port-map/EterLib/GrpShadowTexture.cpp.json @@ -0,0 +1,78 @@ +{ + "reference": "EterLib/GrpShadowTexture.cpp", + "reference_sha256": "0b538a6e4a772ae16cbbf9095c656edd403bd01e60802b6eaa66df352e9507a6", + "priority": "P4", + "contracts": [], + "functions": { + "CGraphicShadowTexture::Destroy": { + "status": "PORTED", + "impl": [ + "extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::Destroy" + ], + "note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)" + }, + "CGraphicShadowTexture::Create": { + "status": "PORTED", + "impl": [ + "extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::Create" + ], + "note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)" + }, + "CGraphicShadowTexture::Set": { + "status": "PORTED", + "impl": [ + "extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::Set" + ], + "note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)" + }, + "CGraphicShadowTexture::GetLightVPMatrixReference": { + "status": "PORTED", + "impl": [ + "extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::GetLightVPMatrixReference" + ], + "note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)" + }, + "CGraphicShadowTexture::GetD3DTexture": { + "status": "PORTED", + "impl": [ + "extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::GetD3DTexture" + ], + "note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)" + }, + "CGraphicShadowTexture::Begin": { + "status": "PORTED", + "impl": [ + "extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::Begin" + ], + "note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)" + }, + "CGraphicShadowTexture::End": { + "status": "PORTED", + "impl": [ + "extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::End" + ], + "note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)" + }, + "CGraphicShadowTexture::Initialize": { + "status": "PORTED", + "impl": [ + "extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::Initialize" + ], + "note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)" + }, + "CGraphicShadowTexture::CGraphicShadowTexture": { + "status": "PORTED", + "impl": [ + "extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::CGraphicShadowTexture" + ], + "note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)" + }, + "CGraphicShadowTexture::~CGraphicShadowTexture": { + "status": "PORTED", + "impl": [ + "extension/src/port/EterLib/GrpShadowTexture.cpp:CGraphicShadowTexture::~CGraphicShadowTexture" + ], + "note": "verbatim; 40250 never instantiates CGraphicShadowTexture (CGraphicThingInstance::ProjectShadow is declared but undefined)" + } + } +} diff --git a/audit/port-map/GameLib/MapOutdoorCharacterShadow.cpp.json b/audit/port-map/GameLib/MapOutdoorCharacterShadow.cpp.json index 2587a9f7..0d003f78 100644 --- a/audit/port-map/GameLib/MapOutdoorCharacterShadow.cpp.json +++ b/audit/port-map/GameLib/MapOutdoorCharacterShadow.cpp.json @@ -33,7 +33,7 @@ "impl": [ "extension/src/platform/GameLib/MapOutdoorCharacterShadow.cpp:CMapOutdoor::BeginRenderCharacterShadowToTexture" ], - "note": "Restores 40250 light view, target, viewport and render states; the small R5G6B5 target is rasterized on CPU. Native fake-server capture contains nonwhite shadow pixels; matched Windows screenshot parity remains open." + "note": "Restores 40250 light view, target, viewport and render states; the small R5G6B5 target is rasterized on CPU. Native fake-server capture contains nonwhite shadow pixels; matched Windows screenshot parity remains open. The pass is SELECTARG1 TFACTOR with ALPHAOP DISABLE into a white-cleared R5G6B5 target, so the CPU rasterizer is output-equivalent to a GPU target; it now follows the D3D top-left fill rule. The only other offscreen target in 40250 (CSnowEnvironment blur) is hard-disabled (m_bBlurEnable = FALSE), so no Vulkan offscreen path is needed." }, "CMapOutdoor::EndRenderCharacterShadowToTexture": { "status": "NEEDS_LIVE", diff --git a/extension/src/platform/EterLib/GrpShadowTexture.cpp b/extension/src/platform/EterLib/GrpShadowTexture.cpp deleted file mode 100644 index 4efd35fa..00000000 --- a/extension/src/platform/EterLib/GrpShadowTexture.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Platform skeleton for EterLib/GrpShadowTexture.h (40250 EterLib/GrpShadowTexture.cpp), generated by platform_stub.py. -// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation. -#include "EterLib/StdAfx.h" -#include "EterLib/GrpShadowTexture.h" - -#include "../PlatformStub.h" - -CGraphicShadowTexture::CGraphicShadowTexture() -{ - MT_PLATFORM_STUB(); -} - -CGraphicShadowTexture::~CGraphicShadowTexture() -{ - MT_PLATFORM_STUB(); -} - -auto CGraphicShadowTexture::Destroy() -> void -{ - MT_PLATFORM_STUB(); -} - -auto CGraphicShadowTexture::Create(int, int) -> bool -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -auto CGraphicShadowTexture::Begin() -> void -{ - MT_PLATFORM_STUB(); -} - -auto CGraphicShadowTexture::End() -> void -{ - MT_PLATFORM_STUB(); -} - -auto CGraphicShadowTexture::Set(int) const -> void -{ - MT_PLATFORM_STUB(); -} - -auto CGraphicShadowTexture::GetLightVPMatrixReference() const -> const D3DXMATRIX & -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -auto CGraphicShadowTexture::GetD3DTexture() const -> LPDIRECT3DTEXTURE8 -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -auto CGraphicShadowTexture::Initialize() -> void -{ - MT_PLATFORM_STUB(); -} diff --git a/extension/src/platform/EterLib/RecordingDevice.cpp b/extension/src/platform/EterLib/RecordingDevice.cpp index 0b55590e..ba8ad176 100644 --- a/extension/src/platform/EterLib/RecordingDevice.cpp +++ b/extension/src/platform/EterLib/RecordingDevice.cpp @@ -642,14 +642,22 @@ private: const int y0 = std::max(0, int(std::floor(std::min({a.y, b.y, c.y})))); const int x1 = std::min(width - 1, int(std::ceil(std::max({a.x, b.x, c.x})))); const int y1 = std::min(height - 1, int(std::ceil(std::max({a.y, b.y, c.y})))); + // D3D top-left fill rule: a pixel centre exactly on an edge is covered only when that edge + // is a left edge (weight grows to the right) or a top edge (horizontal, weight grows downward). + auto top_left = [area](const Point& p, const Point& q) { + const float dx = (q.y - p.y) / area, dy = (p.x - q.x) / area; + return dx > 0.0f || (dx == 0.0f && dy > 0.0f); + }; + const bool tlA = top_left(b, c), tlB = top_left(c, a), tlC = top_left(a, b); + auto covered = [](float w, bool tl) { return w > 0.0f || (w == 0.0f && tl); }; for (int y = y0; y <= y1; ++y) for (int x = x0; x <= x1; ++x) { const float px = float(x) + 0.5f, py = float(y) + 0.5f; const float wa = edge(b, c, px, py) / area; const float wb = edge(c, a, px, py) / area; - const float wc = 1.0f - wa - wb; - if (wa < 0.0f || wb < 0.0f || wc < 0.0f) continue; + const float wc = edge(a, b, px, py) / area; + if (!covered(wa, tlA) || !covered(wb, tlB) || !covered(wc, tlC)) continue; const float z = wa * a.z + wb * b.z + wc * c.z; const size_t pixel = size_t(y) * width + x; if (z < 0.0f || z > 1.0f || z > m_offscreenDepth[pixel]) continue; diff --git a/extension/src/port/EterLib/GrpShadowTexture.cpp b/extension/src/port/EterLib/GrpShadowTexture.cpp new file mode 100644 index 00000000..1e02e78c --- /dev/null +++ b/extension/src/port/EterLib/GrpShadowTexture.cpp @@ -0,0 +1,191 @@ +#include "StdAfx.h" +#include "GrpShadowTexture.h" +#include "StateManager.h" + +////////////////////////////////////////////////////////////////////////// +void CGraphicShadowTexture::Destroy() +{ + CGraphicTexture::Destroy(); + + if (m_lpd3dShadowSurface) + { + m_lpd3dShadowSurface->Release(); + m_lpd3dShadowSurface = NULL; + } + + if (m_lpd3dDepthSurface) + { + m_lpd3dDepthSurface->Release(); + m_lpd3dDepthSurface = NULL; + } + + if (m_lpd3dShadowTexture) + { + m_lpd3dShadowTexture->Release(); + m_lpd3dShadowTexture = NULL; + } + + Initialize(); +} + +bool CGraphicShadowTexture::Create(int width, int height) +{ + Destroy(); + + m_width = width; + m_height = height; + + if (FAILED(ms_lpd3dDevice->CreateTexture(m_width, m_height, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &m_lpd3dShadowTexture))) + return false; + + if (FAILED(m_lpd3dShadowTexture->GetSurfaceLevel(0, &m_lpd3dShadowSurface))) + return false; + + if (FAILED(ms_lpd3dDevice->CreateDepthStencilSurface(m_width, m_height, D3DFMT_D16, D3DMULTISAMPLE_NONE, &m_lpd3dDepthSurface))) + return false; + + return true; +} + +void CGraphicShadowTexture::Set(int stage) const +{ + STATEMANAGER.SetTexture(stage, m_lpd3dShadowTexture); +} + +const D3DXMATRIX& CGraphicShadowTexture::GetLightVPMatrixReference() const +{ + return m_d3dLightVPMatrix; +} + +LPDIRECT3DTEXTURE8 CGraphicShadowTexture::GetD3DTexture() const +{ + return m_lpd3dShadowTexture; +} + +void CGraphicShadowTexture::Begin() +{ + D3DXMatrixMultiply(&m_d3dLightVPMatrix, &ms_matView, &ms_matProj); + + ms_lpd3dDevice->GetRenderTarget(&m_lpd3dOldBackBufferSurface); + ms_lpd3dDevice->GetDepthStencilSurface(&m_lpd3dOldDepthBufferSurface); + ms_lpd3dDevice->GetViewport(&m_d3dOldViewport); + + ms_lpd3dDevice->SetRenderTarget(m_lpd3dShadowSurface, m_lpd3dDepthSurface); + + D3DVIEWPORT8 d3dViewport; + d3dViewport.MinZ = 0.0f; + d3dViewport.MaxZ = 1.0f; + d3dViewport.X = 0; + d3dViewport.Y = 0; + d3dViewport.Width = m_width; + d3dViewport.Height = m_height; + + ms_lpd3dDevice->SetViewport(&d3dViewport); + ms_lpd3dDevice->BeginScene(); + + ms_lpd3dDevice->Clear(0L, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0L); + + STATEMANAGER.SaveRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + STATEMANAGER.SaveRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); + STATEMANAGER.SaveRenderState(D3DRS_ALPHABLENDENABLE, true); + STATEMANAGER.SaveRenderState(D3DRS_ALPHATESTENABLE, true); + STATEMANAGER.SaveRenderState(D3DRS_TEXTUREFACTOR, 0xbb000000); + + STATEMANAGER.SetTexture(0, NULL); + STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR); + STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE); + STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); + STATEMANAGER.SaveTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TFACTOR); + STATEMANAGER.SaveTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE); + STATEMANAGER.SaveTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + + STATEMANAGER.SaveTextureStageState(0, D3DTSS_MINFILTER, D3DTEXF_POINT); + STATEMANAGER.SaveTextureStageState(0, D3DTSS_MAGFILTER, D3DTEXF_POINT); + STATEMANAGER.SaveTextureStageState(0, D3DTSS_MIPFILTER, D3DTEXF_POINT); + STATEMANAGER.SaveTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); + STATEMANAGER.SaveTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); + + STATEMANAGER.SetTexture(1, NULL); + STATEMANAGER.SaveTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT); + STATEMANAGER.SaveTextureStageState(1, D3DTSS_COLORARG2, D3DTA_TEXTURE); + STATEMANAGER.SaveTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + STATEMANAGER.SaveTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_CURRENT); + STATEMANAGER.SaveTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_TEXTURE); + STATEMANAGER.SaveTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + + STATEMANAGER.SaveTextureStageState(1, D3DTSS_MINFILTER, D3DTEXF_POINT); + STATEMANAGER.SaveTextureStageState(1, D3DTSS_MAGFILTER, D3DTEXF_POINT); + STATEMANAGER.SaveTextureStageState(1, D3DTSS_MIPFILTER, D3DTEXF_POINT); + STATEMANAGER.SaveTextureStageState(1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); + STATEMANAGER.SaveTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); +} + +void CGraphicShadowTexture::End() +{ + assert(m_lpd3dOldBackBufferSurface != NULL); + assert(m_lpd3dOldDepthBufferSurface != NULL); + + ms_lpd3dDevice->EndScene(); + + ms_lpd3dDevice->SetRenderTarget(m_lpd3dOldBackBufferSurface, m_lpd3dOldDepthBufferSurface); + ms_lpd3dDevice->SetViewport(&m_d3dOldViewport); + + m_lpd3dOldBackBufferSurface->Release(); + m_lpd3dOldDepthBufferSurface->Release(); + + m_lpd3dOldBackBufferSurface = NULL; + m_lpd3dOldDepthBufferSurface = NULL; + + STATEMANAGER.RestoreRenderState(D3DRS_CULLMODE); + STATEMANAGER.RestoreRenderState(D3DRS_ZFUNC); + STATEMANAGER.RestoreRenderState(D3DRS_ALPHABLENDENABLE); + STATEMANAGER.RestoreRenderState(D3DRS_ALPHATESTENABLE); + STATEMANAGER.RestoreRenderState(D3DRS_TEXTUREFACTOR); + + STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLORARG1); + STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLORARG2); + STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLOROP); + STATEMANAGER.RestoreTextureStageState(0, D3DTSS_ALPHAARG1); + STATEMANAGER.RestoreTextureStageState(0, D3DTSS_ALPHAARG2); + STATEMANAGER.RestoreTextureStageState(0, D3DTSS_ALPHAOP); + + STATEMANAGER.RestoreTextureStageState(0, D3DTSS_MINFILTER); + STATEMANAGER.RestoreTextureStageState(0, D3DTSS_MAGFILTER); + STATEMANAGER.RestoreTextureStageState(0, D3DTSS_MIPFILTER); + STATEMANAGER.RestoreTextureStageState(0, D3DTSS_ADDRESSU); + STATEMANAGER.RestoreTextureStageState(0, D3DTSS_ADDRESSV); + + STATEMANAGER.RestoreTextureStageState(1, D3DTSS_COLORARG1); + STATEMANAGER.RestoreTextureStageState(1, D3DTSS_COLORARG2); + STATEMANAGER.RestoreTextureStageState(1, D3DTSS_COLOROP); + STATEMANAGER.RestoreTextureStageState(1, D3DTSS_ALPHAARG1); + STATEMANAGER.RestoreTextureStageState(1, D3DTSS_ALPHAARG2); + STATEMANAGER.RestoreTextureStageState(1, D3DTSS_ALPHAOP); + + STATEMANAGER.RestoreTextureStageState(1, D3DTSS_MINFILTER); + STATEMANAGER.RestoreTextureStageState(1, D3DTSS_MAGFILTER); + STATEMANAGER.RestoreTextureStageState(1, D3DTSS_MIPFILTER); + STATEMANAGER.RestoreTextureStageState(1, D3DTSS_ADDRESSU); + STATEMANAGER.RestoreTextureStageState(1, D3DTSS_ADDRESSV); +} + +void CGraphicShadowTexture::Initialize() +{ + CGraphicTexture::Initialize(); + + m_lpd3dShadowSurface = NULL; + m_lpd3dDepthSurface = NULL; + m_lpd3dOldBackBufferSurface = NULL; + m_lpd3dOldDepthBufferSurface = NULL; + m_lpd3dShadowTexture = NULL; +} + +CGraphicShadowTexture::CGraphicShadowTexture() +{ + Initialize(); +} + +CGraphicShadowTexture::~CGraphicShadowTexture() +{ + Destroy(); +} \ No newline at end of file