Build PRK.Client linear HDR rendering and high-quality SDR tone mapping
Outcome Build a modern scene-linear HDR rendering foundation with high-quality SDR output, preserving highlight information for later bloom and other scene-referred effects. Existing prototype is in /home/mathias/Projects/worktrees/engines-client-hdr on feature/client-hdr-sdr-tonemapping, entirely uncommitted (including untracked helpers/tests); preserve it before any branch/base change. Resume assessment recommends rebuilding renderer integration on committed feature/client-ambient-occlusion rather than applying the old patch wholesale, while selectively porting format/color helpers and test fixtures. User approved preserving the prototype and restarting integration on the committed AO foundation, and specifically requests proper HDR research before implementation because the original approach was insufficiently justified. Production integration authorized by the user. HDR branch is now rebased onto origin/develop 8f03f0e8d after AO's squash merge (#2650), including the newer interface/client fixes. The superseded six-commit AO sequence was omitted rather than replayed. All60 modified/untracked HDR files were archived and byte-verified at /home/mathias/.local/share/prk-hdr-backups/pre-develop-rebase-20261013-135700 before stash/rebase/restore. No conflicts; only NameplateScene.cs and TargetIndicatorArt.cs differ from the saved bytes because their upstream changes merged automatically. The restored HDR diff was inspected; graphics/client tests build without warnings and the non-install CPU suite passes3677 with52skipped. Temporary rebase stash was dropped only after validation; original prototype stash and file backups remain. HDR implementation remains uncommitted. AO worktree is clean and untouched. Validation sources were archived/byte-verified at /home/mathias/.local/share/prk-hdr-backups/validation-20261013-123601 before the update, then stashed/restored without conflicts. No new commits created. Initial integration uses the documented analytic AgX-based view provisionally; baked assets remain external reference-only until permissions are resolved. Keep existing relative lighting coefficients and the tested HDR-safe extended legacy fog/body response; no native HDR output, bloom or automatic exposure. Proposed first milestone World/sky/reflections/refraction/transparency/additive effects remain HDR until a single final display transform. Use a capability-validated floating-point scene format (RGBA16F preferred), independent of swapchain format. Apply controlled exposure and a documented, reference-based modern tone mapper with smooth highlight rolloff and intentional saturation/gamut behavior; evaluate AgX as the initial candidate, not a simplistic per-channel clamp or an unlabeled ACES approximation. Output SDR with exactly one transfer encoding. Render UI/cursor/nameplate overlays in the appropriate display-referred stage without applying world exposure/tone mapping. Start with stable fixed/manual exposure, not automatic adaptation. Avoid new user-facing controls unless needed; reuse existing options/preferences if controls are agreed. Bloom, auto-exposure, PBR overhaul and a generic render-graph rewrite are deferred. Repository evidence and reuse - PRK.Client/PRK.GraphicsEngine/RendererVulkan.cs creates scene targets from swapchain format and uses alternate UNORM sky views over SRGB images (ChooseSkyFormat). It already has ordered sky/scene/translucent/composite passes and draws UI after the final scene quad. - RenderTargets.cs owns per-frame color/depth/picking and sampled scene copies. ReflectionTargets.cs wraps the same target implementation. Extend these shared owners and existing creation/resize/destruction paths; do not create separate HDR-only resource management. - PRK.Client/PRK.Client/VulkanShaders/composite_fragment.glsl already samples the scene/depth and applies underwater murk. Preserve scene-referred underwater processing before exposure/tone mapping, and leave a clear pre-tone-map integration boundary for later bloom. - Integration contract with liquid task tsk_m6hneajjgj (active uncommitted feature/water-types worktree): composite now has per-liquid clip-dependent linear medium fog before final output; lava/toxic/star outputs remain scene-referred, with lava relative highlights above1 and no local tone mapping. EngineUniformBufferObject reuses the two immersion padding words for LiquidFogWeight/LiquidFogEnabled and appends LiquidMist1/LiquidMist2/LiquidStars/LiquidWaves after UvAnimation; MaterialUniformBufferObject appends LiquidTint/LiquidMotion. Reconcile shared engine/material uniform and composite changes when merging, retaining immersion before the HDR display transform. No native HDR/output negotiation added. - shader_fragment.glsl and transparency_shader_fragment.glsl clamp lighting to 0..1; celestial_fragment.glsl and water_fragment.glsl also contain color clamps. Audit each clamp and authored material operation instead of indiscriminately removing bounds. Retain alpha/UV/visibility/legacy stage semantics where required. - TextureOptions.Srgb and TextureLoader's color-space-aware cache already distinguish color/data inputs. Audit authored sky, fog, vertex colors, lighting/lightmaps, emissive and effects inputs; avoid double decoding or silently treating all legacy values as linear. - Reuse shader includes/variants/compiler, DepthFormatSupport-style testable capability decisions, GpuConstructionScope, frame-slot ownership, GPU maintenance and retirement. Extend RenderTargetOwnershipTests, ShaderVariantTests, EngineUniformLayoutTests and frame/picking/water regressions. Acceptance criteria - Values above 1 survive scene rendering, additive blending, sampled copies and reflection/refraction until the final display transform. Picking/depth and world ordering remain unchanged. - Color-space contracts are explicit and consistently applied; sky no longer depends on a mismatched SDR view workaround. Supported SRGB and UNORM presentation paths encode correctly; unsupported configurations fail clearly rather than silently degrading to clipped SDR. - SDR output has predictable black/mid-gray, smooth highlight rolloff and controlled saturated highlights, with finite bounded output for black, extreme, negative and nonfinite inputs. UI brightness/alpha edges remain correct and independent of scene exposure; audit actual overlay ownership rather than assuming all nameplates are UI. - Resize/minimize/restore, frames in flight, display reconfiguration, construction failure and teardown preserve synchronization and resource lifetime. Quantify added target memory/bandwidth and GPU cost, especially at 4K; consider SDR dithering to avoid gradient banding. - Client/shader builds, numerical reference tests and relevant regressions pass. Vulkan validation and controlled HDR GPU readback verify actual >1 preservation, pass ordering, presentation encoding and lifetimes where available. User-owned visual checks cover bright emissive/additive effects, sky/day/night, indoors, fog/underwater/water and UI. No assistant GUI launches or screenshot review without explicit permission. Windows/Linux/macOS (MoltenVK) capability/platform gaps are reported honestly; no assets committed. Confirmed scope User confirmed internal HDR rendered back to SDR only. Native HDR monitor output, HDR-specific OS/surface negotiation, PQ/scRGB output, display luminance calibration and HDR UI composition are excluded. Bloom and automatic exposure remain deferred; preserve their future integration boundary without implementing them in this milestone. Research-backed design constraints - Working space: scene-linear Rec.709/sRGB primaries, separate from the sRGB transfer function. AO lighting is not calibrated photometry; retain explicitly documented relative intensity units rather than relabeling values as nits. First inventory all authored inputs and validate their interpretation with fixtures/corpus evidence. Existing SDR sky/legacy material operations may need bounded compatibility conversions, not blanket gamma conversion or clamp removal. - Vulkan specification confirms float attachments avoid normalized-range blend clamping; verify COLOR_ATTACHMENT, COLOR_ATTACHMENT_BLEND, SAMPLED_IMAGE, linear-filter support and exact image usage/extent support for scene and copies. RGBA16F preserves alpha and is preferred over packed unsigned RGB formats. Shader computation should remain float32 initially. Half-float max is 65504: establish finite/range policy and stress additive accumulation. Defer pre-exposure unless measured ranges require it; if introduced, scale all lighting/emission/fog/copies consistently and apply exposure only once. - AgX recommendation is provisional. Compare a pinned, licensed real-time implementation against an independent OCIO/reference transform across neutral ramps, saturated primaries/secondaries and exposure sweeps. The three.js implementation has Linear-sRGB input/output, internal Rec.2020 conversions, log exposure range, inset/outset matrices, a polynomial contrast approximation and final gamut clamp. Its internal pow(2.2) is NOT final sRGB output encoding. It is an approximation with limited gamut handling, not a claim of exact Blender equivalence. Check approximation error, hue behavior, mid-gray and GPU cost. Compare Khronos PBR Neutral as a color-preserving alternative; its shiny-dielectric/unit-white PBR reproduction guarantee does not automatically apply to AO's legacy materials. No operator-selection UI or multiple production pipelines are implied. - SDR presentation: SRGB attachment performs destination decode, linear blending and final encoding; shader returns display-linear RGB. A UNORM surface does none of that. Encoding the world shader before blending UI into UNORM is incorrect; use a supported SRGB view when valid, otherwise compose display-linear world/UI into an intermediate and run a final explicit sRGB OETF, or refuse unsupported presentation. Validate exact surface color-space/format pairs instead of inheriting the current availableFormats[0] fallback. Do not substitute pow(1/2.2) for the piecewise sRGB transfer. - Premultiplied UI is a concrete audit item: GuiSurface creates SKAlphaType.Premul without explicit SKColorSpace; UIRenderer uploads an SRGB texture and blends ONE/ONE_MINUS_SRC_ALPHA. Decoding encoded-premultiplied RGB is not equivalent to linear premultiplication. Establish the raster/upload contract and test translucent edges over light/dark backgrounds; preserve transparent zero-alpha safety and filtering correctness. - NameplateScene uses world meshes/shader_vertex, and nameplate_fragment samples sceneColor for panel luminance. Move display-referred markers to a depth-tested post-tone-map overlay stage while preserving occlusion/order, and derive panel contrast from displayed scene luminance. Do not simply tone-map text with the world or duplicate the full world draw pipeline. - Future bloom belongs to HDR scene processing before the final display transform; reflection/refraction buffers remain untonemapped and UI is excluded. Filament's colorGrading material mixes bloom before grading. Its bloom downsample uses a normalized 13-tap filter from Jimenez's SIGGRAPH 2014 presentation; modern bloom need not mean thresholding everything above 1. Defer bloom policy/filter/threshold implementation but retain a shared linear HDR boundary with exposure semantics explicit. - Dithering is a final quantization concern, not arbitrary linear-space noise. Filament's dither helper expects final encoded color and an 8-bit target. Evaluate sub-LSB encoded-domain dithering; automatic SRGB output requires deliberate encode/dither/decode handling or a final explicit-encoding stage. Avoid adding another full-resolution pass solely for an unmeasured benefit or dithering alpha indiscriminately. Research sources (pin exact revisions before reuse) - Vulkan framebuffer specification: https://docs.vulkan.org/spec/latest/chapters/framebuffer.html - Filament lighting/pre-exposure theory: https://google.github.io/filament/Filament.md.html - Filament reference revision 764fe6cac9bdb4ba24d3d852344dfa04a5f70f75: filament/src/materials/colorGrading/colorGrading.mat, filament/src/materials/bloom/bloomDownsample.mat, shaders/src/inline_dithering.fs at https://github.com/google/filament/tree/764fe6cac9bdb4ba24d3d852344dfa04a5f70f75 - Real-time AgX/Neutral implementation: https://github.com/mrdoob/three.js/blob/755f6e48c6865a83ae3e1e512adb3ea9e37a26da/src/renderers/shaders/ShaderChunk/tonemapping_pars_fragment.glsl.js ; three.js MIT license requires copyright/permission notice for substantial reuse. Verify upstream provenance and notices before copying. - AgX OCIO reference and limitations: https://github.com/EaryChow/AgX (license was not established from fetched README; do not copy LUTs/configs without resolving it). - Khronos Neutral specification: https://github.com/KhronosGroup/ToneMapping/blob/main/PBR_Neutral/README.md ; intended nonnegative linear Rec.709 input/output, no general gamut mapping. Resolve reference-code license before reuse. Validation availability The inspected graphics tests target .NET 10. No dotnet executable is currently on PATH; use the repository's supported SDK/container workflow during implementation and report unavailable validation gates explicitly. Resume assessment Fetched origin/develop at 6cc9f7da9. HDR HEAD aca198056 has no unique commits and is 48 commits behind develop; all prototype work is uncommitted. AO HEAD 76385892c is clean, contains the implementation checkpoint on profiler-inclusive bbbf3cba3, and is only two commits behind develop (corpse animation fix plus merge). Inspection found concrete obsolete integration: HDR Exposure replaces a uniform slot now used by LiquidFogWeight; scene_display.glsl lacks current LiquidFogEnabled clip-dependent liquid fog; renderer/draw-stream changes must preserve new profiler instrumentation and AO descriptor/geometry variants. New grass and lava/toxic/star shaders were absent from the prototype and need the color/range audit. AO separates indirect/direct/emissive lighting, but still clamps SurfaceColour lighting to 0..1. Preserve AO's separation when enabling HDR and keep depth/normal/AO data outside color transforms. Prototype already contains RGBA16F capability checks, strict sRGB surface selection, transfer/UI premultiplication helpers, a pinned PBR Neutral candidate and GPU readback fixtures. These are reusable candidates, not verified acceptance outcomes; AgX-versus-Neutral selection remains unresolved. No files, branches or worktree contents changed and no tests/builds ran during this assessment. Research findings and next gates Primary-source first pass: Vulkan framebuffer specification confirms no normalized-range blend clamping for floating attachments, sRGB destination RGB decoding before blending and RGB encoding on write; alpha is never sRGB-encoded. Query actual format blend/filter support and exact usages/extents; float storage does not ensure finite/range safety. Filament explicitly allows coherent arbitrary relative lighting units; physical light calibration, PBR conversion and pre-exposure are not prerequisites. Keep FP32 shader arithmetic and evaluate half-float range/accumulation experimentally before adding pre-exposure. Khronos Neutral README establishes its 0.04 offset compensates the normal-incidence response of a shiny IOR1.5 dielectric under unit white lighting. That guarantee does not apply to this legacy renderer. Independent scalar evaluation gives neutral input 0.18 -> 0.14 linear and 0.018 -> 0.002025. Khronos's May16 2024 release recommends filmic AgX/ACES for strongly HDR scenes; this is use-case guidance, not proof AgX wins for AO assets. Neutral remains a comparison candidate, not the inherited production choice. Sources: https://github.com/KhronosGroup/ToneMapping/blob/main/PBR_Neutral/README.md and https://www.khronos.org/news/press/khronos-pbr-neutral-tone-mapper-released-for-true-to-life-color-rendering-of-3d-products . Pinned three.js AgX implementation and MIT license were fetched at 755f6e48c6865a83ae3e1e512adb3ea9e37a26da: linear-sRGB input/output, Rec2020 conversions, polynomial contrast approximation and simple final gamut clamp. Internal exponent2.2 is not the output OETF. Pinned Blender config fc08f7491e7eba994d86b610e5ec757f9c62ac81 has a LUT-based base-sRGB path with luminance-compensation/process-space machinery and internal exponent2.4: do not claim the compact approximation is exact Blender parity. That config references ocio-license.txt, but the adjacent URL returned404 and its directory listing lacks the file; resolve upstream reference/LUT licensing before copying assets. Three.js MIT notice requirement is verified. Skia sources https://skia.org/docs/user/color/ and https://skia.org/docs/dev/design/raster_tragedy/ establish null destination is unmanaged, premultiplied color conversion must unpremultiply before transfer decoding, and raster blending happens in destination encoding. Current GuiSurface has no explicit color space. Upload correction cannot undo already-rasterized encoded-space blends; evaluate explicit linear tagged rasterization versus a documented compatibility path, including quantization, dirty-upload CPU cost and glyph-weight/AA changes. Numerical alpha example: encoded-premultiplied white at alpha128/255 yields sampled RGB0.21586 if naively sRGB-decoded versus desired linear-premultiplied0.50196. This validates the boundary problem, not the whole UI implementation. Code evidence: AO engine_uniforms.glsl clamps both transfer helpers; ApplyWorldFog mixes encoded values and body shadows use encode/attenuate/decode. Old HDR prototype removes upper transfer clamps and changes fog to linear mixing, but compatibility is unverified. SceneLighting forwards environment-script vectors and packed ambient colors without an established linear-vs-encoded provenance; do not indiscriminately gamma-decode intensity vectors. Grass explicitly decodes authored appearance. Sky/celestial keep legacy layer operations requiring per-input classification. New liquids and particle/effect shaders share world fog and must be included. AO depth/normal/scalar buffers remain data, never color-transformed. Raw storage estimate from actual target ownership: upgrading scene color, sampled color copy and reflection color from4 to8 bytes/pixel adds 298598400 bytes (~284.77MiB) at3840x2160 with3 frame slots, excluding alignment/compression/driver overhead and AO storage. Before integration: inventory color/intensity/alpha provenance and legacy bounded operations; establish an independent pinned OCIO/reference comparison for AgX plus mathematical Neutral reference across ramps, hue/saturation/exposure sweeps and pathological inputs; assess reference error rather than merely translating the same formula into CPU tests. Test deliberate linear fog/body-shadow policy and visual compatibility; validate actual Skia raster/filter/upload/composite behavior. Add Vulkan readbacks for >1 accumulation, reflection/refraction/copies, exactly-one SDR encoding, depth-tested display overlays and finite overflow handling. Benchmark full pipeline/memory alongside AO. Initial research used only a small independent Python arithmetic sanity check. The subsequent validation foundation now runs real shader/GPU and Skia probes; user visual checks and platform coverage remain outstanding. Verified validation foundation Old prototype: all26 modified/untracked files archived and byte-verified at /home/mathias/.local/share/prk-hdr-backups/20261013-095627, with binary patch and manifest; also preserved in stash commit (stash message Preserve HDR prototype before AO-based validation harness). HDR branch fast-forwarded to committed AO76385892c, without replaying the prototype or changing client rendering. AO worktree is concurrently receiving optimization edits and was not modified by this work. Test-only implementation: Tools/HdrValidation/generate_reference.py and README; HdrValidation/reference.json, candidates.glsl, PixelProbe.cs and HdrValidationTests.cs in Vulkan tests; HdrUiValidationTests.cs in CPU tests. Fixture has206 black/gray/exposure/hue/color/extreme cases independently generated with OCIO2.5.1 from pinned Blender fc08f7491e7eba994d86b610e5ec757f9c62ac81 LinearRec709->AgX Base sRGB, no look, followed by exact piecewise sRGB decoding. Config/LUT dependencies are SHA256 checked and not redistributed; generated fixture reproduces byte-for-byte. Neutral expected values use an independent double-precision formulation of pinned Khronos specification180b1a7bddec33f73fe41712a2963cc3ad8e5547 (CC-BY4 attribution recorded). Candidate shader source/notice is pinned three.js MIT. PixelProbe reuses production allocator/RenderTargets/render-pass/pipeline builders, renders, copies to actual scene-copy image, samples it in another pass and reads back. Synchronization validation passes, including reused attachments. FP32 output separates operator error from half quantization; separate RGBA16F additive/copy/filter probes verify >1 retention. SDR probes verify exactly-one RGB encoding, unencoded alpha and proper bilinear linear-premultiplied edges. A probe using actual engine_uniforms.glsl confirms its current transfer helpers still clip4/16 to1. Actual GuiSurface panel painting confirms encoded-space raster blending, while tagged linear RGBAF16 Skia matches analytic source-over; alpha cases1/32/128/254 expose naive sRGB decoding of encoded-premultiplied pixels. Measurements: Neutral maximum/p95 absolute encoded-RGB reference error1.55e-7/3.78e-8. Compact AgX maximum/p95 error0.201936/0.086247 versus pinned Blender; worst sample yellow[.25,.25,0]. It does not meet provisional0.02 encoded-channel approximation budget, which is a screening metric, not a perceptual acceptance criterion. Passing characterization tests explicitly do not approve this candidate. Do not call it Blender-equivalent or conclude all AgX implementations are unsuitable. RX9070 RADV half-float additive out-of-range readback saturates at65504 after copied-image sampling; no universal overflow policy inferred. Validation: both test projects build with zero warnings/errors. Five Vulkan probes pass with synchronization validation on RX9070; six Skia/UI cases pass; complete CPU suite excluding RequiresInstall passes3661 with52 skipped; four Python generator/corpus/hash-rejection tests pass. Full build needs existing Docker .NET10 environments; CPU build used -m:1 to avoid preexisting duplicate-project output collision. Host Vulkan tests run through local .NET runtime/SDK and PRK_TEST_GLSLANG=/usr/bin/glslangValidator because installed/bundled glslc references missing libglslang.so.15. No system packages changed. Results at HDR .dev-data/hdr-validation-results; latest comparison is attached below _archlinux_2026-10-13_10_17_49. No GUI launched and no production renderer/shader changed. Remaining validation scope: material/emissive/texture authoring evidence beyond the sampled environment corpus, and explicit transfer/calibration boundaries; fog/body-shadow appearance policy and representative scene review; negative/nonfinite/overflow policy; full text/cursor raster/upload/composite behavior; depth-tested post-tone-map overlays; actual reflection/refraction integration and frame-slot/resize rollback; full-scene GPU/memory budgets and user appearance/platform checks. Existing implementation acceptance checklist remains open. Completed validation checklist items cover only the verified independent foundation and subsequent authored-environment/fog-shadow characterization; initial production integration is implemented and builds; release acceptance remains open pending live-scene/range/lifecycle/platform validation. Authored environment and compatibility validation Added HdrAuthoredInputTests.cs with deterministic numeric-contract fixtures and a RequiresInstall census using the existing script/RDB readers. Actual install mounted read-only; RDBController's automatic tempPatch.rdbpatch writes are redirected to a disposable scratch directory. Census evaluates all601 shipped closures at six submitted times (3606samples), ExpansionBoth/default game inputs except day time/sun rotations. Reports provider origins, effective/frozen day times, closure hashes, values, ranges and refusals; fallback values are available inputs, not proof indoor rendering uses them. All closures parse and all evaluated declared members succeed; no nonfinite/negative/above-one sampled light channels or baseline fog channels. Ambient max0.95686275; ground/cloud/water/space max1. Six zones yield36 verified frozen-time samples. Effective GAME.CurrentDayTime metadata is unavailable for48 samples across8 other zones; those null values are not counted as frozen time and remain an explicit metadata coverage gap. Corpus excludes localized fog areas/spheres, mesh/emissive/texture data and exhaustive temporal/camera sweeps. Results establish a legacy numeric convention, not an authoring transfer function or physical units. Inspected sunlight scripts explicitly double/cap ground channels and interpolate /255 tables. Preserve those relative coefficients rather than blindly sRGB-decoding intensity vectors. Traced byte-normalized fog/ambient/light records and RGB565/baked vertex lanes; shader_vertex forwards vertex channels directly into additive ambient lighting, with emissive materials suppressing baked lanes. Their transfer/calibration provenance remains separate work. Added HdrCompatibilityTests.cs and test-only compatibility_candidates.glsl. PixelProbe now optionally uploads the actual EngineUniformBufferObject with a shared descriptor allocator. Fog calls production ApplyWorldFog; body attenuation extracts the actual production conditional/statement, replacing only SurfaceColour(unoccludable,texColor) with a fixture-supplied protected-surface floor. Body scope explicitly excludes material evaluation and shadow estimation. Tests compare current bounded behavior, unbounded extended-sRGB legacy-compatible response and linear-light response; no production shaders or renderer changed. Extended variant matches current SDR-domain fog/shadow operations within2e-5 and preserves HDR continuously; it remains an intentional nonlinear compatibility operation, not linear energy transport. Linear variant preserves the existing post-material floor but changes attenuation response. Fog spans8scene colors×7keep values×3fog colors×3modes; body spans8scenes×7occlusions×2protection floors×3modes. Current fog with scene4 jumps by~3 when keep goes from0.999999 to1; both alternatives are continuous. Half black fog on white produces current/extended0.214041 vs linear0.5. Half body occlusion on neutral0.18 produces0.0434809 vs0.09. For fixed encoded gray fog0.5, matching old half-fog with linear mixing requires keep0.762307 for black and0.392489 for white, disproving a universal scalar weight retune. This is an appearance choice requiring scene review, not a drop-in repair. Maintain legitimate visibility/alpha/math-domain bounds separately from radiance clipping. Shared HdrReport.cs writes optional PRK_HDR_REPORT_DIRECTORY and attaches reports; this avoids loss of JSON inside disposable dotnet-test containers. Authored census at HDR .dev-data/hdr-input-results/reports/hdr-authored-environment-inputs.json; fog/body/tone reports at .dev-data/hdr-compatibility-results/reports/. README documents commands, evidence and limitations. Validation: both test projects build zero-warning; all7 HDR Vulkan probes pass synchronization validation on RX9070. All3 authored-input tests (including corpus) pass against read-only install. Full CPU suite excluding RequiresInstall passes3663,52skipped; final census metadata changes were followed by a targeted three-test rerun. Four Python reference-generation tests pass. No GUI launched, no system changes, no commits. Linear-vs-legacy fog/body appearance policy and material transfer/calibration boundaries remain open. Finite/range boundaries are now experimentally verified as proposals; production accumulation bounds/scale and reference-faithful gamut behavior remain unresolved. Finite/range and AgX ablation results Test-only finite_policy.glsl and HdrRangeTests.cs cover NaN, signed infinities, negative/subnormal/extreme finite RGB, coverage and exposure. Proposed half-store boundary maps NaN/negativeRGB to0, +Inf/above-range to65504; scalar nonfinite coverage becomes0 and finite alpha is clamped0..1. Separate premultiplied boundary zeros RGB when coverage becomes0, without imposing RGB<=alpha on HDR. Do not apply these helpers indiscriminately to depth/normal/AO data or legitimate negative color-transform intermediates. Display exposure stays FP32, accepts2^-16..2^16 and is not reclamped to65504 after multiplication. GPU invalid-setting fallback is1; production settings must reject invalid CPU input. All five experimental operators remain finite/bounded after the proposed input boundary. These tests do not implement live renderer/settings behavior or guarantee safe blending. Real RX9070 accumulation counterexample: two individually valid40000 contributions sum80000 inFP32, but unscaledRGBA16F copy/sample reads65504. Fixed storage pre-exposure0.25 stores20000 and FP32 inverse scaling recovers80000. Per-fragment sanitation or a final shader clamp cannot recover energy already lost during blending. No scene-wide scale is selected; measure scene/light/emission/additive/reflection/refraction/fog ranges and precision before adopting fixed pre-exposure. Other devices may overflow nonfinitely; no universal saturation behavior inferred. Compact AgX source ablated into exponent2.4-before-outset, analytical sigmoid and pre/post-curve hue mix. The analytical curve is independently constructed from hyperbolic endpoint/pivot/slope constraints using pinned generator parameters ab7415eca3cbeb14fd55deb1de6d7b2d699a1bb9; no third-party Python or gamut-compensation implementation copied. MIT three.js base operators/matrices retained. A4096-point GPU ramp verifies monotonicity, endpoints and pivot. No coefficients fitted to benchmark samples. Standard206 max/p95 encodedRGB errors: compact0.201936/0.086247; exponent/order0.091053/0.052463; analytical0.067884/0.040536; hue mix0.042524/0.012287. Independent1150-case corpus (existing cases +432hue/exposure/saturation-grid cases +512seeded inputs) gives0.201936/0.075623,0.091053/0.041387,0.086954/0.025933,0.058047/0.009605 respectively. All AgX variants still fail provisional max-error0.02 screen; do not approve or claim Blender equivalence. Negative pre-final-clamp channels occur in28standard/132dense cases. Worst dense cyan[0,.18,.18] has pre-clampred-.0018817 vs referencepositive.0046991; the difference initially suggested missing lower-gamut handling, but subsequent projection/interpolation experiments show that attribution is incomplete. A lower projection alone cannot reproduce the baked reference's positive channel at this off-grid input; do not claim all residual error is a missing guard rail. Preserve those legitimate intermediates until deliberate gamut processing rather than treating them as bad scene input. Generator supports --dense; comparison test supports PRK_HDR_REFERENCE_FILE, records corpus/hash/count/group errors/worst sample and pre-clamp gamut diagnostics. Standard206 fixture reproduces byte-for-byte with pinned OCIO2.5.1 configuration/dependency hashes. Dense data remains local .dev-data/hdr-reference-dense.json, not a large repository fixture. Reports under HDR .dev-data/hdr-range-results/standard and /dense. Studied pinned Godot shader befe24c83135da887e534504874d1dcea4ed1f1c and merged PR106940: its newer allenwp curve intentionally changes bright-end response and is not an exact Blender substitute. No code from that implementation copied. Reference LUT/gamut implementation permissions still need resolution before production reuse; general search became unavailable during license research, though direct primary-source fetches worked. Validation: Vulkan test project builds zero-warning; all13 HDR GPU tests pass synchronization validation onRX9070, including the six new range/curve tests. Dense reference comparison separately passes its characterization/invariant tests while allAgX fidelity-screen flags remainfalse. Five Python tests pass, including deterministic dense-corpus and hash-rejection checks. Production renderer/shaders unchanged; no GUI/system mutation/commits. CPU regression suite was not rerun in this stage (prior3663/52skip result retained). An independent lower projection and a numerically faithful external-data GPU route are now verified with distinct scopes. Next priority is compact-format precision and full-resolution operator cost, then scene accumulation bounds and the production view choice; production acceptance checklist remains open. Lower-gamut versus baked-reference result Added own Rec.709 neutral-axis lift/luminance-restoration projection, with GPU invariants for identity on nonnegative RGB, nonnegative output, positive-Y preservation, uniformly scaled pairwise chroma differences and boundary continuity. Negative/nonpositive-Y inputs fall back toblack; upper-gamut processing is separate. This is not Blender's compensation algorithm or a perceptual-hue guarantee. It improves standard206 max/p95 encoded error to0.037026/0.011853, but dense1150 max remains0.058047 (p950.008780). Thus it is a well-defined analytic design option, not an approved Blender-equivalent operator. Corrected a source/asset assumption: actual pinned LUT header is57^3 (185193nodes), not the older generator source's37^3. New inspect_agx_lut.py reads/validates the header, calculates tetrahedral weights and power2.4 decode, using OCIO only for input-space conversion and comparison. Across1150cases independent sampling matchesOCIO within3.9594e-5 linear RGB. Cyan[0,.18,.18] lies in a tetrahedron with zero/positive-red vertices; interpolation creates the referencepositive red. This explains why simple repairs cannot reproduce that baked behavior, without proving every analytic discrepancy is interpolation alone. No coefficients were fitted. Config verification now hashes the actual supplied config argument (not just a sibling config.ocio); regression test prevents bypass through another filename. Optional native reference probe uses external packed FP32 cube data, a 3D ImageOwner/view, four texelFetch tetrahedral interpolation and the shared PixelProbe/resource owners. External input is generated privately by inspector --gpu-input-dir, verified against source cube and packed-binary hashes, and borrowed for a bounded lifetime. No LUT/config/data is committed; game has no OCIO runtime dependency added. RequiresAgXReference test skips/inconclusive without PRK_HDR_LUT_PROBE_DATA. Native standard206 and dense1150 max/p95 encoded error is1.74046e-5, comfortably within a separate FP32 inspection budget0.001; this establishes faithful baked sampling, not production/visual/permission approval. Raw FP32 texels2963088bytes (~2.83MiB), excluding staging/alignment/allocator overhead. Reference outputs are retained before final SDR quantization. Licensing: inspected pinned Blender COPYING and release/license/OpenColorIO.txt, plus upstream EaryChow/AgX and sobotka README/tree material. OpenColorIO's BSD-style library notice is not an asset grant. No specific permissive grant for the pinned view assets or external gamut implementation established; no production redistribution authorized. This is bounded technical due diligence, not exhaustive legal review. Own projection/interpolation code is independent; no external gamut-compensation implementation copied. Validation: Vulkan project builds zero-warning; all15 HDR GPU tests pass synchronization validation with private reference data onRX9070. Dense analytic/reference pair passes separately; analytic fidelity-screen flags remainfalse. Optional no-data skip verified. Nine Python tests pass, including tetrahedral sectors/borders, bad cube/coordinate rejection and actual-config hash regression. Standard reference regeneration reproduces byte-for-byte. Results at .dev-data/hdr-gamut-results/{standard,dense}, lut-inspection.json and reference-input/. Full CPU suite was not rerun in this stage; prior3663/52skip result retained. No production renderer/shader changes, GUI launch, system mutation or commits. README records corrected reference resolution, design distinction, commands, evidence and limitations. Initial production integration RGBA16F scene/sky/copy/reflection targets are independent of SDR swapchain format and capability/usage/extent validated. Sky no longer uses mutable UNORM-over-SRGB views; terminal authored sky color is decoded once while bounded legacy material-stage operations are retained. World/grass/transparency/water lighting upper clamps removed; liquid/star/lava/toxic/particles/additive effects use finite scene-output boundaries. AO depth/normal/thickness/visibility inputs remain unchanged data, and the committed AO indirect/direct/emissive separation is preserved. World fog/body-shadow encoded-domain operations now extend above1 without clipping, retaining SDR response rather than silently adopting different linear-light attenuation. Clip-dependent liquid immersion remains before final display transform. Production display_transform.glsl implements the previously validated analytical sigmoid/hue mix plus our luminance-preserving lower projection, with MIT three.js matrix attribution in exported licenses. No baked asset or gamut-compensation implementation distributed. GPU comparisons include the actual production helper and enforce agreement with the documented analytic candidate; it remains non-Blender-equivalent. Engine.ExposureCompensation validates finite -16..16 stops before state change, initially0; no new user-facing controls. Composite pass now shares read-only main depth, uses per-frame-slot × swapchain-image framebuffers, draws the world view then display overlays then UI/cursor. Nameplate materials get an explicit DisplayOverlay variant excluded from world/reflection/AO geometry passes, preserve depth occlusion and sample displayed-scene contrast. Nameplate/cursor exports are straight sRGB RGBA8, converted to encoded linear-premultiplied data before hardware sRGB sampling; masks/alpha remain data. UI uses tagged linear Skia RGBAF16 and a linear RGBA16F texture, retaining dirty uploads with correct row/byte strides. Cursor composition and nameplate rasterization also use tagged linear surfaces. PNG captures convert back to tagged sRGB. DisplayOverlayDraws is appended to profiler counters, with recording inside existing composite CPU/GPU spans; transparent-world counters exclude overlay bookkeeping. Resize recreates HDR owners, frame/image depth framebuffers and AO owners. SDR attachment-format changes prepare new composite/UI pipelines and pass, then invalidate only stale overlay pipelines and retire old presentation resources after GPU completion. No live-window test has been run; this path still needs native lifecycle verification rather than source-inferred approval. Existing retirement/construction scopes reused. Verified: both graphics/client and Vulkan test projects build zero-warning. All174 stage-scoped graphics/AO compute artifacts compile using existing release script and a private glslang-based glslc adapter (system/bundled glslc has missing libglslang.so.15); no system/compiler install changed. Complete non-install CPU suite passes3674 with52skipped. All18 non-external HDR GPU tests pass synchronization validation onRX9070. Salvaged/refactored native presentation fixture uses production view and composite pass, verifying >1 blending/copies, sRGB source-over, foreground/background depth occlusion and exposure-independent display overlays. Dense1150 comparison also passes production-vs-validated-analytic agreement. Uniform layout, surface/format capabilities, frame/image indexing, exposure rejection and actual linear GUI paint contracts have dedicated tests. Synthetic 4K full-screen HDR draws+copy+view reports about0.699ms median from12 measured frames after4warmup; this is not a gameplay/incremental cost claim. Raw scene image storage increases~285MiB at4K/3slots, plus linear UI texture/host-staging/raster overhead; actual game memory/bandwidth still needs measurement. Remaining gates: user-owned visual checks (sky/day/night, indoor/outdoor, emissive/additive, water/immersion, foliage, UI/nameplate edges), actual accumulated range/overflow and operator/overlay/UI cost, native renderer live AO/settings/resize/minimize/restore/construction rollback tests, broad platforms and independent patch review. Per-fragment sanitation does not guarantee half-float blend-range preservation. No assistant GUI launches, screenshots, game-install writes, publish artifact, commits or release approval. Original implementation acceptance checklist remains open. User launch packaging regression fixed User's run-client-dev.sh build succeeded but startup failed because copied engine_uniforms.glsl referenced missing scene_color.glsl; display_transform.glsl and scene_display.glsl were also absent from actual client output. Earlier shader validation copied the source tree manually and failed to exercise MSBuild packaging. Replaced handwritten shader-copy entries with a recursive *.glsl rule for both build and publish, and Windows ShaderInclude incremental inputs with a non-stage GLSL glob. Added ShaderPackagingTests checking actual built assets and every local include dependency; reproduced missing-include failure before fix, then passed after fix. Root-run validation containers had also left generated license directories root-owned. Repaired ownership only in HDR worktree project bin/obj directories and its NuGet cache; future validation commands now use host UID/GID. No source/game-install ownership changed. Verified exact ./run-client-dev.sh --build-only with user's PRK_OPENAL_LIBRARY/PRK_DATAROOT: zero warnings/errors, corrected user-owned license output, all45 GLSL files have explicit build/publish copy metadata, and real output's174 stage artifacts compile with now-working system glslc2026.4. Twelve focused packaging/variant/uniform tests pass. User subsequently confirms startup works and all requested visual scenarios (sky/day-night, bright effects, water/immersion, foliage/AO, UI/nameplate edges) look correct. This is user-owned visual verification, not assistant GUI review. Publish metadata checked, not a native publish acceptance run. User HDR profile Analyzed latest export /home/mathias/.local/share/PRK/profiles/profile-20261013-121345-2279d52b38514b2082e9057461b1ab12.json. Debug/developer profiler, .NET10.0.12, no baseline,1199 rendered frames27706–28904 over13.462s; GPU spans resolved for1197, finaltwo pending. All frames probes-off, GPU supported. Stable workload:1character,15294ready entries,452scene/452reflection draws,893transparent draws (891alpha-tested,2water),434AO prepass draws/eight dispatches,one display-overlay draw, no effect draws. Composite GPU median/p950.32416/0.33564ms, max0.45832; Scene2.91252/2.94496, Reflection0.7648/0.77388, Translucent0.6324/0.64796, Shadows0.84608/0.87288, AO2.4722/2.53472 (estimation1.43388 median). Excluding child spans, timed GPU pass sums median/p957.9562/8.12144ms; not whole-frame or presentation GPU time. Composite includes UI upload/view/overlays, not isolated HDR cost. Elapsed update-to-pacing median11.094ms, p9515.232; pacing median6.792ms is consistent with90fps cap, not an uncapped headroom measurement. UiPaint has60 repaint events, median11.821/p9530.274ms, with58of63elapsed>15ms frames coinciding with staging/paint. User explicitly identifies these as profiler-open overhead; do not attribute them as an HDR regression. No same-build/profiler-state non-HDR A/B exists. No GC on worst repaint frames. UI stage event median/p950.624/1.342ms. Resource snapshot active1465947552 bytes (~1.365GiB), reserved1531696128 (~1.427GiB), pendingbytes/owners0, creation/retry/device-loss failures0. RenderTargets884618240 (~843.64MiB); UI/cursor193175840 (~184.23MiB). Snapshot includes managed allocator host/device categories, not pure VRAM and not memory-growth/radiance proof. Median process allocation49200bytes/frame;5gen0/1gen1/0gen2 collections inwindow. Analysis JSON at HDR .dev-data/hdr-profile-analysis.json. User confirms this HDR capture is at4K with High AO. Resolution/tier are user-reported because they are not exported; GPU model also is not exported. Do not infer exact viewport extent from the monitor-resolution label or treat earlier Release AO captures as matched comparisons. Earlier available AO captures are Release, so not a fair Debug/HDR differential. User explicitly verifies and approves the implemented HDR visual result and its measured cost at4K with High AO on the current host. Accept the observed composite cost (0.324ms median/0.336ms p95) as reasonable for this configuration; no matched non-HDR baseline is required for this user sign-off. The capture is Debug/profiler-open, and UI repaint stalls are identified by the user as profiler overhead, not an HDR regression. This is configuration-specific acceptance, not a claim of isolated HDR overhead or broad-platform release approval. User subsequently requests a mild brightness retune for poorly placed local light sources while keeping darker areas unchanged, with example /tmp/pi-clipboard-589ae16c-8391-4f84-a292-3fd8e7b834a5.png. Global exposure must remain unchanged. User clarified that the comparison image is pre-HDR and reports many overlapping player light bars at this location. The placed-only experiment did not address those carried lights. User authorized trying combined placed + carried point/spot overlap response. Implemented ApplyLocalLightResponse in local_lighting.glsl: calculate one peak/scale from their sum and apply it to both original terms, retaining separate placed/probe and carried/body-shadow treatment. Identity up to peak1 (legacy unit coefficient); above it peak=1+(peak-1)/(1+0.25*(peak-1)), keeping RGB ratios and a continuous unit slope at the knee. Examples2->1.8,4->2.714,8->3.545; output remains HDR rather than hard-clipped to1. Static-grid and selected-light contributions are aggregated before one response, so overlapping lamps are handled together. Directional/chunk fill is accumulated separately and restored unchanged, including carried-directional classification if present. Ambient/sun/probes, emission, tone mapper/exposure and UI are unchanged; carried point/spot contributions now share the overlap response. This is an artistic compatibility retune, not a new physically calibrated attenuation model or proof of a specific bad light record. Private GPU response test (.dev-data only, using archived probe helper) verifies low-end identity, monotonic response, RGB ratios, retained >1 values and equal response for placed-only, carried-only and mixed partitions. Mixed terms keep their individual shares. No experimental code added to the PR. Serialized client build zero-warning, all174 shader variants compile, Vulkan readback/synchronization validation passes onRX9070. Initial parallel client build failed copying generated dependency DLLs; serialized rebuild succeeded. The private test initially used a reserved GLSL identifier; renamed it and reran successfully. Only local_lighting.glsl is modified/uncommitted; no commit or push. User provided /tmp/pi-clipboard-eae5e0de-05be-40d0-bd8f-26e7d262e3fb.png and cannot see much improvement; broad pale illuminated paving remains. Tuning strength is not visually accepted. Checked shader freshness: running client PID301246 started18:56:21 after18:52:20 shader artifact compilation; copied local_lighting.glsl matches repository bytes, application shaders take precedence over install assets, and ShaderCompiler recursively tracks include timestamps. Stale artifacts are therefore unlikely, though this is source/file/process evidence, not a live GPU pipeline capture. Keep code unchanged during this assessment. User requested a hotkey for controlled carried-light comparison. Added developer-only F10 to existing DeveloperClientShortcuts, default enabled with local chat ON/OFF notice; WorldManager publishes an empty dynamic-light set while disabled, preventing stale light snapshots. Repeat guard is independent of F11; disposal unsubscribes both. No persisted preference/options UI or production hotkey. Three developer shortcut tests pass (F10/F11 repeat handling, disposal and build flag), and production-flag reflection test confirms the diagnostic property/shortcuts are absent. Restored developer client build with zero warnings/errors; diff whitespace check passes. User elected to exclude both provisional lighting rolloff and F10 diagnostics from HDR and defer the general lighting issue. All five source/test files were byte-verified into /home/mathias/.local/share/prk-hdr-backups/deferred-light-overlap-20261013-191814, with binary diff, hash manifest, private probe sources and test logs; preserved additionally as stash commit7f6132bbc3fcafe234c7b72b33549e2480ed4afd. Saved stash diff exactly matches the archived patch. HDR working tree is now clean and equals published f2f734123. Push confirmed Everything up-to-date. Rebuilt restored developer client, ten focused HDR/shortcut tests pass and174 shader artifacts compile, so next launch returns to published HDR rather than experimental generated artifacts. Existing running client is not forcibly stopped. Lighting follow-up is tracked separately as tsk_upgqr5cpmn; its artistic response is unresolved. No new commits, lighting changes or diagnostic hotkey are present in HDR PR #2652. User supplied same-camera F10 ON /tmp/pi-clipboard-9ff0f34c-5c88-4695-9cb1-e22541cb9918.png and OFF /tmp/pi-clipboard-084ccd3c-d6ea-436b-bf7e-537604ce8146.png. Chat notices confirm states, and shaded grass/paving/left building clearly brighten with carried lights; upper-right wall changes much less. This verifies the toggle in the live client and supports carried lights as a substantial contributor, not global exposure as the target. Player/effect animations differ; no exact radiance/overlap coefficients or per-pixel HDR ranges inferred from SDR screenshots. Next tuning should target excessive carried/combined overlap while preserving weak-light response; artistic strength remains unaccepted and input-range readback would improve calibration. Ordinary/dark-location visual preservation still needs verification. Screenshot is user-supplied and authorizes its inspection; no autonomous GUI launch. Prior approval remains historical for the current implementation; new tuning needs user visual confirmation. Do not restore hard SDR clamping, modify individual scene assets or claim the image proves a specific attenuation-data defect. New tuning edits are not authorized for automatic commit/push merely by the prior PR workflow. Range/overflow and additional native lifecycle/platform validation limitations remain explicit. PR preparation authorized by user, specifically excluding experimental HDR validation code/tools/data. Excluded files are byte-verified and preserved at /home/mathias/.local/share/prk-hdr-backups/pr-excluded-experiments-20261013-143540; project links/copy rules for those files removed. Keep production HdrIntegrationTests, ShaderPackagingTests and uniform-layout coverage. Current dedicated branch feature/client-hdr-sdr-tonemapping has commits c747d0917 (feature: Add linear HDR scene rendering and SDR composition) and f2f734123 (fix: Preserve linear color in grouped UI composition), based on develop8f03f0e8d. Working tree clean; no excluded code/data in the complete committed diff. Static branch review was partial due context budgets, with one independently verified medium finding: offscreen opacity/isolation UI layers were still untagged RGBA8. Added red-green translucent-parent raster case (expected~.249 vs observed~.1065), propagated surface pixel format/color space into both layer paths, retained surface color-space ownership and preserved explicit byte-alpha quantization. Full follow-up commit review completed with no actionable findings; static only. Final zero-warning client/test builds,3671CPU tests (52skipped) and174 compiled shader artifacts pass after exclusion/fix. User requested PR to prk/engines develop; no existing open HDR PR found. After user restored SSH-agent availability, branch push succeeded and upstream is origin/feature/client-hdr-sdr-tonemapping. Opened PR #2652: https://git.project-rk.com/prk/engines/pulls/2652 — feature: Add linear HDR scene rendering and SDR composition. Head feature/client-hdr-sdr-tonemapping, base develop; experimental HDR tools/data/code excluded, production regression coverage retained. Two session commits c747d0917 and f2f734123; working tree clean. No force push, unrelated work, published-history rewrite or status change merely for opening the PR. PR testing retains partial-review and lifecycle/platform/overflow limitations.
Checklist
5 of 10 complete- Establish and verify color/alpha contracts for AO textures, authored colors, sky, lighting/emission, Skia UI and world nameplates within the confirmed internal-HDR-to-SDR scope.
- Build and verify a test-only independent OCIO/Neutral comparison harness, Vulkan float/blend/copy/encoding probes and actual Skia alpha/raster characterization; retain machine-readable candidate errors without selecting a production operator.
- Select a pinned licensed tone-mapping reference through numerical comparison and exposure/color stress tests; define relative intensity, finite/range and correct SDR presentation policies.
- Verify a read-only authored-environment census and GPU fog/body-shadow compatibility fixtures, recording current clipping, HDR-safe alternatives and appearance differences without changing production rendering.
- Implement shared capability-validated HDR scene/reflection/copy targets and unclipped linear world processing; verify >1 preservation, blending, resource rollback and lifecycle.
- Verify finite/coverage/exposure boundary and accumulation experiments plus staged AgX reconstruction against standard and independent dense references; retain fidelity, gamut, licensing and scene-wide range gaps explicitly.
- Implement one final SDR display transform and correctly composed UI/depth-tested nameplates; verify encoding, alpha edges, panel contrast and future pre-tone-map bloom boundary.
- Verify an independent lower-gamut projection and distinguish analytic response from baked LUT interpolation, including a native Vulkan reference probe with external hash-verified data and explicit licensing limits.
- Pass client/shader builds, numerical and lifecycle regressions, available Vulkan readback/validation; measure 4K memory/GPU cost and record user visual checks and platform gaps.
- User verifies and approves HDR visual scenarios and measured performance at 4K with High AO on the current host; preserve capture/build/profiler limitations without claiming isolated overhead or broad-platform approval.
- Area
- Project Rubi-Ka
- Project
- engines
- Kind
- Feature
- Task ID
tsk_3zyv3ddcyc- Updated