Repository navigation
feat(vk): observe completed GPU write-back differences - #2131
Conversation
9acbfa8 to
d67505a
Compare
|
All current-head checks are green at |
bb9944f to
6d53c2a
Compare
Joey-Magnus
left a comment
There was a problem hiding this comment.
This is careful work, and it stays inside what it claims: with RX_VK_WB_OBSERVE unset, the only changes on the hot path are a uniform branch in the shader, an 8-byte-larger push constant and an early return in wbd_table_get. I checked the parts that could hurt players:
- Shader (
gpu/vk/shaders/wb_diff.comp):observecomes from a push constant, and the earlyreturndepends only on the workgroup ID. Bothbarrier()calls are therefore in uniform control flow. Every invocation reaches the final barrier, because both loops run to their bounds. glslang emits them asOpControlBarrier Workgroup Workgroup AcquireRelease|WorkgroupMemory, so the sharedatomicOrresults are visible to invocation 0. On the vector path, the flag followsdiffersper vector, which matches what the vote writes. - Generated header: I regenerated
wb_diff_comp_spv.hwith glslang 16.6.0 using the command in the shader, and it is identical to the committed one. - Lifetime (
gpu/vk/vk_memory.cpp): results are read only inwbd_table_getoncet.value <= mem_completed, which is the same gate the ring already uses for reuse. They are read before the slot can grow, be freed or be overwritten. The compute→host barrier plusinvalidatecover non-coherent memory. The0xffsentinel makes a missing store invalidate the whole batch instead of reading it as clean. The growth-failure fallback keeps the old behaviour: retry at the ordinary size, or returnnullptrand do whole copies. - Tests: on the PR's head,
test_wb_*,test_env_onandtest_menu_env_onpass. CI is green.
minor gpu/vk/vk_memory.cpp:3422: RX_VK_WB_OBSERVE is read as atoi(s) != 0. Since #2042, on/off switches go through env_on (this file already includes hookshot_env.h and uses it for RX_VK_LOG_ADDR, RX_VK_TABLE_NOW and others), so as written RX_VK_WB_OBSERVE=on or =true would quietly do nothing. static const bool on = env_on("RX_VK_WB_OBSERVE"); fixes it.
minor docs/worklist.md:36: the row is added to Open with you as owner, which fits "put your name in Owner in the same PR that starts the work". But this PR also finishes that work, and the worklist says a merged PR's row moves to Fixed, waiting for a release. Put it there with this PR's link, or nobody will clear it.
minor: the branch conflicts with main in docs/tested-configurations.md (rows added on top). The description could also open with a sentence a player could follow, per CONTRIBUTING (for example, "Nobody knows how much of each write-back actually changed, so we can't tell whether skipping some would be safe"). The current summary gets there, but in its second sentence and in renderer terms.
7ee371f to
fb8d0c5
Compare
|
Thanks for the detailed review. Addressed in The lifetime fixture covers ten off/on forms and five rejected mutations, including restoring the numeric-only parser; shared switch and menu-switch checks pass on Windows and Linux. Shader source and SPIR-V are unchanged from the RTX-tested |
Add opt-in actual-difference feedback tied to retired chunk-table submissions, with bounded diagnostics and ordinary write-back fallback. Exercise authored GPU buffers and production host lifetime/failure paths; document measured native controls and limits. Assisted-by: Codex
Keep strict compiler diagnostics and lifecycle assertions while matching the Windows CRT declaration configuration used by the other production-function fixtures. Assisted-by: Codex
Assisted-by: OpenAI Codex
Assisted-by: OpenAI Codex
fb8d0c5 to
da2747e
Compare
Joey-Magnus
left a comment
There was a problem hiding this comment.
All three earlier points are fixed in the head: env_on("RX_VK_WB_OBSERVE") at vk_memory.cpp:3428, the worklist row moved to Fixed, and the description opens with why. With the switch off, the hot path only gains a uniform branch, 8 bytes of push constant and an early return. Good once the queued CI is green.
NotRexed#2131's check (test_wb_observation_lifetime) measures the production observation lifetime by cutting vk_memory.cpp from `struct WbdChunk {` to `void memory_end_submission(` and compiling the span alone. The trials' definitions sat inside that span, so the cut carried them and their symbols went nowhere (texture_frame_no, present_time_total_ns, Pool, dgpu_trial, dgpu_pool_at all undeclared in it). dgpu_trials and dgpu_pick move to just before WbdChunk: after everything they call (mirror_free, the skip list, the window counters), before everything that calls them, and outside the span the check measures - the span is the write-back machinery's own again, and the check passes untouched. Co-authored-by: GLM-5.3-Flash <glm-5.3-flash@opencode.noreply.github.com>
A dGPU has its own fast memory and a slower bus to system RAM; stock mirrors every guest pool into VRAM and keeps both copies coherent with captures, shadows and write-backs. At 4K that coherence is CPU-bound: the write-back diff compares ~3.1 GB per 600 submits and mirror syncs move ~137k dirty pages, for 35.6 fps where 1080p runs 60 (issue NotRexed#2099). Under RX_VK_DGPU=1 a pool's first binding makes no mirror (MIRROR_REFUSED, the state iGPU runs use today): reads take the lock-free TLS path and GPU reads/writes land in the imported guest memory, coherent by construction - no mirror sync, no write-back. Every 600-submit window the heaviest guest-memory-read pool with >=1 MB of reads and not write-dominated runs a measured trial (120 frames direct -> 12 settle, held until the mirror is really on -> 120 mirrored, on the recordings' own timestamps); the mirror stays only when the win is both >10% (RX_VK_DGPU_GAIN) and >0.25 ms (RX_VK_DGPU_FLOOR) on present-to-present frame time, so the verdict sees the mirror's CPU cost and its PCIe benefit alike. Trials whose windows did not measure the same workload (zero binds, a >2x bind shift, a mirror that never came) abort with no verdict and no skip line; a pool that aborts three times sits out the session, and iGPUs run no trials. A lost verdict persists to hookshot_mirror_skip.txt beside the game's own state files, keyed to the device and driver (a foreign or header-less file is ignored and re-trialled). RX_VK_DGPU_TRIALS=0 is pure direct. Trials need serial replay (documented); under RX_VK_PARALLEL the policy runs trial-less. Unset = stock exactly: the added counters and logging are gated on the switch. Refused pools read uniform buffers as submitted (NotRexed#1795's per-pool form: the snapshot engages for mirror-less pools at the read, trial-won pools keep in-place mirror binds), so the artifact class the issue documents on mirror-less GPUs is covered under the policy too. Measured on RX 9070 XT / RADV, 4K, Uncapped frame cap, warp-normalized adjacent pairs (Hyrule Field): 70.2->78.0, 71.0->77.0, 59.6->76.6 fps (+9.7% median), draw parity 705 vs 709, GPU 9.2 ms of a ~14 ms frame. Mirror-sync dirty pages 136,872 -> 0; write-back diff 3.06-3.13 GB -> 330 MB or less per 600 submits. Review round 2 (external): RX_VK_DGPU_TRIALS=0's predicate was inverted and now matches the docs (=0 is pure direct); the window's read counter moved off a shared map into the pool (dgpu_read, both direct paths); Settle holds until the mirror is actually live; zero-bind baselines and >2x bind drift between the direct and mirrored windows abort the trial un-blacklisted; keep_line refuses signed and trailing-garbage lines. Review round 3 (hckrman101, Joey-Magnus): refused pools read UBs as submitted (NotRexed#1795 per-pool, live-smoked at 1.26M copies fault-free); verdicts decide on present-to-present frame time instead of GPU-only time (the mirror's CPU and upload-buffer cost is what the frame pays); the policy runs trial-less under RX_VK_PARALLEL and its fast-path counters are parallel-safe; no trials on iGPUs and a 3-abort cap; the skip list carries a device+driver header and ignores foreign files; the test driver uses {cxx}. Rebased onto main's env_on switch rule (NotRexed#2042) and the split-submission fence work (NotRexed#2419); the trials' functions sit outside the write-back observation's compile-span check (NotRexed#2131) and the GCN mirror-less default (NotRexed#2343) composes unchanged (on GCN the switch runs trial-less direct plus the UB snapshot). Co-authored-by: GLM-5.3-Flash <glm-5.3-flash@opencode.noreply.github.com>
A dGPU has its own fast memory and a slower bus to system RAM; stock mirrors every guest pool into VRAM and keeps both copies coherent with captures, shadows and write-backs. At 4K that coherence is CPU-bound: the write-back diff compares ~3.1 GB per 600 submits and mirror syncs move ~137k dirty pages, for 35.6 fps where 1080p runs 60 (issue NotRexed#2099). Under RX_VK_DGPU=1 a pool's first binding makes no mirror (MIRROR_REFUSED, the state iGPU runs use today): reads take the lock-free TLS path and GPU reads/writes land in the imported guest memory, coherent by construction - no mirror sync, no write-back. Every 600-submit window the heaviest guest-memory-read pool with >=1 MB of reads and not write-dominated runs a measured trial (120 frames direct -> 12 settle, held until the mirror is really on -> 120 mirrored, on the recordings' own timestamps); the mirror stays only when the win is both >10% (RX_VK_DGPU_GAIN) and >0.25 ms (RX_VK_DGPU_FLOOR) on present-to-present frame time, so the verdict sees the mirror's CPU cost and its PCIe benefit alike. Trials whose windows did not measure the same workload (zero binds, a >2x bind shift, a mirror that never came) abort with no verdict and no skip line; a pool that aborts three times sits out the session, and iGPUs run no trials. A lost verdict persists to hookshot_mirror_skip.txt beside the game's own state files, keyed to the device and driver (a foreign or header-less file is ignored and re-trialled). RX_VK_DGPU_TRIALS=0 is pure direct. Trials need serial replay (documented); under RX_VK_PARALLEL the policy runs trial-less. Unset = stock exactly: the added counters and logging are gated on the switch. Refused pools read uniform buffers as submitted (NotRexed#1795's per-pool form: the snapshot engages for mirror-less pools at the read, trial-won pools keep in-place mirror binds), so the artifact class the issue documents on mirror-less GPUs is covered under the policy too. Measured on RX 9070 XT / RADV, 4K, Uncapped frame cap, warp-normalized adjacent pairs (Hyrule Field): 70.2->78.0, 71.0->77.0, 59.6->76.6 fps (+9.7% median), draw parity 705 vs 709, GPU 9.2 ms of a ~14 ms frame. Mirror-sync dirty pages 136,872 -> 0; write-back diff 3.06-3.13 GB -> 330 MB or less per 600 submits. Review round 2 (external): RX_VK_DGPU_TRIALS=0's predicate was inverted and now matches the docs (=0 is pure direct); the window's read counter moved off a shared map into the pool (dgpu_read, both direct paths); Settle holds until the mirror is actually live; zero-bind baselines and >2x bind drift between the direct and mirrored windows abort the trial un-blacklisted; keep_line refuses signed and trailing-garbage lines. Review round 3 (hckrman101, Joey-Magnus): refused pools read UBs as submitted (NotRexed#1795 per-pool, live-smoked at 1.26M copies fault-free); verdicts decide on present-to-present frame time instead of GPU-only time (the mirror's CPU and upload-buffer cost is what the frame pays); the policy runs trial-less under RX_VK_PARALLEL and its fast-path counters are parallel-safe; no trials on iGPUs and a 3-abort cap; the skip list carries a device+driver header and ignores foreign files; the test driver uses {cxx}. Follow-up fix: the drift check counted served volume, so a working mirror's read collapse voided 40+ trials a run and trials never converged; binds now count once at the serving path and the drift rule compares the scene's pool set, with a three-no-verdict cap. Rebased onto main's env_on switch rule (NotRexed#2042) and the split-submission fence work (NotRexed#2419); the trials' functions sit outside the write-back observation's compile-span check (NotRexed#2131) and the GCN mirror-less default (NotRexed#2343) composes unchanged (on GCN the switch runs trial-less direct plus the UB snapshot). Co-authored-by: GLM-5.3-Flash <glm-5.3-flash@opencode.noreply.github.com>
NotRexed#2131's check (test_wb_observation_lifetime) measures the production observation lifetime by cutting vk_memory.cpp from `struct WbdChunk {` to `void memory_end_submission(` and compiling the span alone. The trials' definitions sat inside that span, so the cut carried them and their symbols went nowhere (texture_frame_no, present_time_total_ns, Pool, dgpu_trial, dgpu_pool_at all undeclared in it). dgpu_trials and dgpu_pick move to just before WbdChunk: after everything they call (mirror_free, the skip list, the window counters), before everything that calls them, and outside the span the check measures - the span is the write-back machinery's own again, and the check passes untouched. Co-authored-by: GLM-5.3-Flash <glm-5.3-flash@opencode.noreply.github.com>
A dGPU has its own fast memory and a slower bus to system RAM; stock mirrors every guest pool into VRAM and keeps both copies coherent with captures, shadows and write-backs. At 4K that coherence is CPU-bound: the write-back diff compares ~3.1 GB per 600 submits and mirror syncs move ~137k dirty pages, for 35.6 fps where 1080p runs 60 (issue NotRexed#2099). Under RX_VK_DGPU=1 a pool's first binding makes no mirror (MIRROR_REFUSED, the state iGPU runs use today): reads take the lock-free TLS path and GPU reads/writes land in the imported guest memory, coherent by construction - no mirror sync, no write-back. Every 600-submit window the heaviest guest-memory-read pool with >=1 MB of reads and not write-dominated runs a measured trial (120 frames direct -> 12 settle, held until the mirror is really on -> 120 mirrored, on the recordings' own timestamps); the mirror stays only when the win is both >10% (RX_VK_DGPU_GAIN) and >0.25 ms (RX_VK_DGPU_FLOOR) on present-to-present frame time, so the verdict sees the mirror's CPU cost and its PCIe benefit alike. Trials whose windows did not measure the same workload (zero binds, a >2x bind shift, a mirror that never came) abort with no verdict and no skip line; a pool that aborts three times sits out the session, and iGPUs run no trials. A lost verdict persists to hookshot_mirror_skip.txt beside the game's own state files, keyed to the device and driver (a foreign or header-less file is ignored and re-trialled). RX_VK_DGPU_TRIALS=0 is pure direct. Trials need serial replay (documented); under RX_VK_PARALLEL the policy runs trial-less. Unset = stock exactly: the added counters and logging are gated on the switch. Refused pools read uniform buffers as submitted (NotRexed#1795's per-pool form: the snapshot engages for mirror-less pools at the read, trial-won pools keep in-place mirror binds), so the artifact class the issue documents on mirror-less GPUs is covered under the policy too. Measured on RX 9070 XT / RADV, 4K, Uncapped frame cap, warp-normalized adjacent pairs (Hyrule Field): 70.2->78.0, 71.0->77.0, 59.6->76.6 fps (+9.7% median), draw parity 705 vs 709, GPU 9.2 ms of a ~14 ms frame. Mirror-sync dirty pages 136,872 -> 0; write-back diff 3.06-3.13 GB -> 330 MB or less per 600 submits. Review round 2 (external): RX_VK_DGPU_TRIALS=0's predicate was inverted and now matches the docs (=0 is pure direct); the window's read counter moved off a shared map into the pool (dgpu_read, both direct paths); Settle holds until the mirror is actually live; zero-bind baselines and >2x bind drift between the direct and mirrored windows abort the trial un-blacklisted; keep_line refuses signed and trailing-garbage lines. Review round 3 (hckrman101, Joey-Magnus): refused pools read UBs as submitted (NotRexed#1795 per-pool, live-smoked at 1.26M copies fault-free); verdicts decide on present-to-present frame time instead of GPU-only time (the mirror's CPU and upload-buffer cost is what the frame pays); the policy runs trial-less under RX_VK_PARALLEL and its fast-path counters are parallel-safe; no trials on iGPUs and a 3-abort cap; the skip list carries a device+driver header and ignores foreign files; the test driver uses {cxx}. Follow-up fix: the drift check counted served volume, so a working mirror's read collapse voided 40+ trials a run and trials never converged; binds now count once at the serving path and the drift rule compares the scene's pool set, with a three-no-verdict cap. Rebased onto main's env_on switch rule (NotRexed#2042) and the split-submission fence work (NotRexed#2419); the trials' functions sit outside the write-back observation's compile-span check (NotRexed#2131) and the GCN mirror-less default (NotRexed#2343) composes unchanged (on GCN the switch runs trial-less direct plus the UB snapshot). After the presenter-thread refactor (NotRexed#2325) the present-to-present tick lives in present_finish, which every made present passes exactly once in either threading mode, with a headless twin so offscreen runs stay measurable. Co-authored-by: GLM-5.3-Flash <glm-5.3-flash@opencode.noreply.github.com>
A dGPU has its own fast memory and a slower bus to system RAM; stock mirrors every guest pool into VRAM and keeps both copies coherent with captures, shadows and write-backs. At 4K that coherence is CPU-bound: the write-back diff compares ~3.1 GB per 600 submits and mirror syncs move ~137k dirty pages, for 35.6 fps where 1080p runs 60 (issue NotRexed#2099). Under RX_VK_DGPU=1 a pool's first binding makes no mirror (MIRROR_REFUSED, the state iGPU runs use today): reads take the lock-free TLS path and GPU reads/writes land in the imported guest memory, coherent by construction - no mirror sync, no write-back. Every 600-submit window the heaviest guest-memory-read pool with >=1 MB of reads and not write-dominated runs a measured trial (120 frames direct -> 12 settle, held until the mirror is really on -> 120 mirrored, on the recordings' own timestamps); the mirror stays only when the win is both >10% (RX_VK_DGPU_GAIN) and >0.25 ms (RX_VK_DGPU_FLOOR) on present-to-present frame time, so the verdict sees the mirror's CPU cost and its PCIe benefit alike. Trials whose windows did not measure the same workload (zero binds, a >2x bind shift, a mirror that never came) abort with no verdict and no skip line; a pool that aborts three times sits out the session, and iGPUs run no trials. A lost verdict persists to hookshot_mirror_skip.txt beside the game's own state files, keyed to the device and driver (a foreign or header-less file is ignored and re-trialled). RX_VK_DGPU_TRIALS=0 is pure direct. Trials need serial replay (documented); under RX_VK_PARALLEL the policy runs trial-less. Unset = stock exactly: the added counters and logging are gated on the switch. Refused pools read uniform buffers as submitted (NotRexed#1795's per-pool form: the snapshot engages for mirror-less pools at the read, trial-won pools keep in-place mirror binds), so the artifact class the issue documents on mirror-less GPUs is covered under the policy too. Measured on RX 9070 XT / RADV, 4K, Uncapped frame cap, warp-normalized adjacent pairs (Hyrule Field): 70.2->78.0, 71.0->77.0, 59.6->76.6 fps (+9.7% median), draw parity 705 vs 709, GPU 9.2 ms of a ~14 ms frame. Mirror-sync dirty pages 136,872 -> 0; write-back diff 3.06-3.13 GB -> 330 MB or less per 600 submits. Review round 2 (external): RX_VK_DGPU_TRIALS=0's predicate was inverted and now matches the docs (=0 is pure direct); the window's read counter moved off a shared map into the pool (dgpu_read, both direct paths); Settle holds until the mirror is actually live; zero-bind baselines and >2x bind drift between the direct and mirrored windows abort the trial un-blacklisted; keep_line refuses signed and trailing-garbage lines. Review round 3 (hckrman101, Joey-Magnus): refused pools read UBs as submitted (NotRexed#1795 per-pool, live-smoked at 1.26M copies fault-free); verdicts decide on present-to-present frame time instead of GPU-only time (the mirror's CPU and upload-buffer cost is what the frame pays); the policy runs trial-less under RX_VK_PARALLEL and its fast-path counters are parallel-safe; no trials on iGPUs and a 3-abort cap; the skip list carries a device+driver header and ignores foreign files; the test driver uses {cxx}. Follow-up fix: the drift check counted served volume, so a working mirror's read collapse voided 40+ trials a run and trials never converged; binds now count once at the serving path and the drift rule compares the scene's pool set, with a three-no-verdict cap. The set rule still voided every trial in a streaming scene, so anchors now match by guest range (a recycled pool is the same anchor), anchor at 4x the floor, void only when a substantial fraction of the scene moves, and the candidate floor rose to 256 MB of reads a window (the verdict floor is unreachable below it - a trial there is churn by construction). Rebased onto main's env_on switch rule (NotRexed#2042) and the split-submission fence work (NotRexed#2419); the trials' functions sit outside the write-back observation's compile-span check (NotRexed#2131) and the GCN mirror-less default (NotRexed#2343) composes unchanged (on GCN the switch runs trial-less direct plus the UB snapshot). After the presenter-thread refactor (NotRexed#2325) the present-to-present tick lives in present_finish, which every made present passes exactly once in either threading mode, with a headless twin so offscreen runs stay measurable. Co-authored-by: GLM-5.3-Flash <glm-5.3-flash@opencode.noreply.github.com>
…ifetime and refused imports Port upstream 8b9e549 (NotRexed#2131): RX_VK_WB_OBSERVE=1 exports one actual-difference flag per write-back chunk from shaders/wb_diff.comp, appended to the existing mapped table. Results are read only once the submission that wrote them is done, before the slot is reused; missing, out-of-bounds or invalid results reject the whole batch and keep the ordinary write-back. The 600-submission report adds the completed batches, chunks, changed chunks and changed-chunk bytes, with no extra submission, wait or thread. The SPIR-V header is regenerated from the shader with the pinned glslang 16.6.0 (exact match checked). Port upstream f1d0a67 (NotRexed#2767): a pool whose write-back was worth a 256 KB shadow keeps Pool::shadow_wanted after VRAM pressure takes it, and shadows_check_room makes wanted shadows again (up to 8 a check) once the pressure rule says there is room. A pool whose write-backs stay under the make size for good after a scene change no longer stays a whole copy for the session. Port upstream c2e9ed5 (NotRexed#2795): a retired pool whose mirror was refused is given back like a MIRROR_NONE pool. It has no mirror either, and its import otherwise stayed pinned for the rest of the session. Game-free fixtures, run by builder/rust/tests/vk_memory_fixtures.rs and directly with a C++ compiler: page ranges and part matching (wb_pages.h), fence/timeline ordering (rec_value.h part map), shadow lifetime (wb_shadow_policy.h), retired refused-mirror give-back, and completed observation lifetime (wb_observe.h). tests/wb_diff_observations_gpu.cpp executes the generated shader on a real Vulkan 1.3 device (NVIDIA RTX 4070 Ti here: off/mixed/reused/zero/65535-wide cases pass).
Summary
There is no measurement of how much copied GPU data actually changed, so it is hard to judge whether skipping copies would be safe. This adds optional observations of completed write-backs. It changes no copy decisions and claims no performance improvement.
Coordinated with blessedplummer in #2099; their dirty-origin, urgent/bulk and sparse-mirror work remains separate. Dev-work claim.
Technical changes
RX_VK_WB_OBSERVE=1; regenerate SPIR-V with the pinned glslang 16.6.0.Validation
Current tested head
da2747e1, rebased onto main940eb9ef. The tested-configurations conflict preserves the new main evidence rows and this PR’s earlier benchmark. Production/test blobs are unchanged fromfb8d0c55; focused checks pass again on both systems. Review follow-up uses the sharedenv_onrule, soonandtrueenable observations and off spellings disable them. Production lifetime checks pass for ten unset/off/on forms; all five causal mutations are rejected. Shared C/C++/Python switch and menu-switch checks pass on Windows and Linux. The worklist entry is in Fixed, waiting for a release. Shader source and generated SPIR-V are identical to the GPU-tested7ee371f8; no new game/runtime or overhead result is claimed. Fresh hosted checks are separate exact-head evidence.Earlier validation evidence and limits
At
7ee371f8on6c8c1bfb, four CPU/page checks, lifetime/four mutations and all five actual RTX shader scenarios passed; the main shader passed off parity and rejected missing feedback. Both tested-configuration rows were retained during rebase.Head
6d53c2a7on main4a53c897passed the same focused Windows CPU/lifetime and actual RTX shader checks before this documentation-only rebase.Earlier tested head
bb9944f2, rebased onto main6df80a8d. Both production/test patches are unchanged by range-diff; the documentation conflict preserves both evidence rows.Earlier authored shader execution passed on Linux llvmpipe; lifetime tests and four mutations passed on both platforms. At earlier head
2fe9e4c0, full Linux was 352 / 0 failed / 23 skipped, Windows 341 / 4 failed / 30 skipped: three failures reproduced on the exact base, and one unchanged compiler fixture timed out then passed isolated. Fourteen release-script cases subsequently passed with jq; the aggregate's earlier skipped internal cases are not counted as executed.Six earlier native processes used the
fab8cb37host with candidate edits, clean/off/on/on/off/clean, Kokiri balcony at 4K, FG off and cap 144. Mean source FPS: clean 75.25, off 75.52, on 74.24. On was 1.70% below off; two windows per mode do not establish equivalence or precise overhead. Reports found differences in about 15% of chunks, covering about 13% of compared volume, with zero unavailable chunks. All six exited normally and preserved the checkpoint. These are earlier-host results, not validation of the current rebase.No AMD hardware, physical noncoherent-memory or campaign coverage is claimed. No game-derived files are included.