diff --git a/docs/tomes/05-render.md b/docs/tomes/05-render.md index e0970c7..faf9dfc 100644 --- a/docs/tomes/05-render.md +++ b/docs/tomes/05-render.md @@ -1750,6 +1750,18 @@ state, not a persistent cache identity or a WEAR row. The observation narrows the next terrain task to profiling the cache's generated entries and their consumer; it still does not establish a Land1 blend equation. +The probe now also reads the bounded eight-byte entry table and only hashes +the fixed 212-byte profile banks it observes; raw process bytes never enter +the repository. The same AutoDemo cache had 3,426 materialized record pointers +among 3,462 entries and referenced banks `0..4`. Two consecutive captures +kept that count, bank set, and all five FNV-1a-64 bank hashes stable: +`9426539192111566946`, `2915543853261903601`, `13156371703608727216`, +`11280405573172155968`, and `18234288329145104612`. Their result views changed +from `0x008A565C` to `0x008A55A8`. Thus the generated cache/profile state is +stable over these render samples while the shared result view is not. This +still identifies neither the record-field semantics nor the final terrain +blend/pass operation. + ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File ` tools\capture-terrain-shade-cache.ps1 -ProcessId diff --git a/tools/capture-terrain-shade-cache.ps1 b/tools/capture-terrain-shade-cache.ps1 index 7de5f9c..5c7b10d 100644 --- a/tools/capture-terrain-shade-cache.ps1 +++ b/tools/capture-terrain-shade-cache.ps1 @@ -78,6 +78,17 @@ public static class FparkanTerrainShadeProbe { } return -1; } + + public static ulong Fnv1a64(byte[] bytes, int offset, int length) { + ulong hash = 14695981039346656037UL; + unchecked { + for (int index = offset; index < offset + length; index++) { + hash ^= bytes[index]; + hash *= 1099511628211UL; + } + } + return hash; + } } '@ @@ -175,6 +186,45 @@ function Find-ShadeCache { return $null } +function Get-ShadeCacheSummary { + param([IntPtr]$Process, [Int64]$Cache, [byte[]]$Header) + $entryTable = [BitConverter]::ToUInt32($Header, 316) + $entryCount = [BitConverter]::ToUInt32($Header, 320) + $summary = [ordered]@{ + materialized_entries = $null + profile_banks = @() + } + # The count field is runtime data. Keep a hard observer bound so a corrupt + # or stale candidate cannot turn a passive sample into an excessive read. + if ($entryTable -lt 0x1000 -or $entryCount -gt 100000) { return $summary } + $entryBytes = Read-Bytes $Process $entryTable ([int]($entryCount * 8)) + if ($null -eq $entryBytes) { return $summary } + $materialized = 0 + $banks = [System.Collections.Generic.SortedSet[int]]::new() + for ($index = 0; $index -lt $entryCount; $index++) { + $offset = $index * 8 + if ([BitConverter]::ToUInt32($entryBytes, $offset) -ge 0x1000) { + $materialized++ + [void]$banks.Add([int]$entryBytes[$offset + 4]) + } + } + $summary.materialized_entries = $materialized + if ($banks.Count -eq 0) { return $summary } + $maxBank = $banks.Max + if ($maxBank -ge 100) { return $summary } + $bankBytes = Read-Bytes $Process ($Cache + 332) (($maxBank + 1) * 212) + if ($null -eq $bankBytes) { return $summary } + $profiles = [System.Collections.Generic.List[object]]::new() + foreach ($bank in $banks) { + $profiles.Add([ordered]@{ + bank = $bank + fnv1a64 = [FparkanTerrainShadeProbe]::Fnv1a64($bankBytes, $bank * 212, 212).ToString() + }) + } + $summary.profile_banks = @($profiles) + return $summary +} + if ($SearchEnd -le $SearchStart) { throw 'SearchEnd must exceed SearchStart' } $terrainBase = Get-TerrainModuleBase $ProcessId $process = [FparkanTerrainShadeProbe]::OpenProcess( @@ -194,6 +244,7 @@ try { if ($null -ne $cache) { $header = Read-Bytes $process $cache 324 if ($null -ne $header) { + $summary = Get-ShadeCacheSummary $process $cache $header [ordered]@{ schema = 'fparkan-terrain-shade-cache-v1' process_id = $ProcessId @@ -203,6 +254,8 @@ try { result_view = ('0x{0:X8}' -f [BitConverter]::ToUInt32($header, 24)) entry_table = ('0x{0:X8}' -f [BitConverter]::ToUInt32($header, 316)) entry_count = [BitConverter]::ToUInt32($header, 320) + materialized_entries = $summary.materialized_entries + profile_banks = $summary.profile_banks scan_attempt = $attempt } | ConvertTo-Json -Compress exit 0