Clarify GPS candidate score penalty

This commit is contained in:
2026-06-27 05:08:35 -05:00
parent 3ac184a2de
commit 8135ff96bf
+26 -17
View File
@@ -86,8 +86,9 @@ struct Sdk
namespace namespace
{ {
// Executable RVAs in the tested Cyberpunk 2077 build. The two expansion sites // Executable RVAs in the tested Cyberpunk 2077 build. The two expansion sites
// are ordinary road-neighbor relax callsites; both feed the native relax/update // are the known ordinary road-neighbor relax callsites; both feed the native
// helper that commits a candidate route node when the new score wins. // relax/update helper that commits a candidate route node when the new score
// wins. Solver paths that do not flow through these callsites are not changed.
constexpr uintptr_t kGpsRelaxUpdateRva = 0x040BA58; constexpr uintptr_t kGpsRelaxUpdateRva = 0x040BA58;
constexpr uintptr_t kGpsExpandListARelaxPatchRva = 0x040B49A; constexpr uintptr_t kGpsExpandListARelaxPatchRva = 0x040B49A;
constexpr uintptr_t kGpsExpandListBRelaxPatchRva = 0x040B6C3; constexpr uintptr_t kGpsExpandListBRelaxPatchRva = 0x040B6C3;
@@ -96,6 +97,10 @@ constexpr uintptr_t kGpsExpandListBRelaxPatchRva = 0x040B6C3;
// of native code. // of native code.
constexpr size_t kRelaxPatchSize = 23; constexpr size_t kRelaxPatchSize = 23;
constexpr size_t kPatchCaveSize = 80; constexpr size_t kPatchCaveSize = 80;
// Added to each candidate relaxation handled by the patched expansion sites.
// This is not a rewrite of stored graph edge weights; routes with more ordinary
// segment relaxations accumulate more penalty than routes with fewer/smoother
// segments, which is what makes the constant affect route choice.
constexpr float kFixedEdgePenalty = 80.0f; constexpr float kFixedEdgePenalty = 80.0f;
struct PatchSite struct PatchSite
@@ -114,8 +119,9 @@ const RED4ext::v1::Sdk* gSdk = nullptr;
std::array<PatchSite, 2> gPatchSites{{ std::array<PatchSite, 2> gPatchSites{{
// The two sites share the same relax/update tail but arrive with the // The two sites share the same relax/update tail but arrive with the
// current node cost in different XMM registers. Each expected instruction // neighbor candidate cost in different XMM registers. Each expected
// is replayed first so the cave preserves vanilla's edge accumulation. // instruction is replayed first so the cave preserves vanilla's edge
// accumulation.
{ {
"expand-list-a", "expand-list-a",
kGpsExpandListARelaxPatchRva, kGpsExpandListARelaxPatchRva,
@@ -228,7 +234,7 @@ bool InstallPatchSite(PatchSite& aSite)
if (std::memcmp(target, aSite.expectedFirstInstruction.data(), aSite.expectedFirstInstruction.size()) != 0) if (std::memcmp(target, aSite.expectedFirstInstruction.data(), aSite.expectedFirstInstruction.size()) != 0)
{ {
std::ostringstream line; std::ostringstream line;
line << "GPS fixed-edge penalty skipped site=" << aSite.name line << "GPS candidate-score penalty skipped site=" << aSite.name
<< " reason=unexpected-bytes rva=0x" << std::hex << aSite.targetRva; << " reason=unexpected-bytes rva=0x" << std::hex << aSite.targetRva;
LogError(line.str()); LogError(line.str());
return false; return false;
@@ -239,7 +245,7 @@ bool InstallPatchSite(PatchSite& aSite)
if (!cave) if (!cave)
{ {
std::ostringstream line; std::ostringstream line;
line << "GPS fixed-edge penalty skipped site=" << aSite.name << " reason=alloc-failed"; line << "GPS candidate-score penalty skipped site=" << aSite.name << " reason=alloc-failed";
LogError(line.str()); LogError(line.str());
return false; return false;
} }
@@ -251,12 +257,15 @@ bool InstallPatchSite(PatchSite& aSite)
} }
// Register contract for the native relax/update call: // Register contract for the native relax/update call:
// - xmm6 holds this edge's accumulated g-score. // - replaying the site-specific add makes xmm6 the neighbor candidate's
// - xmm0 is the candidate f-score passed through [rsp+0x20]. // path-cost contribution for this relaxation.
// - xmm0 carries the remaining score term; vanilla adds xmm6 into it
// before passing the final candidate score through [rsp+0x20].
// - xmm3 mirrors xmm6 for the helper call. // - xmm3 mirrors xmm6 for the helper call.
// The cave replays vanilla's first add into xmm6, adds our fixed segment // The cave adds a fixed per-relaxation penalty into xmm6 before rebuilding
// penalty, rebuilds the helper arguments, then jumps back after the // the helper arguments, so this biases against routes made from many small
// overwritten instruction window. // ordinary road-neighbor relaxations rather than uniformly lifting the
// whole graph at rest.
const auto addPenaltyOffset = offset; const auto addPenaltyOffset = offset;
EmitBytes(cave, offset, {0xF3, 0x0F, 0x58, 0x35, 0x00, 0x00, 0x00, 0x00}); // addss xmm6, [rip+disp32] EmitBytes(cave, offset, {0xF3, 0x0F, 0x58, 0x35, 0x00, 0x00, 0x00, 0x00}); // addss xmm6, [rip+disp32]
EmitBytes(cave, offset, {0xF3, 0x0F, 0x58, 0xC6}); // addss xmm0, xmm6 EmitBytes(cave, offset, {0xF3, 0x0F, 0x58, 0xC6}); // addss xmm0, xmm6
@@ -290,7 +299,7 @@ bool InstallPatchSite(PatchSite& aSite)
{ {
VirtualFree(cave, 0, MEM_RELEASE); VirtualFree(cave, 0, MEM_RELEASE);
std::ostringstream line; std::ostringstream line;
line << "GPS fixed-edge penalty skipped site=" << aSite.name << " reason=protect-failed"; line << "GPS candidate-score penalty skipped site=" << aSite.name << " reason=protect-failed";
LogError(line.str()); LogError(line.str());
return false; return false;
} }
@@ -305,7 +314,7 @@ bool InstallPatchSite(PatchSite& aSite)
aSite.applied = true; aSite.applied = true;
std::ostringstream line; std::ostringstream line;
line << "GPS fixed-edge penalty applied site=" << aSite.name << " rva=0x" << std::hex << aSite.targetRva line << "GPS candidate-score penalty applied site=" << aSite.name << " rva=0x" << std::hex << aSite.targetRva
<< " cave=" << static_cast<void*>(cave) << std::dec << " penalty=" << kFixedEdgePenalty; << " cave=" << static_cast<void*>(cave) << std::dec << " penalty=" << kFixedEdgePenalty;
LogInfo(line.str()); LogInfo(line.str());
return true; return true;
@@ -331,13 +340,13 @@ void RemovePatchSites()
VirtualProtect(target, site.original.size(), oldProtect, &ignoredProtect); VirtualProtect(target, site.original.size(), oldProtect, &ignoredProtect);
std::ostringstream line; std::ostringstream line;
line << "GPS fixed-edge penalty restored site=" << site.name; line << "GPS candidate-score penalty restored site=" << site.name;
LogInfo(line.str()); LogInfo(line.str());
} }
else else
{ {
std::ostringstream line; std::ostringstream line;
line << "GPS fixed-edge penalty restore failed site=" << site.name << " reason=protect-failed"; line << "GPS candidate-score penalty restore failed site=" << site.name << " reason=protect-failed";
LogError(line.str()); LogError(line.str());
} }
@@ -361,8 +370,8 @@ bool InstallPatchSites()
if (!ok) if (!ok)
{ {
// Route scoring should be all-or-nothing: one patched expansion site // Both known expansion sites must be patched together; otherwise these
// and one vanilla site would produce inconsistent route costs. // two relaxation paths would score candidate routes inconsistently.
RemovePatchSites(); RemovePatchSites();
} }