31 KiB
P3 Temporal Reconstruction Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Deliver selectable 1:1 TAA and, after it passes moving-image acceptance, output-resolution-history temporal upscaling for Direct and P2 visibility paths, with explicit fallback and measurable image quality.
Architecture: Keep the existing Off render branch as a reference. Add independent temporal history and per-instance previous transforms; raster the scene to internal color/depth/velocity, resolve against output-resolution history, then composite sharp UI into the existing final color image. HZB and temporal history share no validity flag, while using the same current scene depth and jittered raster projection.
Tech Stack: C++20, Vulkan 1.3 dynamic rendering and synchronization2, Slang/SPIR-V, CMake/Ninja, SDL3, headless GPU image tests, Khronos validation, Linux/Windows software-Vulkan CI.
Spec: docs/superpowers/specs/2026-09-24-p3-temporal-design.md
Prerequisite checkpoint: Complete Task 2 of docs/superpowers/plans/2026-09-24-p3-lighting.md through the green commit Share typed multi-light shading across Direct and GPU paths before the temporal plan changes renderer.hpp, renderer.cpp, baseline.slang, gpu_scene.slang or shader-contract validation. Pure temporal policy/test drafting can happen earlier; shared renderer integration cannot. Re-run Direct/P2 lighting reflection, hot-reload and image tests after each temporal shader or pipeline change.
Global Constraints
TemporalMode::Offand output-resolutioncolorremain the defaults and reference output; Direct visibility must work with TAA and Upscale.- Preserve the lighting graphics ABI: material set 0 bindings 0–3, frame-lighting set 1, GPU graphics scene set 2 bindings 0–2, unchanged 96/112-byte graphics push blocks; P2 compute sets remain set 0.
Snapshot::view_projection, projection,scene_rect, picking and UI coordinates remain unjittered and in output pixels.- HZB history and temporal color history have independent validity; GPU occlusion remains correct through temporal mode/scale switches.
- A missing previous transform, anonymous draw, mesh/LOD identity change, camera cut, changed view/projection/scene rectangle, resize or incompatible shader generation cannot reuse stale color history.
- World transparency/sprites retain depth and order, but their composited pixels reject temporal color history; UI is rendered at output resolution after resolve.
- Shadow views and light-space matrices are never jittered or scaled by temporal rendering.
- Player bundles contain complete checked SPIR-V and reflection metadata; no Slang compiler or Editor/MCP service is required at runtime.
- Unsupported temporal capabilities must use Off with an exposed effective mode and reason, never a silent path change.
- Every new pass receives a GPU label and timing; full-frame measurements include the existing synchronous readback cost unless explicitly excluded in a matched experiment.
Review Focus
- Direct mode while TAA is enabled: a stable opaque instance must gain a valid prior transform on frame two without requiring HZB; Task 1 and Task 4 test this.
- GPU PostRaster after an object emerges from occlusion: its color and velocity must exist before temporal resolve so the same final frame shows it; Tasks 3 and 4 test this.
- Editor scene rectangle and UI: an offset/odd-sized scene viewport may scale internally, but text and buttons must remain pixel-exact in final output; Tasks 3 and 5 test this.
- A translucent object moving across an opaque surface: its pixels must not borrow the underlying opaque motion/history; Tasks 3 and 4 test this.
- Shader reload, format fallback and relocated exports: a partial bundle or unsupported format cannot produce a half-active temporal path; Task 6 tests this.
Task 1: Independent temporal history policy and public mode
Files: Create include/faset/render/temporal.hpp, src/render/temporal.cpp, tests/render_temporal_policy_tests.cpp; modify include/faset/render/renderer.hpp, src/render/renderer.cpp, cmake/Renderer.cmake.
Interfaces: Add enum class TemporalMode { Off, TAA, Upscale };, RendererConfig::temporal_mode, RendererConfig::render_scale, Renderer::set_temporal_mode(TemporalMode, float), Renderer::temporal_mode(), FrameStats::requested_temporal_mode, effective_temporal_mode, temporal_history_valid, temporal_reset_reason, and temporal_internal_width/height. Define TemporalResetReason { None, FirstFrame, CameraCut, CameraDiscontinuity, ViewChanged, ViewportChanged, ProjectionChanged, Resize, ModeChanged, ScaleChanged, ShaderReload, Unsupported } and TemporalCapabilities { bool compute, formats, extent; }. A pure evaluate_temporal_history(previous, current) -> TemporalHistoryDecision compares TemporalHistoryKey values containing view ID, output/internal extent, scene rectangle, unjittered projection, mode, shader generation, camera eye/current VP and explicit cut. temporal_jitter(frameIndex, viewportWidth, viewportHeight) returns a deterministic Halton(2,3) clip offset. Add select_effective_temporal_mode(requested, capabilities) as a pure policy function. Shared graphics descriptors remain material set 0, lighting set 1, GPU scene set 2; this task does not reallocate those bindings.
- Step 1: Write failing policy tests and register their CMake target. In a Direct visibility configuration, a same-view second frame returns
valid=trueeven when HZB is absent. Test first frame, cut, view switch, changed projection, odd scene rectangle, resize, mode/scale change, shader generation, a camera teleport and unsupported format/compute capabilities. Addfaset_render_temporal_policy_tests/render_temporal_policytocmake/Renderer.cmakebefore the RED build. Example contract:auto decision = evaluate_temporal_history(previous, current); require(decision.valid && decision.reason == TemporalResetReason::None); current.camera_cut = true; require(!evaluate_temporal_history(previous, current).valid); require(select_effective_temporal_mode(TemporalMode::TAA, {false, true}) == TemporalMode::Off); - Step 2: Run the focused target and record the intended missing-interface failure. Reconfigure with
cmake --preset linux-debug, then runcmake --build --preset linux-debug --target faset_render_temporal_policy_tests -j 4; it must fail compiling the new test against the absent temporal API, not withunknown target. After implementation,ctest --test-dir build/linux-debug --no-tests=error -R '^render_temporal_policy$'runs its behavioral assertions. - Step 3: Implement the pure policy and attach it to rendered-frame completion. Keep
scene.hzb_history_validand temporal history fields separate; do not callInstanceTracker::invalidate_view()merely because P2 occlusion is inactive. Reject invalid scale (TAArequires1,Upscalerequires[0.5, 1)) withstd::invalid_argument. Define camera teleport conservatively using eye displacement and an unjittered VP discontinuity, and expose the reason throughFrameStats.const bool hzb_compatible = evaluate_hzb_history(...); const auto temporal = evaluate_temporal_history(previous_temporal, current_temporal); gpu_frame.view.flags[0] = hzb_compatible ? 1u : 0u; statistics.temporal_history_valid = temporal.valid; - Step 4: Run the CPU tests and existing P2 policy tests.
ctest --test-dir build/linux-debug --output-on-failure -R 'render_temporal_policy|render_visibility_policy|render_gpu_shader_contract'must pass. Check that the default constructor still selects Off. - Step 5: Commit.
git add include/faset/render/temporal.hpp src/render/temporal.cpp tests/render_temporal_policy_tests.cpp include/faset/render/renderer.hpp src/render/renderer.cpp cmake/Renderer.cmake && git commit -m "Define independent temporal history and mode policy".
Task 2: Previous transforms and checked motion-vector shaders
Files: Modify src/render/renderer.cpp, include/faset/render/visibility.hpp, shaders/baseline.slang, shaders/gpu_scene.slang, cmake/Renderer.cmake, src/render/shader_contract.hpp, src/render/shader_contract.cpp, tests/test_shader_reflection.py, tests/render_gpu_shader_contract_tests.cpp; create tests/render_temporal_motion_tests.cpp.
Interfaces: Extend the GPU SceneInstance/InstanceRecord from 224 to 288 bytes by appending previousModel at offset 224; retain slot/generation metadata at offsets 208–223. metadata.x & 1 means prior HZB eligibility; metadata.x & 2 means prior temporal transform eligibility. A temporal Direct vertex carries previousClip and validity alongside existing current clip and material data. Temporal Direct/GPU vertex entries feed one temporal fragment entry that writes scene color and an R16G16B16A16_SFLOAT target: .xy = currentUV - previousUV, .z = previous clip depth, .w = 1 for valid opaque motion and 0 for reactive/invalid pixels. Add a pure project_motion(current_clip, previous_clip) -> Vec2 CPU oracle with the same sign/space convention. Preserve the post-lighting baseline Off shader entry points and their layout fingerprints; GPU P2 fingerprints change with the checked record stride. Temporal fragment binds material set 0 and lighting set 1; temporal GPU vertex reads instance/visible-ID/view from set 2. Do not move cull/HZB compute off set 0 or increase 96/112-byte graphics push constants.
- Step 1: Write failing shader and motion tests and register their target. Assert the reflected 288-byte storage stride, current/previous clip varyings, velocity attachment output, lighting set 1/GPU scene set 2, and rejection of a tampered SPIR-V/reflection pair. Add
faset_render_temporal_motion_tests/render_temporal_motiontocmake/Renderer.cmake. Testproject_motionagainst a known current/prior clip pair andInstanceTrackerthrough a two-frame rigid object move; include a replaced mesh and anonymous draw whose motion validity is false. GPU image-level motion tests belong to Task 4 after MRT resources exist.auto previous = tracker.update("cube", mesh, model_a, bounds_a, "main"); tracker.finish_frame(); auto moved = tracker.update("cube", mesh, model_b, bounds_b, "main"); require(moved.previous_valid && moved.previous_model == model_a); - Step 2: Observe the expected pre-implementation failure. Reconfigure, then run
cmake --build --preset linux-debug --target faset_render_temporal_motion_tests -j 4; the new test fails to compile against missing motion interfaces. Build the changed existing tests withcmake --build --preset linux-debug --target faset_render_gpu_shader_contract_tests faset_shaders -j 4, thenctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^(render_shader_reflection|render_gpu_shader_contract)$'must reject the new temporal ABI assertions rather than pass only old cases. - Step 3: Implement both geometry paths and shader contracts. Retain
InstanceUpdatein selected Direct draws; compute the previous clip position from its previous model and previous jittered VP. ExtendGpuVertexonly as needed by temporal shader input locations. GPU temporal vertex multipliespreviousViewProjection * previousModel * localVertex. Current/previous scene clip and validity reach the fragment; encode current-minus-prior local-scene UV, prior clip depth and validity. Make P2 culling read(metadata.x & 1u) != 0, notmetadata.x != 0. Update C++ static assertions and reflection checks together.float4 previousClip = mul(view.previousViewProjection, mul(instance.previousModel, float4(vertex.position, 1))); bool temporalValid = (instance.metadata.x & 2u) != 0u && previousClip.w > 0; // Off still uses vertexMain / gpuVertexMain / fragmentMain. - Step 4: Run reflection, motion and P2 GPU suites under validation.
ctest --test-dir build/linux-debug --output-on-failure -R 'render_temporal_motion|render_shader_reflection|render_gpu_shader_contract|render_gpu_visibility'must pass with zero validation errors. Verify the Off direct/GPU image comparison remains within its established tolerance. - Step 5: Commit.
git add src/render/renderer.cpp include/faset/render/visibility.hpp shaders/baseline.slang shaders/gpu_scene.slang cmake/Renderer.cmake src/render/shader_contract.hpp src/render/shader_contract.cpp tests/test_shader_reflection.py tests/render_gpu_shader_contract_tests.cpp tests/render_temporal_motion_tests.cpp && git commit -m "Emit checked motion vectors for direct and GPU scenes".
Task 3: Scene targets, post-cull ordering and sharp UI boundary
Files: Create shaders/temporal.slang, tests/render_temporal_graph_tests.cpp; modify src/render/renderer.cpp, src/render/shader_contract.hpp, src/render/shader_contract.cpp, shaders/baseline.slang, shaders/gpu_scene.slang, cmake/Renderer.cmake, tests/render_tests.cpp, tests/render_gpu_acceptance_tests.cpp.
Interfaces: Add temporal-only internal sceneColor (R8G8B8A8_UNORM, sampled/color attachment), sceneDepth (D32_SFLOAT, sampled/depth attachment) and sceneVelocity (sampled floating-point/color attachment). Keep existing full-resolution color for capture and presentation. Temporal opaque pipeline variants use color+velocity MRT; transparent/sprite pixels overwrite velocity validity with invalid/reactive state. Split draw_sprites_and_ui so world sprites join the internal scene and UI draws only after resolve. Record names in FrameStats::graph_passes for ordering diagnostics. Main and post-raster graphics pipelines bind material set 0 and lighting set 1, plus scene set 2 only for GPU instances; shadow planning remains unjittered. For TAA, internal and output extents are identical; the scaling of Task 5 follows later.
- Step 1: Write failing graph/image tests and register their target. Add
faset_render_temporal_graph_tests/render_temporal_graphtocmake/Renderer.cmake. Render an odd, offset Editorscene_rect, a textured sprite behind/in front of a mesh, moving translucent geometry and bright UI text/quad. AssertTemporalResolveoccurs afterPostRasterScenein GPU occlusion mode,UIoccurs after resolve, viewport clipping holds, and an unchanged UI pixel matches Off exactly. Check depth and velocity attachment store/load behavior with validation.require(position(pass_names, "PostRasterScene") < position(pass_names, "TemporalResolve")); require(position(pass_names, "TemporalResolve") < position(pass_names, "TemporalComposite")); require(position(pass_names, "TemporalComposite") < position(pass_names, "UI")); require(taa_frame.ui_pixel == off_frame.ui_pixel); - Step 2: Run the focused test and confirm the missing scene/UI split. Reconfigure/build
faset_render_temporal_graph_tests, thenctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^render_temporal_graph$'must fail at the new pass-order/UI assertion. A compile failure on an absentFrameStats::graph_passesfield is also a valid RED result; an unregistered test is not. - Step 3: Refactor the render branch without changing Off output. In the temporal branch, MainRaster clears color/depth/velocity, HZB reads stored scene depth, PostRaster loads all attachments, transparent/sprites retain their scene-depth test and invalidate velocity/reactive pixels, then
TemporalResolve,TemporalCompositeandUIare separate named passes. First make Resolve a spatial copy into a temporaryresolvedColorand Composite a fullscreen draw tocolor; Task 4 replaces the spatial result with temporal accumulation. Explicitly transition color/depth/velocity from attachment to sampled layouts before compute, and resolved output to sampled before composite.// Temporal branch only; Off keeps the established ForwardAndUI path. graph.add("PostRasterScene", {"sceneColor", "sceneDepth", "post_indirect"}, {"sceneColor", "sceneDepth", "sceneVelocity"}, draw_post_scene); graph.add("TemporalResolve", {"sceneColor", "sceneDepth", "sceneVelocity"}, {"resolvedColor"}, resolve_spatial); graph.add("TemporalComposite", {"resolvedColor"}, {"color"}, draw_resolved_scene); graph.add("UI", {"color"}, {"color"}, draw_output_ui); - Step 4: Run GPU image/validation and sprite/UI suites.
ctest --test-dir build/linux-debug --output-on-failure -R 'render_temporal_graph|render_offscreen|render_sprite_alpha|render_gpu_visibility|ui_render'passes. Compare Off captures before/after this commit to ensure no unrequested shading/UI change. - Step 5: Commit.
git add src/render/renderer.cpp src/render/shader_contract.hpp src/render/shader_contract.cpp shaders/baseline.slang shaders/gpu_scene.slang shaders/temporal.slang cmake/Renderer.cmake tests/render_tests.cpp tests/render_gpu_acceptance_tests.cpp tests/render_temporal_graph_tests.cpp && git commit -m "Separate temporal scene raster from output UI".
Task 4: Stable 1:1 TAA with disocclusion rejection
Files: Create tests/render_temporal_acceptance_tests.cpp; modify shaders/temporal.slang, src/render/renderer.cpp, cmake/Renderer.cmake, src/render/shader_contract.hpp, src/render/shader_contract.cpp, tests/test_shader_reflection.py, tests/render_temporal_graph_tests.cpp.
Interfaces: Allocate two output-resolution R16G16B16A16_SFLOAT sampled/storage color histories and two output-resolution R32_SFLOAT sampled/storage depth histories. temporalResolveMain reads current scene color/depth/velocity and prior history, writes current history. temporalComposite* draws the resolved result to the existing RGBA8 final color; it does not reapply tone mapping/gamma because scene shading is already display-referred. Add GPU pass time and a history accepted/rejected diagnostic counter that is read only in diagnostic mode.
- Step 1: Write failing deterministic frame-sequence tests and register their target. Add
faset_render_temporal_acceptance_tests/render_temporal_acceptancetocmake/Renderer.cmake. On Direct and P2 GPU modes, require first-frame rejection and second-frame acceptance. Test static diagonal/wire variance after 16 jitter phases, slow camera pan, moving rigid cube, opening a door, explicit cut, unmarked teleport, mesh/LOD identity change, and UI opacity. Compare each reveal/cut frame to Off at the same camera; old foreground colors must not trail into exposed background. Record tolerances per fixture in the test, not a universal image-perfect claim.auto cut = render_frame(taa, scene_with_cut); auto fresh = render_frame(off, scene_without_history); require(!cut.stats.temporal_history_valid); require(mean_rgb_error(cut.rgba, fresh.rgba, reveal_roi) <= 8.0); - Step 2: Run the acceptance target to see the expected lack of accumulation/rejection. Reconfigure/build the new target, then
ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^render_temporal_acceptance$'must fail in its new history/variance assertions; a zero-tests pass is not evidence. - Step 3: Implement TAA resolve and real history lifetime. Use deterministic Halton jitter, local-scene normalized motion and 3×3 nearest-depth velocity dilation. Reject invalid/out-of-bounds motion and sampled prior depth mismatches, clamp previous color to a current 3×3 neighborhood, reduce its bounded weight for high motion/reactive pixels, and use current color on first/rejected pixels. Write current color/depth to the next history only after a successful submitted frame. Grow the 12-query timestamp pool so every graph pass is timed, and expose separate temporal resolve/composite timings.
float2 previousUV = currentUV - selectedMotion.xy; bool accept = historyValid && selectedMotion.w > 0 && inBounds(previousUV) && depthAgrees(selectedMotion.z, historyDepth.SampleLevel(sampler, previousUV, 0)); float3 resolved = accept ? lerp(current.rgb, clamp(history.rgb, neighborhoodMin, neighborhoodMax), historyWeight) : current.rgb; - Step 4: Run policy, shader and GPU acceptance under validation.
ctest --test-dir build/linux-debug --output-on-failure -R 'render_temporal|render_shader_reflection|render_gpu_visibility|render_offscreen'passes, including Direct/P2. Capture repeatable Off/TAA comparison images and per-pass timing for review; a TAA frame with zero validation errors is not sufficient without the moving-image assertions. - Step 5: Commit.
git add shaders/temporal.slang tests/render_temporal_acceptance_tests.cpp src/render/renderer.cpp cmake/Renderer.cmake src/render/shader_contract.hpp src/render/shader_contract.cpp tests/test_shader_reflection.py tests/render_temporal_graph_tests.cpp && git commit -m "Resolve scene with depth-rejected temporal AA".
Task 5: Lower-resolution scene and output-resolution history
Files: Modify src/render/renderer.cpp, include/faset/render/renderer.hpp, include/faset/render/temporal.hpp, src/render/temporal.cpp, shaders/temporal.slang, tests/render_temporal_policy_tests.cpp, tests/render_temporal_acceptance_tests.cpp, tests/render_gpu_acceptance_tests.cpp.
Interfaces: Upscale computes internal width/height by ceil(outputExtent * renderScale) with a minimum of one pixel; stores history at output extent. It transforms scene_rect into a clipped internal viewport once per frame. P2 HZB, current/post depth and SceneView viewport use these internal values. Resolve maps each output-scene pixel to the internal scene signal and handles depth/velocity boundaries before history reuse. Output UI and capture remain full resolution.
- Step 1: Write failing scale tests. Exercise 320×240 at 0.67, 319×241 with an offset scene rectangle at 0.5, resize, 2D sprites, and toggling 1.0 TAA → 0.67 Upscale → Off. Check reported internal extent, exact UI pixels, HZB reset, history reset reason and no stale edge pixels. Compare a static thin-wire and slow-pan sequence to a full-resolution spatial reference; require measured temporal variance to improve over a nearest-neighbor 0.67 spatial baseline, while reporting image error rather than asserting that all scenes improve.
require(upscaled.stats.temporal_internal_width == 215); require(upscaled.stats.temporal_internal_height == 161); require(upscaled.rgba.size() == 320u * 240u * 4u); require(upscaled.stats.temporal_reset_reason == TemporalResetReason::ScaleChanged); - Step 2: Run the scale test and observe the missing reduced-resolution path. Rebuild
faset_render_temporal_acceptance_testswith the new assertions, thenctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^render_temporal_acceptance$'must fail at the internal extent/scale transition assertion. - Step 3: Implement internal target recreation and upscale sampling. Keep
color/readback output-sized, resize scene color/depth/velocity and HZB to internal size, scale only scene viewport/scissor, and include internal extent in both history keys. Use output-resolution history and depth-aware current sampling at output pixel centers; keep motion in local-scene normalized coordinates. Recreate descriptors after target changes and invalidate both temporal/HZB histories on scale/resize.const auto internal_w = std::max(1u, static_cast<unsigned>(std::ceil(width * scale))); const auto internal_h = std::max(1u, static_cast<unsigned>(std::ceil(height * scale))); // scene_rect is mapped to this extent; UI remains at width x height. - Step 4: Run Direct/frustum/occlusion GPU sequences and baseline UI/resize tests.
ctest --test-dir build/linux-debug --output-on-failure -R 'render_temporal|render_gpu_visibility|render_offscreen|render_sprite_alpha|editor_ui'passes with zero Vulkan validation errors where the validation layer exists. Inspect side-by-side captures on physical GPU and software Vulkan. - Step 5: Commit.
git add src/render/renderer.cpp include/faset/render/renderer.hpp include/faset/render/temporal.hpp src/render/temporal.cpp shaders/temporal.slang tests/render_temporal_policy_tests.cpp tests/render_temporal_acceptance_tests.cpp tests/render_gpu_acceptance_tests.cpp && git commit -m "Reconstruct lower-resolution scenes at output resolution".
Task 6: Editor/Player control, shader reload, package and graceful fallback
Files: Modify src/editor/debug_overlay.cpp, src/editor/build_service.cpp, apps/player_main.cpp, src/render/renderer.cpp, src/render/shader_contract.cpp, cmake/Renderer.cmake, tests/render_reload_tests.cpp, tests/build_service_tests.cpp, tests/player_diagnostics_test.py, docs/manual/editor/profiling.md; add temporal mode usage to the appropriate scripting/manual rendering page.
Interfaces: Editor and Player select Off/TAA/Upscale with scale; the Player profile reports requested/effective mode, fallback/reset reason, internal/output extent, jitter, temporal GPU ms and memory. A complete shader bundle includes all temporal entry points and metadata. Reload is transactional: failure preserves the active set and history, success replaces pipelines and resets history. Device feature checks are separate from P2 scene.available, then select_effective_temporal_mode exposes fallback.
- Step 1: Write failing integration tests. A missing/tampered temporal
.spvor reflection rejects the bundle and exported game; a failed hot reload leaves previous rendered pixels/actual mode intact; successful reload resets history; a pure unsupported-capability fixture selects Off with a specific reason. A relocated 2D and 3D Player profile contains temporal requested/effective and reset fields. Check Editor control state matchesRenderer::temporal_mode().const auto before = renderer.stats().effective_temporal_mode; require(!renderer.reload_shaders(error) && !error.empty()); renderer.render(scene); require(renderer.stats().effective_temporal_mode == before); - Step 2: Run focused integration tests for the expected package/control failure. Build
faset_render_reload_tests,faset_build_service_tests, andfaset_player_diagnostics; thenctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R '^(render_shader_reload|process_and_cook|player_shutdown_diagnostics)$'must execute registered tests and fail in the new temporal assertions. For the optional overlay, configurecmake -S . -B build/p3-temporal-ui -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DBUILD_TESTING=ON -DFASET_DEBUG_IMGUI=ON, buildfaset_debug_overlay_tests, and runctest --test-dir build/p3-temporal-ui --no-tests=error --output-on-failure -R '^editor_debug_overlay$'. Record the specific failing assertions; an absent overlay test is not a pass. - Step 3: Implement controls and packaging. Compile/install temporal shaders in
cmake/Renderer.cmake; update both explicit shader-copy lists insrc/editor/build_service.cpp; validate reflection/strides; stage temporal pipelines before replacing live ones. Query sampled/color/storage/filter format support and compute queue support, report Off fallback explicitly, and avoid allocating history on Off. Add concise English Manual examples for mode choice and reading the profile.stats.requested_temporal_mode = config.temporal_mode; stats.effective_temporal_mode = select_effective_temporal_mode(config.temporal_mode, caps); stats.temporal_status_reason = stats.effective_temporal_mode == TemporalMode::Off ? missing_capability_name(caps) : std::string{}; - Step 4: Run focused tests, both sample exports, strict Manual build and native Windows CI.
ctest --test-dir build/linux-debug --no-tests=error --output-on-failure -R 'render_shader_reload|process_and_cook|player_shutdown_diagnostics|render_temporal',ctest --test-dir build/p3-temporal-ui --no-tests=error --output-on-failure -R '^editor_debug_overlay$', andpython -m mkdocs build --strictpass; relocated exports launch without compiler/source checkout. Record any Windows GPU/validation coverage gap explicitly. - Step 5: Commit.
git add src/editor/debug_overlay.cpp src/editor/build_service.cpp apps/player_main.cpp src/render/renderer.cpp src/render/shader_contract.cpp cmake/Renderer.cmake tests/render_reload_tests.cpp tests/build_service_tests.cpp tests/player_diagnostics_test.py docs/manual && git commit -m "Expose and package temporal reconstruction".
Task 7: Adversarial quality gate, performance record and P3 integration
Files: Add docs/validation/p3-temporal-2026-09-24/README.md and image/measurement artifacts; modify PLAN.md, docs/IMPLEMENTATION.md, docs/ARCHITECTURE.md, docs/manual/editor/profiling.md, temporal tests as findings require.
Interfaces: The evidence dossier binds revision, OS/GPU/driver, exact test commands, mode/scale, output/internal extent, scene sequence, raw image differences, static variance, disocclusion trail length, pass/full-frame time, allocated GPU bytes and feature limits. Lighting/shadow work may use the final sceneColor/depth/velocity boundary but does not mark temporal acceptance by itself.
- Step 1: Run every adversarial sequence against Off and 1:1 TAA before claiming upscaling readiness. A newly exposed surface and camera cut must show current color immediately; thin static geometry should have lower temporal variance without unacceptable trail length. Repeat with 0.67 Upscale against 0.67 spatial and 1.0 full-res references on closed/open scenes. Keep the raw captures and metric script with the dossier.
Sequences: wire-static-16, pan-16, moving-cube-16, door-open-4, cut-2, teleport-2, projection/resize/view-switch, UI+alpha. Modes: Off, TAA 1.0, Upscale 0.67; visibility: Direct, Frustum, Occlusion. - Step 2: Run Debug/Release tests, validation, shader reload, strict docs, software GPU and native CI.
cmake --build --preset linux-debug --parallel 4,ctest --preset linux-debug --output-on-failure,cmake --build --preset linux-release --parallel 4,ctest --test-dir build/linux-release --output-on-failure, and strict MkDocs must pass. Run the selected SwiftShader ICD with temporal GPU tests and relocated exports. If a physical Windows GPU is unavailable, say so. - Step 3: Record matched profiling and image metrics. Use the same scene/camera path and resolution for Off/TAA/Upscale. Report pass GPU ms, full GPU ms, CPU extraction/readback, and live allocation, with raw samples and p50/p95. Report quality metrics on fixed ROIs and representative frames, plus captures for moving thin geometry, disocclusion, UI and transparency; do not turn fixture-specific results into a universal speed/quality claim.
- Step 4: Review the final diff independently and close load-bearing findings. Confirm HZB correctness, history invalidation, shader package/reload atomicity, Direct/P2 equivalence and UI sharpness; run
graphify update .after final code edits, then update PLAN/Manual/architecture and evidence links to match actual tested scope. - Step 5: Commit and publish only verified work.
git add PLAN.md docs/IMPLEMENTATION.md docs/ARCHITECTURE.md docs/manual docs/validation/p3-temporal-2026-09-24 tests && git commit -m "Validate P3 temporal reconstruction"; publish checkpoint and final commits to the authorized remotes once local verification and CI are green.