Add live-tunable GPS solver highway weight
This commit is contained in:
+46
-11
@@ -2,6 +2,41 @@
|
||||
|
||||
Current date/time context: 2026-06-26, local timezone America/Chicago.
|
||||
|
||||
## 2026-06-26 Confirmed Solver-Cost Patch
|
||||
|
||||
- We have unequivocally found the player GPS route-cost lever.
|
||||
- The crashing RED4ext detour on `0x40bb40` has been replaced by an inline
|
||||
patch at the road-tail block `0x40bb98`. The patch preserves vanilla
|
||||
`node+0x93` behavior, checks `node+0x88 & 0x4000` for `Highway`, and then
|
||||
multiplies highway lane cost by a tunable float.
|
||||
- This inline patch avoids the register-clobber crash from the C++ detour.
|
||||
Static disassembly showed the optimized caller reusing volatile state after
|
||||
calling the tiny vanilla multiplier helper.
|
||||
- The proof build used highway multiplier `0.35` and visibly changed player
|
||||
map routes. It also caused over-aggressive behavior, including a bad custom
|
||||
pin hairpin around the city-center roundabout, proving the hook is real but
|
||||
that `0.35` is too low.
|
||||
- The vanilla A/B build disabled only the inline multiplier patch and restored
|
||||
obviously different route choices. User confirmed the custom pin returned to
|
||||
the straight city-center roundabout route, while the `0.35` build took the
|
||||
wrong hairpin.
|
||||
- Current installed build enables the inline patch with default highway
|
||||
multiplier `0.80` and polls
|
||||
`red4ext/plugins/EdgeWeightGPS/solver_weights.bin` every 500 ms. The file is
|
||||
exactly one little-endian float32 highway multiplier.
|
||||
- The inline patch is deliberately broad across the vanilla road-tail branch:
|
||||
reaching `0x40bb98` already means vanilla selected road-style modes `0/2/4`,
|
||||
so we do not hard-gate on `solver+0xc4 == 2`. This avoids creating a manual
|
||||
drive/autodrive disparity. Pedestrian mode `1` uses a different vanilla
|
||||
branch and is not changed by this road-tail patch.
|
||||
- Preset writer:
|
||||
`tools/write_solver_weights.py`. Preset binaries live in
|
||||
`presets/solver_weights/`: `vanilla`, `mild`, `default`, `strong`,
|
||||
`proof-highway-cheap`, and `highway-expensive`.
|
||||
- Evidence logs:
|
||||
`logs/EdgeWeightGPS_inline_highway035_changed_routes_1449.log` and
|
||||
`logs/EdgeWeightGPS_vanilla_ab_2120.log`.
|
||||
|
||||
## 2026-06-26 Static Update
|
||||
|
||||
- Installed RED4ext state is safe: the game plugin directory contains
|
||||
@@ -110,19 +145,19 @@ Important caveat:
|
||||
- Older DLLs are preserved disabled as:
|
||||
`Cyberpunk 2077/red4ext/plugins/EdgeWeightGPS/EdgeWeightGPS.dll.disabled`
|
||||
and `EdgeWeightGPS.dd1423b.dll.disabled`.
|
||||
- Source defaults now enable the full async solver multiplier hook:
|
||||
`kEnableGpsMultiplierHooks = true`.
|
||||
- The current behavior patch is deliberately narrow and diagnostic:
|
||||
`DetourGpsNodeMultiplier` hooks native `0x40bb40`, applies only when
|
||||
`job+0xc4 == 2` (driving route mode), reads lane/node flags from
|
||||
`node+0x88`, and multiplies vanilla node edge cost by highway `0.35`,
|
||||
GPSOnly `1.10`, pavement `1.25`, and all other flags `1.0`.
|
||||
- `0x40bb00` auxiliary/reverse multiplier is hooked only for signature logging;
|
||||
it is not patched yet.
|
||||
- Source defaults enable the inline road-tail patch:
|
||||
`kEnableGpsNodeMultiplierInlinePatch = true`.
|
||||
- Source defaults do not enable the old full-function detour:
|
||||
`kEnableGpsMultiplierHooks = false`.
|
||||
- Current behavior patch is the inline `0x40bb98` code-cave patch. It preserves
|
||||
vanilla road cost except for highway lanes, whose cost is multiplied by the
|
||||
current `solver_weights.bin` float. Installed default is `0.80`.
|
||||
- `solver_weights.bin` lives beside the DLL and is hot-polled every 500 ms.
|
||||
- The plugin log was truncated immediately before installing this build.
|
||||
- Latest archived test log:
|
||||
`logs/EdgeWeightGPS_spatial_inversion_no_route_change_1845.log`.
|
||||
- Game is cold as of the latest user message.
|
||||
`logs/EdgeWeightGPS_vanilla_ab_2120.log`.
|
||||
- A cold restart is required after installing a new DLL; changing
|
||||
`solver_weights.bin` after that is hot-reloaded.
|
||||
|
||||
## 2026-06-26 Full Async Solver Findings
|
||||
|
||||
|
||||
@@ -66,12 +66,22 @@ Current behavior:
|
||||
|
||||
Active build note:
|
||||
|
||||
- The currently installed build is disabled on disk:
|
||||
`EdgeWeightGPS.dll.disabled`. There is no active `EdgeWeightGPS.dll` in the
|
||||
game plugin directory.
|
||||
- Source defaults are still read-only for route behavior. They disable provider
|
||||
class multiplier overrides and the spatial edge-cost patch, while keeping GPS
|
||||
query lifecycle tracing available for probe builds.
|
||||
- The currently installed build is active as
|
||||
`red4ext/plugins/EdgeWeightGPS/EdgeWeightGPS.dll`.
|
||||
- Source defaults enable the stable inline solver-cost patch at `0x40bb98`
|
||||
(`kEnableGpsNodeMultiplierInlinePatch = true`) and keep the old full-function
|
||||
`0x40bb40` C++ detour disabled (`kEnableGpsMultiplierHooks = false`).
|
||||
- The inline patch preserves vanilla road cost except for highway lanes in the
|
||||
road-tail branch. Highway lanes are multiplied by the live value in
|
||||
`solver_weights.bin`, defaulting to `0.80`.
|
||||
- The patch is intentionally not hard-gated on `solver+0xc4 == 2`; the vanilla
|
||||
branch already limits it to road-style modes `0/2/4`, which keeps manual
|
||||
drive and autodrive-like callers from diverging while leaving pedestrian mode
|
||||
`1` alone.
|
||||
- `solver_weights.bin` lives beside the DLL and is exactly one little-endian
|
||||
float32. The plugin polls it every 500 ms from the Running game-state update.
|
||||
`tools/write_solver_weights.py` writes the installed file or generates preset
|
||||
binaries under `presets/solver_weights/`.
|
||||
- The async route-job hooks are split behind `kEnableGpsRouteJobLifecycleHooks`
|
||||
and are disabled in the active probe; this keeps the log focused on
|
||||
`0x70a42c` submissions, `0x7094b8` result records, and local
|
||||
|
||||
@@ -980,32 +980,62 @@ the original symptom: the solver is weighted enough to avoid obviously invalid
|
||||
surfaces, but it has no strong reason to stay on highway corridors for long
|
||||
vehicle trips.
|
||||
|
||||
The current RED4ext prototype therefore hooks `0x40bb40` and only changes
|
||||
driving-mode calls (`solver/job + 0xc4 == 2`). It reads flags from
|
||||
`node+0x88`, leaves vanilla results intact for ordinary roads and unknown
|
||||
surfaces, and applies this diagnostic multiplier set:
|
||||
The first RED4ext prototype tried to hook `0x40bb40` as a normal C++ detour.
|
||||
That found the correct function but crashed during load-time GPS warmup because
|
||||
the optimized caller expects the tiny vanilla helper not to disturb volatile
|
||||
register state used immediately afterward.
|
||||
|
||||
The stable prototype now patches inline at the road-tail block `0x40bb98`
|
||||
instead. Reaching that block already means the vanilla function selected one of
|
||||
the road-style modes `0/2/4`; pedestrian mode `1` uses a different branch. The
|
||||
patch preserves vanilla `node+0x93` behavior, checks `node+0x88 & 0x4000`, and
|
||||
applies an extra multiplier only for highway lanes.
|
||||
|
||||
The proof multiplier was:
|
||||
|
||||
```text
|
||||
Highway 0.35
|
||||
GPSOnly 1.10
|
||||
Pavement 1.25
|
||||
Other 1.00
|
||||
```
|
||||
|
||||
`0x40bb00` is currently hooked only for signature logging. If the node
|
||||
multiplier test moves routes but produces weird reverse/turnaround behavior,
|
||||
the next step is to tune or gate the auxiliary multiplier as well.
|
||||
Live A/B testing confirmed this is the decisive world-map route-cost lever.
|
||||
With `0.35`, routes visibly changed and a custom pin around the city-center
|
||||
roundabout took an obviously bad highway/ramp hairpin. With the inline patch
|
||||
disabled, the same route returned to the straight vanilla path through the
|
||||
roundabout.
|
||||
|
||||
The current installed build uses a default highway multiplier of `0.80` and
|
||||
hot-reloads `solver_weights.bin` from the plugin directory. The file is exactly
|
||||
one little-endian float32. `tools/write_solver_weights.py` writes the installed
|
||||
file and can generate named presets:
|
||||
|
||||
```text
|
||||
vanilla 1.00
|
||||
mild 0.90
|
||||
default 0.80
|
||||
strong 0.70
|
||||
proof-highway-cheap 0.35
|
||||
highway-expensive 3.00
|
||||
```
|
||||
|
||||
The patch intentionally does not hard-gate on `solver+0xc4 == 2`, because
|
||||
autodrive-like local vehicle navigation may use another road-style mode. Since
|
||||
the patch lives in the vanilla road-tail branch, it applies to road-style modes
|
||||
`0/2/4` and still leaves pedestrian mode `1` alone.
|
||||
|
||||
`0x40bb00` is not patched yet. If tuned highway weights still produce
|
||||
reverse/turnaround artifacts, inspect or patch that auxiliary multiplier so
|
||||
reverse/special progress branches receive a compatible policy.
|
||||
|
||||
## Future Improvements
|
||||
|
||||
Better versions of this mod could:
|
||||
|
||||
- tune the `0x40bb40` solver multiplier after live A/B testing confirms visible
|
||||
route changes
|
||||
- tune the `0x40bb98` inline highway multiplier from the current `0.80` default
|
||||
using live `solver_weights.bin` presets
|
||||
- inspect and, if needed, patch `0x40bb00` so reverse/special progress branches
|
||||
receive the same road-class policy as forward expansion
|
||||
- add a tiny live-tuning file for the solver multipliers, similar to the earlier
|
||||
`spatial_weights.bin`, once the hook is confirmed stable
|
||||
- extend `solver_weights.bin` beyond one highway float only if GPSOnly/pavement
|
||||
tuning becomes necessary
|
||||
- replace the crashing `0x8d46cc` detour with a narrower inline patch or a
|
||||
safer caller-side hook
|
||||
- decode enough of the `VAND` graph to join navigation points to traffic lane
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
ヘフL?
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
fff?
|
||||
@@ -0,0 +1 @@
|
||||
33�>
|
||||
@@ -0,0 +1 @@
|
||||
333?
|
||||
Binary file not shown.
@@ -102,7 +102,7 @@ constexpr bool kEnableVerboseMappinRouteState = false;
|
||||
constexpr bool kEnableGpsMultiplierHooks = false;
|
||||
constexpr bool kEnableGpsAuxMultiplierHook = false;
|
||||
constexpr bool kEnableGpsMultiplierSignatureTrace = false;
|
||||
constexpr bool kEnableGpsNodeMultiplierInlinePatch = false;
|
||||
constexpr bool kEnableGpsNodeMultiplierInlinePatch = true;
|
||||
constexpr bool kEnableGpsCostTablePatch = false;
|
||||
constexpr bool kEnableGpsProviderClassPatch = false;
|
||||
constexpr bool kEnableGpsProviderClassPatchTrace = false;
|
||||
@@ -213,7 +213,7 @@ constexpr uint16_t kTrafficLaneFlagNoAiDriving = 0x2000;
|
||||
constexpr uint16_t kTrafficLaneFlagHighway = 0x4000;
|
||||
constexpr uint16_t kTrafficLaneFlagNoAutodrive = 0x8000;
|
||||
constexpr uintptr_t kMinimumReadableAddress = 0x10000;
|
||||
constexpr float kGpsSolverHighwayMultiplier = 0.35f;
|
||||
constexpr float kGpsSolverHighwayMultiplierDefault = 0.80f;
|
||||
constexpr float kGpsSolverGpsOnlyMultiplier = 1.10f;
|
||||
constexpr float kGpsSolverPavementMultiplier = 1.25f;
|
||||
constexpr size_t kGpsNodeMultiplierRoadTailPatchSize = 14;
|
||||
@@ -226,6 +226,7 @@ constexpr float kGpsSpatialRoadMultiplier = 1.0f;
|
||||
constexpr float kGpsSpatialGpsOnlyMultiplier = 1.20f;
|
||||
constexpr float kGpsSpatialPavementMultiplier = 1.35f;
|
||||
constexpr float kGpsSpatialUnknownMultiplier = 1.05f;
|
||||
constexpr uint64_t kGpsSolverWeightsReloadIntervalMs = 500;
|
||||
constexpr uint64_t kGpsSpatialWeightsReloadIntervalMs = 500;
|
||||
|
||||
struct GpsProviderClassOverride
|
||||
@@ -327,10 +328,12 @@ uint32_t gGpsResolveTraceWindowSerial = 0;
|
||||
std::array<uint32_t, 64> gGpsEdgeCostClassLogCounts{};
|
||||
std::array<uint8_t, kGpsNodeMultiplierRoadTailPatchSize> gGpsNodeMultiplierRoadTailOriginal{};
|
||||
void* gGpsNodeMultiplierRoadTailCave = nullptr;
|
||||
uint8_t* gGpsNodeMultiplierRoadTailHighwayConstant = nullptr;
|
||||
bool gGpsNodeMultiplierRoadTailPatchApplied = false;
|
||||
std::mutex gGpsCostLogMutex;
|
||||
std::mutex gGpsAsyncLogMutex;
|
||||
std::mutex gGpsCostTablePatchMutex;
|
||||
std::mutex gGpsSolverWeightsMutex;
|
||||
uint64_t gLogStartTick = 0;
|
||||
uint64_t gLogFrequency = 0;
|
||||
void* gMappinSetTrackedTarget = nullptr;
|
||||
@@ -569,9 +572,13 @@ std::array<std::atomic<float>, 5> gGpsSpatialMultipliers = {
|
||||
kGpsSpatialPavementMultiplier,
|
||||
kGpsSpatialUnknownMultiplier,
|
||||
};
|
||||
std::atomic<float> gGpsSolverHighwayMultiplier{kGpsSolverHighwayMultiplierDefault};
|
||||
std::mutex gGpsSpatialWeightsMutex;
|
||||
std::atomic<uint64_t> gGpsSolverWeightsNextReloadCheckMs = 0;
|
||||
std::atomic<uint64_t> gGpsSpatialWeightsNextReloadCheckMs = 0;
|
||||
std::filesystem::file_time_type gGpsSolverWeightsLastWriteTime{};
|
||||
std::filesystem::file_time_type gGpsSpatialWeightsLastWriteTime{};
|
||||
bool gGpsSolverWeightsFileKnown = false;
|
||||
bool gGpsSpatialWeightsFileKnown = false;
|
||||
|
||||
uintptr_t GetImageBase();
|
||||
@@ -598,6 +605,11 @@ std::filesystem::path GetSpatialWeightsPath()
|
||||
return GetPluginDirectory() / L"spatial_weights.bin";
|
||||
}
|
||||
|
||||
std::filesystem::path GetSolverWeightsPath()
|
||||
{
|
||||
return GetPluginDirectory() / L"solver_weights.bin";
|
||||
}
|
||||
|
||||
uint64_t GetElapsedMs()
|
||||
{
|
||||
if (!gLogStartTick || !gLogFrequency)
|
||||
@@ -652,6 +664,103 @@ void WriteFloat(uint8_t* aTarget, float aValue)
|
||||
std::memcpy(aTarget, &aValue, sizeof(aValue));
|
||||
}
|
||||
|
||||
bool IsUsableSolverMultiplier(float aValue)
|
||||
{
|
||||
return std::isfinite(aValue) && aValue > 0.0f && aValue < 20.0f;
|
||||
}
|
||||
|
||||
void StoreGpsSolverHighwayMultiplier(float aValue)
|
||||
{
|
||||
gGpsSolverHighwayMultiplier.store(aValue, std::memory_order_relaxed);
|
||||
|
||||
if (gGpsNodeMultiplierRoadTailHighwayConstant)
|
||||
{
|
||||
WriteFloat(gGpsNodeMultiplierRoadTailHighwayConstant, aValue);
|
||||
FlushInstructionCache(GetCurrentProcess(), gGpsNodeMultiplierRoadTailHighwayConstant, sizeof(aValue));
|
||||
}
|
||||
}
|
||||
|
||||
void LogGpsSolverHighwayMultiplier(std::string_view aPrefix, float aValue)
|
||||
{
|
||||
std::ostringstream line;
|
||||
line << aPrefix << " highway=" << aValue;
|
||||
LogRed4ext(line.str());
|
||||
}
|
||||
|
||||
void MaybeReloadGpsSolverWeights(bool aForce = false)
|
||||
{
|
||||
if (!kEnableGpsNodeMultiplierInlinePatch)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto nowMs = GetElapsedMs();
|
||||
if (!aForce && nowMs < gGpsSolverWeightsNextReloadCheckMs.load(std::memory_order_relaxed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock(gGpsSolverWeightsMutex);
|
||||
if (!aForce && nowMs < gGpsSolverWeightsNextReloadCheckMs.load(std::memory_order_relaxed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
gGpsSolverWeightsNextReloadCheckMs.store(nowMs + kGpsSolverWeightsReloadIntervalMs, std::memory_order_relaxed);
|
||||
|
||||
const auto path = GetSolverWeightsPath();
|
||||
std::error_code error;
|
||||
const auto writeTime = std::filesystem::last_write_time(path, error);
|
||||
if (error)
|
||||
{
|
||||
if (aForce || gGpsSolverWeightsFileKnown)
|
||||
{
|
||||
gGpsSolverWeightsFileKnown = false;
|
||||
std::ostringstream line;
|
||||
line << "solver weights file unavailable path=" << path.string()
|
||||
<< " using highway=" << gGpsSolverHighwayMultiplier.load(std::memory_order_relaxed);
|
||||
LogRed4ext(line.str());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aForce && gGpsSolverWeightsFileKnown && writeTime == gGpsSolverWeightsLastWriteTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float highwayMultiplier = 0.0f;
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
input.read(reinterpret_cast<char*>(&highwayMultiplier), static_cast<std::streamsize>(sizeof(highwayMultiplier)));
|
||||
if (input.gcount() != static_cast<std::streamsize>(sizeof(highwayMultiplier)))
|
||||
{
|
||||
std::ostringstream line;
|
||||
line << "solver weights reload skipped path=" << path.string() << " bytes=" << input.gcount()
|
||||
<< " expected=" << sizeof(highwayMultiplier);
|
||||
LogRed4ext(line.str());
|
||||
gGpsSolverWeightsFileKnown = true;
|
||||
gGpsSolverWeightsLastWriteTime = writeTime;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsUsableSolverMultiplier(highwayMultiplier))
|
||||
{
|
||||
std::ostringstream line;
|
||||
line << "solver weights reload skipped path=" << path.string() << " invalidHighway=" << highwayMultiplier;
|
||||
LogRed4ext(line.str());
|
||||
gGpsSolverWeightsFileKnown = true;
|
||||
gGpsSolverWeightsLastWriteTime = writeTime;
|
||||
return;
|
||||
}
|
||||
|
||||
StoreGpsSolverHighwayMultiplier(highwayMultiplier);
|
||||
gGpsSolverWeightsFileKnown = true;
|
||||
gGpsSolverWeightsLastWriteTime = writeTime;
|
||||
|
||||
std::ostringstream line;
|
||||
line << "solver weights reloaded path=" << path.string() << " highway=" << highwayMultiplier;
|
||||
LogRed4ext(line.str());
|
||||
}
|
||||
|
||||
bool InstallGpsNodeMultiplierInlinePatch()
|
||||
{
|
||||
if (!kEnableGpsNodeMultiplierInlinePatch || gGpsNodeMultiplierRoadTailPatchApplied)
|
||||
@@ -659,7 +768,7 @@ bool InstallGpsNodeMultiplierInlinePatch()
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr size_t kCaveSize = 64;
|
||||
constexpr size_t kCaveSize = 80;
|
||||
auto* cave = static_cast<uint8_t*>(
|
||||
VirtualAlloc(nullptr, kCaveSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
|
||||
if (!cave)
|
||||
@@ -668,8 +777,9 @@ bool InstallGpsNodeMultiplierInlinePatch()
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replaces the road-tail at 0x40bb98. This preserves the vanilla node93
|
||||
// multiplier, then applies the highway multiplier before returning.
|
||||
// Replaces the road-tail at 0x40bb98. Reaching this block already means
|
||||
// vanilla selected one of the road-style solver modes (0/2/4), so keep
|
||||
// the change broad enough for manual GPS and autodrive-like callers.
|
||||
size_t offset = 0;
|
||||
const auto emit = [&](std::initializer_list<uint8_t> aBytes) {
|
||||
for (auto byte : aBytes)
|
||||
@@ -691,7 +801,7 @@ bool InstallGpsNodeMultiplierInlinePatch()
|
||||
emit({0xF3, 0x0F, 0x59, 0x05, 0x00, 0x00, 0x00, 0x00}); // mulss xmm0, [rip+disp32]
|
||||
emit({0xC3}); // ret
|
||||
|
||||
while (offset < 40)
|
||||
while ((offset % alignof(float)) != 0)
|
||||
{
|
||||
cave[offset++] = 0x90;
|
||||
}
|
||||
@@ -700,7 +810,8 @@ bool InstallGpsNodeMultiplierInlinePatch()
|
||||
WriteFloat(cave + offset, 1.1f);
|
||||
offset += sizeof(float);
|
||||
const auto highwayConstantOffset = offset;
|
||||
WriteFloat(cave + offset, kGpsSolverHighwayMultiplier);
|
||||
const auto highwayMultiplier = gGpsSolverHighwayMultiplier.load(std::memory_order_relaxed);
|
||||
WriteFloat(cave + offset, highwayMultiplier);
|
||||
offset += sizeof(float);
|
||||
|
||||
const auto caveAddress = reinterpret_cast<uintptr_t>(cave);
|
||||
@@ -743,12 +854,13 @@ bool InstallGpsNodeMultiplierInlinePatch()
|
||||
VirtualProtect(target, sizeof(patch), oldProtect, &ignoredProtect);
|
||||
|
||||
gGpsNodeMultiplierRoadTailCave = cave;
|
||||
gGpsNodeMultiplierRoadTailHighwayConstant = cave + highwayConstantOffset;
|
||||
gGpsNodeMultiplierRoadTailPatchApplied = true;
|
||||
|
||||
std::ostringstream line;
|
||||
line << "gps-node-multiplier-inline-patch applied target_rva=0x" << std::hex
|
||||
<< kGpsNodeMultiplierRoadTailRva << " cave=" << static_cast<void*>(cave)
|
||||
<< " highwayMultiplier=" << std::dec << kGpsSolverHighwayMultiplier;
|
||||
<< " highwayMultiplier=" << std::dec << highwayMultiplier << " modes=road-tail";
|
||||
LogRed4ext(line.str());
|
||||
return true;
|
||||
}
|
||||
@@ -782,6 +894,7 @@ void RemoveGpsNodeMultiplierInlinePatch()
|
||||
gGpsNodeMultiplierRoadTailCave = nullptr;
|
||||
}
|
||||
|
||||
gGpsNodeMultiplierRoadTailHighwayConstant = nullptr;
|
||||
gGpsNodeMultiplierRoadTailPatchApplied = false;
|
||||
}
|
||||
|
||||
@@ -2338,16 +2451,21 @@ bool TryReadUint32FieldValue(void* aObject, uintptr_t aOffset, uint32_t& aValue)
|
||||
aValue);
|
||||
}
|
||||
|
||||
bool IsGpsRoadStyleMode(uint32_t aMode)
|
||||
{
|
||||
return aMode == 0 || aMode == 2 || aMode == 4;
|
||||
}
|
||||
|
||||
bool IsDrivingRouteJob(void* aRouteSystem, void* aActiveEntry)
|
||||
{
|
||||
uint32_t mode = 0;
|
||||
if (TryReadUint32FieldValue(aActiveEntry, 0x80, mode) && mode == 2)
|
||||
if (TryReadUint32FieldValue(aActiveEntry, 0x80, mode) && IsGpsRoadStyleMode(mode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
auto* job = ReadPointerField(aRouteSystem, 0x90D0);
|
||||
return TryReadUint32FieldValue(job, 0xC4, mode) && mode == 2;
|
||||
return TryReadUint32FieldValue(job, 0xC4, mode) && IsGpsRoadStyleMode(mode);
|
||||
}
|
||||
|
||||
void AppendFloatArray(std::ostringstream& aLine, const char* aLabel, const float* aValues, size_t aCount)
|
||||
@@ -4339,7 +4457,7 @@ float GpsSolverNodeWeightMultiplier(uint16_t aFlags)
|
||||
{
|
||||
if ((aFlags & kTrafficLaneFlagHighway) != 0)
|
||||
{
|
||||
return kGpsSolverHighwayMultiplier;
|
||||
return gGpsSolverHighwayMultiplier.load(std::memory_order_relaxed);
|
||||
}
|
||||
if ((aFlags & kTrafficLaneFlagGpsOnly) != 0)
|
||||
{
|
||||
@@ -5084,7 +5202,7 @@ float DetourGpsNodeMultiplier(void* aJob, void* aNode)
|
||||
result = ReadGpsSolverNonRoadMultiplier(retry);
|
||||
}
|
||||
|
||||
if (mode == 2 && std::isfinite(result))
|
||||
if (IsGpsRoadStyleMode(mode) && std::isfinite(result))
|
||||
{
|
||||
result *= GpsSolverNodeWeightMultiplier(nodeFlags);
|
||||
}
|
||||
@@ -5352,6 +5470,8 @@ bool OnRunningUpdate(void* aApp)
|
||||
++gUpdateLogCount;
|
||||
}
|
||||
|
||||
MaybeReloadGpsSolverWeights();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5420,6 +5540,16 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e
|
||||
LogGpsSpatialMultipliers("spatial weights defaults", values);
|
||||
}
|
||||
|
||||
if (kEnableGpsNodeMultiplierInlinePatch)
|
||||
{
|
||||
std::ostringstream line;
|
||||
line << "solver weights path=" << GetSolverWeightsPath().string();
|
||||
LogRed4ext(line.str());
|
||||
MaybeReloadGpsSolverWeights(true);
|
||||
LogGpsSolverHighwayMultiplier("solver weights active",
|
||||
gGpsSolverHighwayMultiplier.load(std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
InstallGpsNodeMultiplierInlinePatch();
|
||||
|
||||
if (kEnableGpsQueryLifecycleHooks)
|
||||
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Write EdgeWeightGPS solver highway presets as one raw float32 value."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import struct
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
DEFAULT_INSTALL_PATH = Path(
|
||||
"/var/home/salt/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/"
|
||||
"Cyberpunk 2077/red4ext/plugins/EdgeWeightGPS/solver_weights.bin"
|
||||
)
|
||||
|
||||
PRESETS: dict[str, float] = {
|
||||
"vanilla": 1.00,
|
||||
"default": 0.80,
|
||||
"mild": 0.90,
|
||||
"strong": 0.70,
|
||||
"proof-highway-cheap": 0.35,
|
||||
"highway-expensive": 3.00,
|
||||
}
|
||||
|
||||
|
||||
def parse_multiplier(text: str) -> float:
|
||||
value = float(text)
|
||||
if not 0.0 < value < 20.0:
|
||||
raise argparse.ArgumentTypeError("multiplier must be greater than 0 and less than 20")
|
||||
return value
|
||||
|
||||
|
||||
def write_weight(path: Path, value: float) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(struct.pack("<f", value))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("preset", nargs="?", choices=sorted(PRESETS), default="default")
|
||||
parser.add_argument("--value", type=parse_multiplier, help="explicit highway multiplier")
|
||||
parser.add_argument("--output", type=Path, default=DEFAULT_INSTALL_PATH)
|
||||
parser.add_argument("--preset-dir", type=Path, help="write every named preset into this directory")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.preset_dir:
|
||||
for name, value in PRESETS.items():
|
||||
path = args.preset_dir / f"{name}.bin"
|
||||
write_weight(path, value)
|
||||
print(f"{path}: {value}")
|
||||
return 0
|
||||
|
||||
value = args.value if args.value is not None else PRESETS[args.preset]
|
||||
write_weight(args.output, value)
|
||||
print(f"{args.output}: {value}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user