Skip to content

Commit d57c82d

Browse files
Cheater_Detection: fix
1 parent d8d2195 commit d57c82d

5 files changed

Lines changed: 159 additions & 100 deletions

File tree

Cheater_Detection/Core/Evidence_system.lua

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ Evidence.Config = {
3939
},
4040
},
4141

42+
-- No decay until this many seconds after LastDecayTime (HoldDecay resets the clock).
43+
MethodDecayGraceSec = 60.0,
44+
4245
-- Per-method decay (weight/sec). double_tap: ~1 usage (30) per minute when idle.
4346
MethodDecayPerSec = {
4447
double_tap = 30.0 / 60.0,
@@ -167,19 +170,41 @@ local function getMethodDecayRate(detectionName, category)
167170
return getCategoryDecayRate(category)
168171
end
169172

170-
local function applyLazyDecayToReason(reason, detectionName)
173+
local function getDecayGraceSec()
174+
return Evidence.Config.MethodDecayGraceSec or 60.0
175+
end
176+
177+
local function applyWeightDecay(reason, detectionName, methodName, now)
171178
if not reason or reason.ManualDecay == true then
172-
return
179+
return false
173180
end
174-
local now = globals.RealTime()
181+
175182
local elapsed = now - (reason.LastDecayTime or now)
176-
if elapsed > 0 then
177-
local rate = reason.DecayRate or getMethodDecayRate(detectionName, reason.Category)
178-
if rate > 0 and reason.Weight > 0 then
179-
reason.Weight = math.max(Evidence.Config.MinWeightFloor, reason.Weight - rate * elapsed)
180-
end
183+
local grace = getDecayGraceSec()
184+
if elapsed <= grace then
185+
return false
186+
end
187+
188+
local rate = reason.DecayRate or getMethodDecayRate(methodName or detectionName, reason.Category)
189+
if not isDetectionEnabled(methodName or detectionName) then
190+
rate = rate * 5.0
191+
end
192+
if rate <= 0 or reason.Weight <= 0 then
193+
reason.LastDecayTime = now
194+
return false
181195
end
196+
197+
local oldWeight = reason.Weight
198+
reason.Weight = math.max(
199+
Evidence.Config.MinWeightFloor,
200+
reason.Weight - rate * (elapsed - grace)
201+
)
182202
reason.LastDecayTime = now
203+
return reason.Weight ~= oldWeight
204+
end
205+
206+
local function applyLazyDecayToReason(reason, detectionName)
207+
applyWeightDecay(reason, detectionName, detectionName, globals.RealTime())
183208
end
184209

185210
local function applyReasonOptions(reason, opts)
@@ -522,29 +547,20 @@ local function applyDecayToEvidence(steamID, evidence, now)
522547
-- downgraded by score decay (syncPlayerStateFromEvidence guards against it),
523548
-- so computing decay is wasted work once the flag is set.
524549
local state = PlayerCache.GetByID(steamID)
525-
if state and (state.flags & Constants.Flags.CHEATER) ~= 0 then
526-
return false
550+
if state then
551+
if (state.flags & Constants.Flags.CHEATER) ~= 0 then
552+
return false
553+
end
554+
local pdata = state.pdata
555+
if pdata and (pdata.isDormant or not pdata.isAlive) then
556+
return false
557+
end
527558
end
528559

529560
local changed = false
530561
for methodName, reason in pairs(evidence.Reasons) do
531-
if reason.ManualDecay ~= true then
532-
local oldWeight = reason.Weight
533-
local now = globals.RealTime()
534-
local elapsed = now - (reason.LastDecayTime or now)
535-
if elapsed > 0 then
536-
local rate = reason.DecayRate or getMethodDecayRate(methodName, reason.Category)
537-
if not isDetectionEnabled(methodName) then
538-
rate = rate * 5.0
539-
end
540-
if rate > 0 and reason.Weight > 0 then
541-
reason.Weight = math.max(Evidence.Config.MinWeightFloor, reason.Weight - rate * elapsed)
542-
end
543-
reason.LastDecayTime = now
544-
end
545-
if reason.Weight ~= oldWeight then
546-
changed = true
547-
end
562+
if applyWeightDecay(reason, methodName, methodName, now) then
563+
changed = true
548564
end
549565
end
550566

Cheater_Detection/Main.lua

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,6 @@ end
264264
-- [[ Callbacks ]]
265265
local function OnCreateMove(cmd)
266266
-- Game logic only on CreateMove (never on Draw).
267-
Evidence.ApplyDecay()
268267
Scheduler.Tick()
269268

270269
-- SetContext is expensive; Begin/End already no-op when profiler overlay is off.
@@ -376,6 +375,9 @@ local function OnCreateMove(cmd)
376375
end
377376
lastDetectorTick = curTick
378377

378+
-- Once per game tick: decay only for alive, non-dormant players (60s grace after HoldDecay).
379+
Evidence.ApplyDecay()
380+
379381
local isDebug = isDebugEnabled()
380382
local localID = tostring(Common.GetSteamID64(localPlayer) or "")
381383
local stateTable = PlayerCache.GetActiveTable()

Cheater_Detection/Utils/DetectionConfig.lua

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,6 @@ DetectionConfig.Detectors = {
2222
retentionTicks = 40,
2323
fields = { HistoryManager.Fields.SimulationTime },
2424
},
25-
FakeLag = {
26-
retentionTicks = 40,
27-
fields = { HistoryManager.Fields.SimulationTime },
28-
},
29-
DoubleTap = {
30-
retentionTicks = 40,
31-
fields = { HistoryManager.Fields.SimulationTime },
32-
},
3325
}
3426

3527
DetectionConfig.DefaultRetentionTicks = 24

Cheater_Detection/detectors/double_tap.lua

Lines changed: 74 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ local FAKE_LAG_SUPPRESS_TICKS_66HZ = 66.0
3434
local DT_EVIDENCE_WEIGHT = 30.0
3535
local DT_LATE_DELAY_PENALTY_MAX = 0.2 -- up to -20% weight at end of 48t confirm window
3636
local DT_EVIDENCE_COOLDOWN_S = 0.5
37-
local REJECT_LOG_INTERVAL_S = 0.35
37+
local REJECT_LOG_INTERVAL_S = 0.35
38+
local HURT_LOG_INTERVAL_S = 1.0
39+
local WATCH_BURST_SCAN_INTERVAL = 4 -- ticks between scans while waiting for post-hit burst
3840

3941
local cachedTickInterval = nil
4042
local cachedBurstMinTicks = nil
@@ -173,6 +175,7 @@ local function getState(id)
173175
lastBurstSignature = nil,
174176
lastRejectTag = nil,
175177
lastRejectLogTime = 0,
178+
lastHurtLogTime = 0,
176179
}
177180
end
178181
return playerState[id]
@@ -314,20 +317,27 @@ local function logBurstScanFailure(id, tag, detail)
314317
end
315318
end
316319

317-
local function findRecentDtBurst(id)
320+
local function findRecentDtBurst(id, scanDepthOverride, quietOnMiss)
318321
local history = HistoryManager.GetPlayerHistory(id)
319322
if not history or history._count < 2 then
320-
logBurstScanFailure(id, "no_history", string.format(
321-
"history missing or count=%s", history and tostring(history._count) or "nil"))
323+
if not quietOnMiss then
324+
logBurstScanFailure(id, "no_history", string.format(
325+
"history missing or count=%s", history and tostring(history._count) or "nil"))
326+
end
322327
return 0, 1, nil, 0
323328
end
324329

325330
local burstMin, burstMax = getBurstThresholds()
326-
local scanDepth = isWatchingForBurst(id) and WATCHING_DT_BURST_SCAN or RECENT_DT_BURST_SCAN
331+
local scanDepth = scanDepthOverride
332+
if not scanDepth then
333+
scanDepth = isWatchingForBurst(id) and WATCHING_DT_BURST_SCAN or RECENT_DT_BURST_SCAN
334+
end
327335
local deltaTicks, deltaCount = HistoryManager.GetSimDeltaDeltas(history, scanDepth)
328336
if deltaCount < 1 then
329-
logBurstScanFailure(id, "no_deltas", string.format(
330-
"history count=%d but no simtime gaps", history._count))
337+
if not quietOnMiss then
338+
logBurstScanFailure(id, "no_deltas", string.format(
339+
"history count=%d but no simtime gaps", history._count))
340+
end
331341
return 0, 1, deltaTicks, 0
332342
end
333343

@@ -356,12 +366,14 @@ local function findRecentDtBurst(id)
356366
end
357367
end
358368

359-
if rhythmBlocked then
360-
logBurstScanFailure(id, "rhythm", rhythmBlocked .. " | deltas=" .. formatDeltaPreview(deltaTicks, 8))
361-
else
362-
logBurstScanFailure(id, "no_dt_band", string.format(
363-
"scan=%d band=[%d,%d) sawDtBand=%s maxInScan=%d deltas=%s",
364-
scanLimit, burstMin, burstMax, tostring(sawDtBand), maxInScan, formatDeltaPreview(deltaTicks, 8)))
369+
if not quietOnMiss then
370+
if rhythmBlocked then
371+
logBurstScanFailure(id, "rhythm", rhythmBlocked .. " | deltas=" .. formatDeltaPreview(deltaTicks, 8))
372+
else
373+
logBurstScanFailure(id, "no_dt_band", string.format(
374+
"scan=%d band=[%d,%d) sawDtBand=%s maxInScan=%d deltas=%s",
375+
scanLimit, burstMin, burstMax, tostring(sawDtBand), maxInScan, formatDeltaPreview(deltaTicks, 8)))
376+
end
365377
end
366378

367379
return 0, 1, deltaTicks, deltaCount
@@ -400,16 +412,52 @@ local function onBurstDetected(id, burstAmount, curTick, burstIndex)
400412
end
401413
end
402414

415+
local function shouldScanBurstThisTick(id, curTick)
416+
if not isWatchingForBurst(id) then
417+
return true
418+
end
419+
return (curTick % WATCH_BURST_SCAN_INTERVAL) == 0
420+
end
421+
422+
local function tryDetectBurstForPlayer(id, curTick, forceScan)
423+
if not forceScan and not shouldScanBurstThisTick(id, curTick) then
424+
return
425+
end
426+
427+
local scanDepth = forceScan and RECENT_DT_BURST_SCAN or nil
428+
local quietOnMiss = forceScan == true
429+
local burstAmount, burstIndex = findRecentDtBurst(id, scanDepth, quietOnMiss)
430+
if burstAmount == 0 then
431+
return
432+
end
433+
434+
cleanBurstTable(curTick)
435+
recordBurstTick(curTick, id)
436+
437+
if isInHitchWindow(curTick) then
438+
return
439+
end
440+
441+
if isServerHitch(curTick) then
442+
lastServerHitchTick = curTick
443+
if isDtLogEnabled() then
444+
print(string.format(
445+
"[DoubleTap] server hitch suppressed burst for %s (tick=%d)",
446+
id, curTick))
447+
end
448+
return
449+
end
450+
451+
onBurstDetected(id, burstAmount, curTick, burstIndex)
452+
end
453+
403454
function DoubleTap.HasWork(playerState)
404455
if not isEnabled() then
405456
return false
406457
end
407458
if not playerState or not playerState.id then
408459
return false
409460
end
410-
if Common.IsLogCategoryEnabled("DoubleTap") or Common.IsLogCategoryEnabled("Warp/DT") then
411-
return true
412-
end
413461
local id = playerState.id
414462
local weight = Evidence.GetMethodWeight(id, "double_tap")
415463
if weight <= 0 then
@@ -423,7 +471,7 @@ function DoubleTap.HasWork(playerState)
423471
return true
424472
end
425473
if isWatchingForBurst(id) then
426-
return true
474+
return (globals.TickCount() % WATCH_BURST_SCAN_INTERVAL) == 0
427475
end
428476
return (globals.TickCount() % 4) == 0
429477
end
@@ -459,30 +507,7 @@ function DoubleTap.ProcessPlayer(playerState)
459507

460508
local curTick = globals.TickCount()
461509
clearStaleBurstState(getState(id), curTick)
462-
463-
local burstAmount, burstIndex = findRecentDtBurst(id)
464-
if burstAmount == 0 then
465-
return
466-
end
467-
468-
cleanBurstTable(curTick)
469-
recordBurstTick(curTick, id)
470-
471-
if isInHitchWindow(curTick) then
472-
return
473-
end
474-
475-
if isServerHitch(curTick) then
476-
lastServerHitchTick = curTick
477-
if isDtLogEnabled() then
478-
print(string.format(
479-
"[DoubleTap] server hitch suppressed burst for %s (tick=%d)",
480-
id, curTick))
481-
end
482-
return
483-
end
484-
485-
onBurstDetected(id, burstAmount, curTick, burstIndex)
510+
tryDetectBurstForPlayer(id, curTick)
486511
end
487512

488513
function DoubleTap.Tick()
@@ -518,11 +543,16 @@ Events.Subscribe("OnHitscanHit", function(hit)
518543
end
519544

520545
data.lastDamageTick = curTick
546+
tryDetectBurstForPlayer(attackerID, curTick, true)
521547

522548
if isDtLogEnabled() then
523-
print(string.format(
524-
"[DoubleTap] %s dealt dmg=%d — watching for burst within %d ticks",
525-
attackerID, hit.damage, getConfirmWindowTicks()))
549+
local now = globals.RealTime()
550+
if (now - data.lastHurtLogTime) >= HURT_LOG_INTERVAL_S then
551+
data.lastHurtLogTime = now
552+
print(string.format(
553+
"[DoubleTap] %s dealt dmg=%d — watching for burst within %d ticks",
554+
attackerID, hit.damage, getConfirmWindowTicks()))
555+
end
526556
end
527557
end)
528558

0 commit comments

Comments
 (0)