$ErrorActionPreference='Stop' $root=(Resolve-Path (Join-Path $PSScriptRoot '../../..')).Path Set-Location $root $baseline=Get-Content 'CodexOutput/upserver/阶段4B2方案受审基线-修订2-2026-09-23.json' -Raw | ConvertFrom-Json $partial=Get-Content "$PSScriptRoot/checkpoint2-partial-final-verification.json" -Raw | ConvertFrom-Json $allowed=[Collections.Generic.HashSet[string]]::new([StringComparer]::Ordinal) foreach($item in $partial.checks){if($item.authorizedChange){[void]$allowed.Add($item.path)}} foreach($path in @('DataCenter/DataCenter.PlayerFishData.cs','DataCenter/EventPartnerGatherManager.cs','Services/UserService.cs','Services/AggUserService.cs','Core/CustomServerMgr.cs','Core/PlayFabMgr.cs','Stages/QuitAct.cs','EventPartnerGather/PartnerInfoManager.cs')){[void]$allowed.Add("Client/Assets/Scripts/$path")} $checks=@(foreach($item in $baseline.files){$hash=(Get-FileHash -LiteralPath $item.path -Algorithm SHA256).Hash; [ordered]@{path=$item.path;baseline=$item.sha256;actual=$hash;unchanged=$hash -eq $item.sha256;authorizedChange=$allowed.Contains($item.path)}}) $outside=@($checks | Where-Object { !$_.unchanged -and !$_.authorizedChange }) $club=Get-Content 'Client/Assets/Scripts/Services/ClubService.cs' -Raw $clubBase=Get-Content "$PSScriptRoot/baseline/ClubService.cs.txt" -Raw $begin=$club.IndexOf(' // KV loading owns these three fields only;') $end=$club.IndexOf(' public int ClubLureDonateCount', $begin) $stripped=$club.Remove($begin,$end-$begin).Replace("`r`n","`n") $clubOnlyKV=$stripped.Replace(" LureDonateData lureDonateData;`n`n"," LureDonateData lureDonateData;`n") -ceq $clubBase.Replace("`r`n","`n") $sourcePaths=@($allowed | Where-Object { $_.EndsWith('.cs') }) $sourcePaths+=@(Get-ChildItem 'Client/Assets/Scripts/Services/PlayerData/PlayerKV*.cs' | ForEach-Object { $_.FullName.Substring($root.Length+1).Replace('\','/') }) $sourcePaths+=@('Client/Assets/Scripts/Services/UserService.KVLoad.cs','Client/Assets/Editor/KVGameLoadValidation.cs') $methods=@();$flags=@() foreach($path in ($sourcePaths | Sort-Object -Unique)){ $text=Get-Content -LiteralPath $path -Raw foreach($match in [regex]::Matches($text,'\b(?:internal|public|private)\s+(?:override\s+|virtual\s+)?void\s+(ResetForKVLoad|ReplaceFromKVDecoded)\s*\([^)]*\)\s*\{')){ $start=$match.Index+$match.Length;$depth=1;$end=$start while($end -lt $text.Length -and $depth -gt 0){if($text[$end] -eq '{'){$depth++};if($text[$end] -eq '}'){$depth--};$end++} $body=$text.Substring($start,$end-$start) # This is a bounded direct-call audit, not a substitute for constructor/helper inspection or runtime counters. $hits=@([regex]::Matches($body,'\b(?:PlayerPrefs|PlayFabClientAPI|PlayFabClientAsyncAPI|TYSdkFacade|GEvent|Task\.Delay|Observable|Timer)\b|\b(?:Publish|Subscribe|SaveData|UpdateUserDataValue|LoadData|InitData|Init|ClearData|AddGiftFaceUIForce|AddItem)\s*\(') | ForEach-Object Value) $record=[ordered]@{path=$path;method=$match.Groups[1].Value;line=1+([regex]::Matches($text.Substring(0,$match.Index),"`n")).Count;directForbiddenCalls=$hits} $methods+=$record;if($hits.Count){$flags+=$record} } } $compile=Get-Content "$PSScriptRoot/checkpoint4-final4-compile.log" -Raw | ConvertFrom-Json $localCases=(Get-Content "$PSScriptRoot/checkpoint4-final-unity-cases.log" -Raw | ConvertFrom-Json).Result | ConvertFrom-Json | Where-Object caseId -ne 'B2-20' $full=(Get-Content "$PSScriptRoot/checkpoint4-release-full.log" -Raw | ConvertFrom-Json).Result | ConvertFrom-Json $result=[ordered]@{generatedAt=(Get-Date).ToString('o');baselineCount=$checks.Count;unchanged=@($checks | Where-Object unchanged).Count;authorizedChanges=@($checks|Where-Object{!$_.unchanged -and $_.authorizedChange}).Count;outside=$outside;clubOnlyAddedKVBlock=$clubOnlyKV;directAuditMethodCount=$methods.Count;directAuditFlags=$flags;compile=@{success=$compile.Success;errors=$compile.ErrorCount;warnings=$compile.WarningCount;newKVWarnings=@($compile.Warnings|Where-Object{$_.File -match 'PlayerKV|KVGameLoadValidation|UserService.KVLoad'}).Count};unityLocalCases=@($localCases);unityFull=$full;unityAssertions=($localCases.assertions | Measure-Object -Sum).Sum+$full.assertions;checks=$checks;methods=$methods} $result | ConvertTo-Json -Depth 8 | Set-Content "$PSScriptRoot/checkpoint4-boundary-and-evidence.json" [ordered]@{baselineCount=$checks.Count;outside=$outside.Count;clubOnlyKV=$clubOnlyKV;auditMethods=$methods.Count;directFlags=$flags.Count;unityAssertions=$result.unityAssertions;fullB220=$full.fullB220} | ConvertTo-Json if($outside.Count -gt 0 -or !$clubOnlyKV -or $flags.Count -gt 0 -or !$full.success){exit 1} exit 0