$ErrorActionPreference='Stop' $root='C:/projects/ft/CodexOutput/upserver' function Read-Backup($stamp) { Get-Content -Raw -LiteralPath "$root/KVGameplayLoopTests/backups/$stamp/102.json" | ConvertFrom-Json } function Changed-Keys($left,$right) { @(@($left.PSObject.Properties.Name)+@($right.PSObject.Properties.Name) | Sort-Object -Unique | Where-Object { $left.$_ -cne $right.$_ }) } function Hash-Value([string]$value) { $sha=[Security.Cryptography.SHA256]::Create() try { [BitConverter]::ToString($sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($value))).Replace('-','') } finally { $sha.Dispose() } } $before=Read-Backup '20260928T024346Z' $reward=Read-Backup '20260928T024903Z' $closed=Read-Backup '20260928T025001Z' $final=Read-Backup '20260928T025514Z' $events=@(Get-Content -LiteralPath "$root/KVGameplayLoopTests/runs/44848_639261601944579366/events.jsonl" | ForEach-Object { $_ | ConvertFrom-Json }) $recoveryEvents=@(Get-Content -LiteralPath "$root/KVGameplayLoopTests/runs/103828_639261606481142886/events.jsonl" | ForEach-Object { $_ | ConvertFrom-Json }) $recovered=(Get-Content -Raw -LiteralPath "$root/KVFishingRewardTests/fourth-recovered-values.json" | ConvertFrom-Json).Result | ConvertFrom-Json $preMain=$before.data.NewPlayerDataDic | ConvertFrom-Json $postMain=$final.data.NewPlayerDataDic | ConvertFrom-Json $preMap=$before.data.Map_5_102 | ConvertFrom-Json $postMap=$final.data.Map_5_102 | ConvertFrom-Json $rewardConfirmation=@($events | Where-Object kind -EQ 'fishing-reward-confirmed')[0] $presentation=@($events | Where-Object kind -EQ 'fishing-reward-presented')[0] $ticketMatches=@($rewardConfirmation.detail.tickets | ForEach-Object { [pscustomobject]@{key=$_.key;sequence=$_.sequence;matchesFinalServer=((Hash-Value $final.data.($_.key)) -ceq $_.valueHash)} }) $cacheResults=@('44848_639261601944579366','103828_639261606481142886' | ForEach-Object { $cacheFile=Get-ChildItem -LiteralPath "$root/KVGameplayLoopTests/runs/$_/cache" -Recurse -Filter state.json $cache=Get-Content -Raw -LiteralPath $cacheFile.FullName | ConvertFrom-Json [pscustomobject]@{run=$_;pending=@($cache.pending).Count;unknown=($null -ne $cache.unknownBatch);active=($null -ne $cache.activeBatch);baselineChangedKeys=@(Changed-Keys $cache.baseline $final.data)} }) $report=[ordered]@{ phase='4C2-fourth-real-run-and-fresh-process-recovery-awaiting-independent-review' api=$recovered.api backups=@('20260928T024346Z','20260928T024903Z','20260928T025001Z','20260928T025514Z') countBefore=@($before.data.PSObject.Properties).Count;countAfter=@($final.data.PSObject.Properties).Count changedKeys=@(Changed-Keys $before.data $final.data) changedFishIds=@(Changed-Keys $preMap $postMap) fishBefore=$preMap.'10223';fishAfter=$postMap.'10223' mainBefore=$preMain;mainAfter=$postMain rewardToQuitChangedKeys=@(Changed-Keys $reward.data $closed.data) quitToRecoveryFinalChangedKeys=@(Changed-Keys $closed.data $final.data) consumeCount=@($events | Where-Object kind -EQ 'fishing-energy-applied').Count rewardConfirmedCount=@($events | Where-Object kind -EQ 'fishing-reward-confirmed').Count rewardPresentedCount=@($events | Where-Object kind -EQ 'fishing-reward-presented').Count confirmedBeforePresented=([DateTime]$rewardConfirmation.at -lt [DateTime]$presentation.at) rewardTickets=$ticketMatches faults=@($events | Where-Object kind -EQ 'fault').Count recoveryFishingEvents=@($recoveryEvents | Where-Object kind -Like 'fishing-*').Count recoveryWrites=@($recoveryEvents | Where-Object kind -EQ 'accepted').Count recovered=$recovered recoveredCoreEqualsFinalServer=($recovered.core -ceq $postMain.newData) recoveredFishRawEqualsFinalServer=($recovered.fishRaw -ceq $postMap.'10223') cacheResults=$cacheResults signupGold=16200;fishingGold=2516;totalGold=18716 independentFormulaRecomputed=$false precastIndependentBackupCaptured=$false boundary='用户在主会话尚未完成动作前快照时手动签到并抛竿;precast/live-gates实际采样晚于扣费,不能充当动作前基线。保存完整审计和服务器前后值,交独立复核裁决证据是否充分,不自动重跑。鱼TotalNum由新进程KV有效值读回验证;活对象直接验证重量/等级/NewFish,未通过私有反射读取TotalNum。' } $report | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath "$root/KVFishingRewardTests/fourth-reconciliation.json" -Encoding utf8 $checks=@( $report.api -ceq 'http://s101.local:18080/' $report.consumeCount -eq 1 $report.rewardConfirmedCount -eq 1 $report.rewardPresentedCount -eq 1 $report.confirmedBeforePresented $report.faults -eq 0 $report.recoveryFishingEvents -eq 0 $report.rewardToQuitChangedKeys.Count -eq 0 $report.quitToRecoveryFinalChangedKeys.Count -eq 0 $report.recoveredCoreEqualsFinalServer $report.recoveredFishRawEqualsFinalServer @($ticketMatches | Where-Object { -not $_.matchesFinalServer }).Count -eq 0 @($cacheResults | Where-Object { $_.pending -ne 0 -or $_.unknown -or $_.active -or $_.baselineChangedKeys.Count -ne 0 }).Count -eq 0 ) if($checks -contains $false) { throw 'Reconciliation assertion failed; inspect fourth-reconciliation.json' } [pscustomobject]@{checks=$checks.Count;passed=$true;changedKeys=$report.changedKeys;changedFishIds=$report.changedFishIds;recoveryUnchanged=$true} | ConvertTo-Json -Depth 4