Luth Engine

July 13, 2026 · View on GitHub

Completed Epics

Summaries are intentionally terse — full writeups in history/, one file per epic slug.

VersionEpicSummaryDate
v1.0.0job-systemFiber-based scheduler: FLS, Chase-Lev work-stealing, MPMC queues, SpinLock, isolated main thread2026-03-07
v1.0.0frame-pipelineTriple-buffered pipelined execution (Game N / Render N-1 / GPU N-2), MAX_FRAMES_IN_FLIGHT=32026-03-07
v1.0.0render-graphDAG compile with dead-pass culling + batched barriers, serial outer with parallel inner recording2026-03-07
v1.0.0cleanupHot-path mutex → SpinLock, deleted GLAD/OpenGL remnants2026-03-07
v1.0.0rendering-debugFixed 5 rendering bugs: SceneColor disconnect, depth clear, bindless slot 0, Y-flip, front face winding2026-03-15
v1.0.0pbr-materialCook-Torrance BRDF, Material SSBO (Set 2), per-RenderMode pipeline variants2026-03-15
v1.0.0lighting-shadowsLightUBO (Set 3), ShadowPass (2048² D32), PCF 3×3, ECS-driven light collection2026-03-15
v1.0.0shader-systemShaderLibrary singleton, FileWatcher hot-reload, SPIRV-Cross reflection2026-03-15
v1.0.0inspector-editorSave button + dirty tracking, Add Component dropdown, DirLight shadow controls, albedo color picker2026-03-16
v1.0.0shader-asset-pipelineShaderImporter, SPIR-V artifact cache, stable UUIDs via .meta, MaterialSystem dirty sync2026-03-19
v1.0.0mipmap-generationvkCmdBlitImage chain, TextureSettings pipeline, sampler maxLod2026-03-19
v1.0.0scene-serializationJSON .luth format, Win32 file dialogs, editor File menu + dirty tracking2026-03-19
v1.0.0asset-lifetime-fixScene holds shared_ptrs to prevent GC eviction, full async load chain, VMA shutdown fix2026-03-21
v1.0.0frame-debuggerGPUTimerPool, RenderGraphSnapshot, split-panel UI, event slider, named texture registry2026-03-22
v1.0.0post-processingHDR (RGBA16F), bloom (extract + Gaussian blur), 4 tonemap operators, vignette, grain, CA2026-03-22
v1.0.0pipeline-cacheVkPipelineCache disk persistence, PipelineManager keyed by {shaderUUID, renderMode} with lazy creation2026-03-22
v1.0.0skybox-iblHDR equirect→cubemap, irradiance + pre-filtered env (5 mips) + BRDF LUT, split-sum ambient, Set 0 expanded to 4 bindings2026-03-23
v1.0.0polishBug fixes (transform/alpha/shadows), Rider theme, inspector overhaul, picking + outline, profiler rework2026-03-25
v1.0.0editor-qa22-item QA pass: semantic EditorColors, HDR picker, outline children+occluded, recursive search, primitive geometry, project filters/sort2026-03-30
v1.0.0animation-systemFiber-parallel sampling, GPU skinning (BoneMatrixBuffer SSBO), SQT blending, crossfade, layered override with masks, root motion2026-03-30
v1.0.0smart-import-hot-reloadMulti-strategy texture discovery, ImportReport/TextureRemapDialog, FileWatcher hot-reload, drop-to-current-dir2026-03-31
v1.0.0frame-debugger-upgradeTrigger-based capture, per-draw scrubbing, DebuggerState machine, RenderCapturedFrame replay, depth linearization2026-04-03
v1.1.0undo-redoCommand pattern (14 types), UUID-based entity resolution, gizmo drag coalescing, compound commands, material snapshots2026-04-09
v1.1.1architecture-cleanupRenderingSystem split (4060→2321 LOC): EditorCamera/CameraParams/IBL/FrameDebugger extracted; 9 passes moved to renderer/passes/2026-04-13
v1.2.0compute-gpu-cullingCompute pass + buffer support in render graph, GPU frustum cull shader, GPUObjectData SSBO, draws via vkCmdDrawIndexedIndirect2026-04-15
v1.3.0csm4-cascade PSSM (bounding-sphere fit), 4-layer shadow array, per-cascade GPU cull, shader cascade selection + blend + bias2026-04-16
v1.4.0frame-debugger-syncArchive sink + per-pass image staging, frozen-state auto-recapture, hierarchical EventNode tree, per-draw replay2026-04-17
v1.5.0gtaoDepthPrepass + half-res GTAO compute (prefilter → horizon integral → bilateral denoise), Jimenez 2016, Set 0 expanded to 62026-04-17
v1.6.0arch-cleanupFolder reorg: events/ extracted, utils/ dispersed, Components.h split, renderer/ subdivided into 7 concept folders2026-04-18
v1.7.0arch-renderer-splitRenderingSystem 3500→350 LOC: FrameTargets/DrawListBuilder/LightGatherer/CascadeBuilder extracted; RenderPipeline owns graph + resources2026-04-18
v2.0.0arch-target-splitEditor extracted from Luth.lib into Luthien.lib (~12k LOC); IEditorHooks breaks engine→editor include dep; Sandbox.exe descoped2026-04-18
v2.1.0shader-asset-pipelineSingle-stage shader assets (.vert/.frag/.comp = one artifact + UUID); ShaderHeader V2; all 24 engine shaders routed through pipeline2026-04-18
v2.2.0math-abstractionLuth::Math facade — single <glm/...> owner. 25 wrappers + templated constants. 37 files migrated, 38 glm includes purged outside facade2026-04-18
v2.3.0core-reorgluth/core/ split into types/ (LuthTypes/LuthMath/TypeTraits), diagnostics/ (Log/LogFormatters/Profiler), time/ (Time/Timer)2026-04-18
v2.4.0animation-splitluth/animation/ dissolved: BoneMatrixBuffer/Skeleton/AnimationClip → renderer/resources/; AnimationController → scene/components/2026-04-19
v2.5.0render-pipeline-splitRenderPipeline.cpp 3104→781 LOC across 7 topic files + new FrameDebuggerContext. CreatePipelines split into 8 per-family builders2026-04-19
v2.6.0rendering-system-slimRenderingSystem 533→395 LOC: LightingSystem + ShaderWatcher + PickingSystem extracted; cascades pass-by-value2026-04-19
v2.7.0editor-cleanupComment audit + Command.h reorg + CommandHistory deque + Editor::GetPanel<T> O(1) cache + Editor::Init decomposition2026-04-19
v2.7.1editor-style-assetsStylePreset → JSON assets (luth/assets/styles/*.json). EditorStyle 616→280 LOC. New LoadStyle(nameOrPath), Save Current As...2026-04-19
v2.7.2editor-widgets-reorgluthien/UI.{h,cpp} split into 5 widget files (Properties/AssetSlot/CollapsingHeader/InfoTable/TexturePreview) under widgets/2026-04-19
v2.7.3editor-undo-gapsAll 14 MarkDirty callsites wrapped in commands. New VectorElement/Insert/Erase + EntityActive commands. Drive-by Entity::isActive → Disabled tag2026-04-23
v2.7.4editor-component-registryHand-written DrawComponent<T> switch → type-erased ComponentDrawerRegistry. 8 drawers + DebugDrawers consolidated. InspectorPanel 976→255 LOC2026-04-23
v2.7.5editor-scene-panel-slimScenePanel 1001→443 LOC: ViewportRenderer + GizmoController + ViewportOverlays extracted to viewport/2026-04-23
v2.8.0play-modeEditor state machine (Editing/Playing/Paused) + JSON scene snapshot; AnimationSystem gated; CommandHistory blocks during play; transport bar + viewport tint2026-04-23
v2.8.1game-panelDedicated Game panel rendering first Camera entity with letterbox; new RenderView + ViewResources cache; per-instance resize callback2026-04-24
v2.8.2engine-consolidationAudit-driven housekeeping: roadmap restructure + 4 new arch docs (memory/profiling/validation-layers/version-glossary) + comment-banner sanitization + Tracy global memory hooks + CPU coverage gaps filled2026-04-25
v2.8.3tracy-on-demandHotfix: TRACY_ON_DEMAND so Tracy macros no-op when no profiler client connected. Fixes 10 MB/s launcher leak + 0.2 MB/s in-game leak (#30)2026-04-25
v2.8.4pipeline-phase-3Pipelined CPU stages (Game N / Render N-1) on worker fibers; RenderSnapshot POD frozen at game-stage end; two-phase RG dispatch (1 yield/frame); 4 latent JobSystem bugs fixed along the way2026-04-26
v2.8.5build-config-foundationluth/core/BuildConfig.h centralizes detection (LUTH_BUILD_DEBUG/RELEASE/DIST + derived flags); engine no longer tests _DEBUG/NDEBUG; first effort under tag-only release policy2026-04-27
v2.8.6frame-debugger-polishUnity-style draw-scrub slider + tree-click snap; viewport pass overlay; depth archives via tonemapped preview; archive reuse + 10 Hz throttle eliminating editor freezes under continuous camera movement2026-04-28
v2.8.7vulkan-correctnessTier-1 hardening: deletion-queue SpinLock (V1), RG WAW barriers, swapchain OUT_OF_DATE + deferred Present rebuild (V2), device Features2 validation, sync2 frame submit + RG-driven present transition2026-04-28
v2.8.8animation-quick-passAnimationClip becomes UUID-addressable asset (.anim); Animation::ClipUUID replaces integer indices; drawers swap Combo for PropertyAsset; foundation for animation-controller-v22026-04-28
v2.8.9persistent-buffer-ringTriple-buffered the 3 persistent CPU-mapped SSBOs (ObjectSSBO/IndirectBuffer/Material SSBO) so frame N writes never overlap frame N-1 GPU reads; VMA modernized off deprecated CPU_TO_GPU2026-04-28
v2.8.10gpu-tagged-heapGPU half of Naughty Dog Onion/Garlic: GPUTaggedPageAllocator (2 MB pages from host-mapped backings, bulk-free wired to GPU N-2 timeline); Sets 2/4/5 rebind per-frame via UPDATE_AFTER_BIND2026-04-29
v2.8.11slot-alloc-spinlockClosed v2.8.4 D6 carry-over: MaterialSystem::m_Lock + BoneMatrixBuffer::m_LockLuth::SpinLock; arch doc sweep2026-04-29
v2.8.12shader-reload-asyncDrops per-save vkDeviceWaitIdle from shader hot-reload; defers old pipelines via PushDeletion (V1 SpinLock-safe), drained 3 frames later; shader save no longer drops a frame2026-04-29
v2.8.13vulkan-polishTier-2/3 cleanup before jolt-physics: validation messenger pNext-chained; BindlessDescriptorSet LIFO free-list; RenderResourceCache keyed on multimap; outline/grid push-constants routed through EditorSettings2026-04-29
v2.8.14texture-async-uploadsTexture half of vulkan-polish S4: UploadImageMipped records pre-barrier → staging copy → blit chain → SHADER_READ_ONLY in one cmd-buffer; 4-slot cmd-buffer ring; deferred-bindless-registration pump2026-04-30
v2.9.0editor-foundationFirst AAA editor effort: Gather→Draw lifecycle for panels (parallel gather on worker fibers; main-thread ImGui draws from frozen snapshots); per-panel LinearAllocator(64K) gather scratch; 9 panels migrated2026-05-01
v2.9.1editor-signal-busTyped EditorSignal events on EventBus (selection/hierarchy/asset/project/play-state); panels react to subscriptions instead of polling; EventBus hardening (exception-safe dispatch, SubscriptionHandle, tracked alloc)2026-05-02
v2.9.2editor-console-errorsNew Log::AddSink / ILogSink interface + ConsolePanel (sink + signal-based append, level filter, search, clipper); per-panel error boundary with stack-trace dump via Win32 DbgHelp2026-05-02
v2.9.3editor-job-pumpNew MainThreadPump static facade — Post(Callback) any thread, Drain() on main; mirrors v2.9.1 EventBus shape (swap-and-drain, thread-assert, per-callback try/catch); foundation for autosave + thumbnails2026-05-02
v2.9.4editor-autosaveFirst MainThreadPump consumer: periodic side-channel autosave to <project>/.luth/autosaves/ (never canonical); JSON snapshot on main + file write via IOThread; crash-recovery prompt in OpenScene2026-05-03
v2.9.5editor-thumbnailsProjectPanel grid switches from FA-icons to rendered previews; ThumbnailCache (UUID-keyed, signal-driven invalidation), ThumbnailGenerator (worker-fiber CPU bake), ThumbnailPreviewScene (Lambert + ambient); disk-persisted2026-05-03
v2.9.6editor-undo-fixSlider-driven inspector edits stop over-coalescing across release boundaries; new EditState { changed, committed, itemId } from every UI::Property*; per-T pre-edit value stash between IsItemActivated/IsItemDeactivatedAfterEdit2026-05-04
v2.9.7editor-panels-polishScenePanel toolbar re-laid (gizmo group + transport + render-mode + Debug/Camera/Gizmos splits); HierarchyPanel per-row visibility eye; ProjectPanel grid → BeginTable + clipper; new EditorSettingsWindow; Window menu + Reset Layout2026-05-04
v2.9.8editor-inspector-polishEditorClipboard + ComponentReset/ReplaceCommand + per-component JSON Copy/Paste in all 8 drawers; new InspectorHeader + Splitter widgets; live orbit-cam 3D preview for Material/Model in pinned footer2026-05-05
v2.9.9editor-workspacesAAA editor rework closeout: multi-named workspaces (.ini + .workspace.json sidecar); built-in Default ships at luth/assets/workspaces/; built-in shadows user copy of same name2026-05-05
v2.9.10staging-ring-wrap-overlapHotfix for two boundary-condition bugs in UploadContext::AllocateStaging producing intermittent banded pixel mixing under burst loads; explicit wrapped tracking + head == tail full-ring detection2026-05-05
v2.9.11render-hardeningAudit batch: per-frame UBOs migrate to GPUTaggedPageAllocator; FreeTag(waitValue - 2) correction (iter T+1 retires before its tag T pages free); Set 3 multi-view race fix; cross-view shadow sync2026-05-06
v2.9.12render-pipeline-subsystemsRenderPipeline god-class extracted into 6 per-domain subsystems (Global/Lighting/Geometry/GTAO/PostProcess/EditorOverlays), each owning lifecycle + descriptor sets + passes; friend coupling fully gone; RP shrinks to ~650 LOC orchestrator2026-05-06
v2.9.15frame-debugger-polish-v2Batch-handles 3 deferred issues (#98 event-tree group ordering, #99 Source = Game replay, #100 Shadow/DepthPrepass/SelectionMask per-draw replay) + 8 drive-by polish; auto-recapture extends to IBL/skybox; cascade label shows splits range2026-05-07
v2.10.0jolt-rigid-bodiesTier 0 of Jolt: vendor + LuthJobSystemForJolt adapter, Collider/RigidBody/PhysicsBodyRuntime components, kinematic/dynamic transform sync, debug-draw subsystem, CCD motionQuality; WaitForCounter UAF fix along the way2026-05-14
v2.10.1jolt-physics-queriesRaycast + OverlapBox/Sphere/Capsule; LuthContactListener (Godot-pattern trigger cache under SpinLock); 4-kind event surface + per-frame DrainEvents2026-05-15
v2.10.2jolt-physics-assetsPhysics::ShapeCache (UUID + meshIndex + shapeKind keyed) for ConvexHullShape + MeshShape from Model data; PhysicsMaterial UUID-keyed asset; ModelImportSettings::PhysicsBakeMode { None, Auto } opt-in; hot-reload via AssetDatabase callback2026-05-15
v2.10.3jolt-character-controllerTier 1 JPH::CharacterVirtual (requires paired Collider Type::Capsule); ExtendedUpdate defaults for stair/stick-to-floor; debug-draw colored by GroundState; stub PlayerControllerSystem until scripting lands2026-05-18
v2.11.0custom-fibersCustom x86_64 MASM context switch + VirtualAlloc stacks replaces Win32 fibers so ASan can track per-fiber stack bounds. TIB ArbitraryUserPointer (gs:[0x28]) replaces Win32 FLS. ~5× faster switch (secondary win)2026-05-19
v2.11.1foundation-testing28-case stress harness (V1–V6, AtomicCounter, LinearAllocator, TaggedPageAllocator, SpinLock, MPMCQueue, WorkStealingDeque) under DebugASan; caught two engine bugs inline2026-05-20
v2.12.0async-compute-queueThree-queue Vulkan foundation (graphics + async-compute + transfer) with per-view 3-submit topology, RG queue routing + cross-queue barrier rule (TOP_OF_PIPE substitution on the reader's pre-barrier), CONCURRENT sharing opt-in per cross-queue resource, GTAO chain routed to async compute, UploadContext routed to dedicated transfer queue (DMA engine on discrete GPUs)2026-05-20
v3.0.0bindless-migrationrt-renderer arc opens. BDA enabled (mesh + tagged-heap buffers carry SHADER_DEVICE_ADDRESS_BIT, addresses cached); Set 1 binding 1 sampler array (4 canonical at 0-3, LIFO 4-31); GPUMaterialData 4→8 map indices + flag repack; thumbnail_mesh migrated to bindless (−120 LOC)2026-05-22
v3.0.1slim-gbufferSlimGBufferPass writes RG16F oct-normal + R8 roughness + RG16F NDC motion + R16U matID (depth-EQUAL, opaque-only). Adds prevViewProjection per-view (multi-view contamination fix), prevModel cache, dual-region BoneMatrixBuffer for prev-bones. Frame-debugger decoders + 4 ShadeMode toggles2026-05-22
v3.0.2forward-plusOlsson 3D clustered lighting (16×9×24 grid) replaces 64-light UBO. ClusterBuildPass + LightAssignPass (async-compute) write per-cluster AABBs + atomic-packed light indices; PBR fragment loops cluster's lights (cap 128). Set 3 reshaped to 4 bindings, per-view. New ShadeMode::ClustersDensity 3D heat-map2026-05-22
v3.0.3volumetric-fogWronski frustum voxel fog (160×90×128 RGBA16F per view). Async-compute inject (cluster lights + CSM + Component::FogVolume) + integrate (front-to-back Beer-Lambert); graphics composite via standard alpha-blend. New VolumetricSubsystem, VKTexture 3D ctor, tagged-union FogVolume component + inspector + master toggle2026-05-23
v3.0.4volumetric-validationCloses v3.0.3 known VUIDs. Image barriers set VK_QUEUE_FAMILY_IGNORED (was zero-init for CONCURRENT images); inject/composite declare per-cascade shadow + atlas reads so RG emits DSA → SHADER_READ_ONLY barriers; inject layout gains UPDATE_AFTER_BIND for per-frame rewrites. New 4th RG hazard documented2026-05-23
v3.0.5volumetric-fog-polish (initial)Three deferred sub-tasks (Hillaire multi-scatter, temporal accumulation, ShadeMode viz) plus audit fixes. Temporal moved to dedicated VolumetricResolve compute pass after integrate (inject-time blend produced energy non-conservation). HG phase, point-light 1/d², sun-fog absorption, IBL irradiance multi-scatter, half-step transmittance, configurable Quality preset, shader #include support, k_ClusterSlicesZ 24→482026-05-24
v3.0.6volumetric-fog-polish (follow-up)Inject split into density + scatter passes joined by RG barrier (enables density-atlas sampling along sun ray — v3.0.5's single-pass SunFogTransmittance was dead-code). Canonical σ_t contract drops spurious × density in scatter (was double-applying). New scatteringIntensity knob, 3D Worley-FBM density noise, recalibrated defaults2026-05-25
v3.0.7image-qualityCloses Phase A. Tokuyoshi19 specular AA + AgX + AgX Punchy tonemaps. Karis14 TAA YCoCg-clip K3 — Halton(2,3) prefix-8 jitter, per-view RGBA16F history ping-pong, ~170 LOC resolve (closest-depth velocity, Blackman-Harris reconstruction, chroma narrow, luma feedback). Roberts R2 blue-noise volumetric dither; pbr.frag → common/globals.glsl2026-05-25
v3.0.8rt-extensionsPhase B opener: 4 KHR RT extensions enabled + feature validation + RT-mandatory device check; VKRayTracingPipeline + RtShaderBindingTable factories; validation-gated no-op traceRays smoke test2026-05-26
v3.0.9blas-tlasPer-mesh BLAS at import (static + skinned, skinning.comp deformed-VB + MODE_UPDATE refit); per-frame TLAS rebuild on AsyncCompute with FNV-1a hash dirty-skip; Set 0 → 7 bindings (TLAS at b6)2026-05-26
v3.0.10rt-shadowsFirst RT-pipeline consumer: ray-traced sun shadows (1-spp, R8 mask) as default, raster CSM kept as ShadowingMode compare toggle; RtSunShadowsPass on AsyncCompute; pbr.frag CSM/RT dispatch. Closes Phase B2026-05-31
v3.0.10gpu-debug-toolkitGPU crash-debugging subsystem: dynamic-load Nsight Aftermath, LUTH_VALIDATION runtime tiers (GPU-AV opt-in), per-pass NV checkpoints + device-lost dump; new arch/gpu-crash-debugging.md2026-05-31
v3.0.11vulkan-sync-hardeningClean sync-val baseline before Phase C: GPU object names + RG pass labels, LUTH_RG_DUMP/LUTH_RG_TRACE + headless solver tests; loadOp-LOAD, swapchain-acquire, cross-queue-WAW barrier fixes2026-06-06
v3.0.11gpu-device-lostRoot-caused the recurring skinned VK_ERROR_DEVICE_LOST: 4 MiB BoneMatrixBuffer took the tagged-heap large-one-shot destroy path; ND-model fix recycles large tagged allocs (never vkDestroyBuffer)2026-06-06
v3.0.12restir-diBitterli 2020 ReSTIR DI for point lights: device-local (Garlic) reservoir buffers, rayQuery-in-compute initial RIS + temporal + spatial reuse, demodulated diffuse irradiance remodulated in pbr.frag, editor tuning; opens Phase C2026-06-07
v3.0.13svgf-denoiserSchied 2017 SVGF over the demodulated ReSTIR DI: reproject (temporal EMA + moments) → variance (7×7 spatial fallback) → edge-aware à-trous, behind an IDenoiser abstraction; per-view image history + a GENERAL-preserving RG storage read; feedbackTap=−1, A-SVGF deferred2026-06-07
v3.0.14restir-giOuyang 2021 ReSTIR GI: per-pixel 1-bounce path reservoirs + reconnection Jacobian (temporal/spatial reuse, RTXDI BASIC); real bindless secondary-hit material via a per-frame geometry table (instanceCustomIndex contract change + buffer_reference deref); second channel-parameterized SVGF instance denoises the bounce; editor tuning2026-06-08
v3.0.15gi-polishPost-restir-gi follow-ups: TLAS builds for any RT consumer (DI/GI no longer dark under CSM), sun light added to the GI bounce, GI reservoir M·age debug view, and a zero-weight-reservoir fix (unlit hits keep a valid sample point — killed a world-plane confidence split + latent shadowed-region reuse bias)2026-06-09
v3.0.16path-trace-referenceGround-truth path-traced reference mode: a rayQuery-in-compute megakernel reusing the TLAS + bindless materials, multi-bounce NEE + full Cook-Torrance BRDF + GGX VNDF lobe-MIS, fp32 progressive accumulation reset-on-change, RenderMode toggle + editor convergence UI; validates ReSTIR DI/GI convergence2026-06-09
v3.0.17rt-reflectionsStochastic RT specular reflections: GGX-VNDF rays from the slim G-buffer, NEE hit shading via the geometry table, demodulated split-sum composite in pbr.frag below a roughness cutoff; dedicated specular denoiser (SvgfDenoiser Reflections channel, hit-distance virtual reprojection); shared common/brdf.glsl. Supersedes SSR; opens Phase D2026-06-09
v3.0.18volumetric-rt-shadowsPer-froxel RT shadow rays in the fog inject-scatter pass: point lights + arbitrary occluders now cast fog shadows (sun swaps CSM→RT ray), 1-spp softened by the existing temporal resolve (no denoiser), default-off toggle; TLAS-build hoisted before the volumetric chain for registration-order correctness; folds two post-D.1 ReSTIR-DI audit fixes2026-06-09
v3.1.0emissive-parityOpens the material-system arc: emissive factor + HDR strength, identical raster (pbr.frag) + RT (geom_table.glsl) emission closing the raster≠RT bug, editor controls + import bridge + inspector preview + ShadeMode::Emission; folds material-authoring fixes — metal/rough/color reach the GPU via direct fields, textureless emissive editable, no autosave reimport bounce2026-06-09
v3.1.1material-eval-seamRetires pbr.frag's hand-duplicated Cook-Torrance onto the shared common/brdf.glsl seam — raster==RT BRDF parity now structural (single source); neutral-renamed the Pt* functions; fp32-identical output2026-06-10
v3.1.2cutout-rtAlpha-cutout materials hole correctly in every RT path: per-instance TLAS opaque flag (cutout→FORCE_NO_OPAQUE) + a shared geom_table.glsl candidate-loop alpha-test across PT/GI/reflections/ReSTIR-DI/volumetric fog; rewrites the lone RT-pipeline sun-shadow pass to rayQuery-compute, deleting the SBT/rgen/rmiss2026-06-11
v3.1.3transparency-tierTransparent tier after the fog composite (fixes #32 skybox compositing): PPLL OIT default (per-view heads image + Garlic node pool, sort-K resolve) + per-view sorted A/B; corrected transparent shading — rayQuery sun shadow, cluster lights, fragment-depth froxel fog; TLAS cull masks make glass shadow-ray-invisible yet GI/reflection/PT-visible2026-06-11
v3.1.4rt-normal-mapsRT hits sample the normal map (TBN at the rayQuery hit, inverse-transpose normal matrix matching pbr.vert) + the occlusion map (HitSurface.ao → rt-reflections' IBL ambient; PT omits it — geometric occlusion); GI keeps its geometric secondary normal; dead alpha/specular/thickness indices marked reserved2026-06-11
v3.1.5restir-di-specularReSTIR DI gains specular: combined diffuse+spec RIS target (initial/temporal/spatial), demodulated F0-free specular from the shade pass, a dedicated 4th SVGF channel (surface-motion reproject), and pbr.frag F0-remodulation under restirParams.z; fixes metals/specular getting ~nothing from point lights2026-06-11
v3.1.6model-import-fidelityImporter fidelity: faithful DCC node graph → entity tree (Model V4, un-baked static meshes), camera + light import, render-mode/cutout/cull/occlusion/UV1 from Assimp, engine-side Scene::InstantiateModel; fixes a node-rotation decompose-conjugate inversion; sRGB attempt reverted (editor not sRGB-aware)2026-06-12
v3.2.0slang-spikeOpens the slang-material series — Phase-0 gate GO: in-process SlangCompiler (Slang 2026.1) coexisting with libshaderc, one rayQuery+BDA+nonuniform-bindless shader ported, in-engine A/B bit-identical, link-spec valid across 2 stages; slang#10525/#9578 clear; default-off harness merged as the regression guard2026-06-13
v3.2.1slang-toolchain.slang through the asset pipeline (stage via Slang reflection) + ShaderWatcher hot-reload, coexisting with GLSL; spike promoted to a SlangParityGuard whose gate is a deterministic SPIR-V NonUniform/caps scan, with the pixel A/B kept default-off as a diagnostic; multi-entry link probe retired2026-06-13
v3.2.2slang-imaterialBounded material.slang + one generic two-tier eval (RasterFetch/RayFetch) shared by all 9 raster/RT/transparent/volumetric consumers; production GLSL seam deleted; fixed a Slang global-session concurrency crash (fresh session per compile); drift-guard + spike retirement deferred2026-06-14
v3.2.3slang-material-cleanupCloses the slang-imaterial follow-ups: an init-time MaterialLayoutGuard cross-checks GPUMaterialData/GlobalUniforms field offsets against their .slang twins via Slang reflection; full Phase-0 spike retirement — SlangParityGuard is now a gate-only SPIR-V scan on production restir_gi_initial.slang, deleting the pixel A/B + geom_table.glsl2026-06-14
v3.2.4packed-texture-routingImport-side remap into the bounded channels: ORM aliasing, separate-map + spec-gloss derived-texture bake, DirectX-normal green-flip, TextureRole auto-detect + editor override; zero GPU-struct change, raster==RT by construction2026-06-14
v3.2.5material-node-editorBlender-style channel-routing node graph → generated per-material Slang; raster per-material pipeline + RT variant-registry megakernel dispatch (raster==RT); 9-node vocab, live recompile, undo; effect-layers / transparent / constants-as-data deferred2026-06-14
v3.2.6graph-param-bufferGraph constants routed to a per-material gMatParams buffer (binding 1 on the shared material set, both tiers) + structure-hash-keyed variants; value edits become data (no recompile), structurally-identical materials share one shader + variant, killing the recompile hitch + 16-cap + per-edit pipeline leak2026-06-14
v3.2.7transparent-graphNode-graph materials decode through the shared variant registry in the raster transparent pass (sorted + OIT), closing the raster≠RT gap; one decode-seam swap + the two transparent shaders join the registry-reload set2026-06-14
v3.2.8material-authoringM1-authoring closeout: graphed mi.normal honored raster==RT (tangent-space convention + ApplyTangentNormal), lightweight graph-aware material preview (self-contained PreviewFetch UBO + per-structure Lambert-over-graph consumer), searchable node quick-add; granular per-node undo deferred2026-06-14
v3.3.0vertex-deformationGPU deformation seam (#161, D1–D4): one graphics-queue compute pass writes a per-asset deformed buffer that raster + the RT BLAS/geometry-table share (raster==RT structural); skinned→static-deformable generalization; procedural wind = global field (gusts/turbulence/world-space dir) + per-entity Component::Wind; empty-descriptor deform.comp, last-writer-wins per-asset2026-06-19
v3.4.0profiling-observabilityComprehensive Tracy coverage (~300 CPU zones to peer density, per-queue GPU zones graphics+async-compute, per-frame plots, event messages) + in-editor GPU pipeline statistics (per-pass overdraw) + render-graph barrier inspector; scheduler advisor deferred to #163 (touches V1–V6 hot paths, research-first)2026-06-20
v3.5.0engine-optimizationGPU perf pass on the RT chain: half-res ReSTIR DI/GI/Reflections (trace + denoise at half, shared bilateral upscale, default-off toggles; ~17% GI), DiSpec/GTAO/bloom skip when off, PathTrace gates the realtime pipeline at registration; tracker #164 stays open2026-06-20
v3.6.0editor-panel-redesignRender panel 21 flat headers → 5 pipeline-stage tabs + search (drops Preferences dupes, unifies SVGF, surfaces hidden fields); Profiler rebuilt as a scheduler/memory/GPU dashboard (per-worker time-in-state occupancy, GPU-mem by resource type, EMA-stable per-pass table) on new JobSystem/VMA instrumentation; searchable Scene debug picker + 7 new ShadeModes; new FilterBox/CategoryPopup/Charts widgets (DiReservoir/Overdraw deferred)2026-06-21
v3.6.1editor-qolEditor UX papercuts: New Folder auto-renames on create, Project panel refreshes on drag-drop import, single-mesh imports drop the redundant scene-root wrapper entity, Disabled cascades to whole subtrees in render, default window 1600×900 centered on the monitor2026-06-28
v3.6.2render-correctnessThree renderer fixes: persistent reservoir/OIT Garlic buffers destroyed (deferred N+2) on resize instead of recycle-orphaned in the device-local pool (fixes monotonic VRAM leak), grid + selection-mask overlays reconstruct an un-jittered projection (kills TAA shimmer), TLAS dirty-hash folds the full world matrix so static-mesh rotation/scale rebuilds the acceleration structure2026-06-28
v3.6.3hierarchy-multiselectMulti-select consumers catch up to the selection vector: batch delete as one undo, Inspector apply-to-all (per-member + Reset/Remove/Add/Active via a MultiEdit broadcast context; combos primary-only), gizmo transforms the whole group about the active pivot, LMB-drag marquee select, Hierarchy shift-range select, viewport click-to-drill (root first then exact part), Delete from Hierarchy or viewport focus/hover, gizmo W/E/R on hover2026-06-28
v3.6.4spotlightsThird punctual light type on the point path: SpotLight component + 64-B SpotLightData appended to the Set 3 light SSBO, clustered via the shared tagged index list (sphere test, high-bit tag) with cone-attenuated EvalClusteredLights, unshadowed MVP; inspector drawer + hierarchy create/icon + scene/clipboard serialize + Assimp aiLightSource_SPOT import; cone gizmo + RT spot shadows deferred2026-06-28
v3.6.5debug-draw-gizmosEditor in-world gizmos moved off ImGui onto the GPU debug-draw line pass: new DebugDraw wire-shape helpers (box/sphere/frustum/cone/arrow/cross), a game-stage DebugGizmos producer for lights/cameras/AABBs/bones gated by EditorViewportState toggles+palette, the deferred spot-light cone, FogVolume moved off its racy render-stage slot, ViewportOverlays thinned to click-select icons; depth-tested variant deferred2026-06-28
v3.6.6bloom-pyramidPost bloom replaced with a Jimenez/CoD progressive compute pyramid: a Karis soft-knee prefilter + 5 13-tap downsamples + 5 tent upsamples accumulate additively up ViewResources::bloomMip[6] (per-view RGBA16F storage, RG-handle-threaded, in-place-RMW barrier) composited × strength; new radius/scatter knob, threshold/strength preserved, net VRAM win over the old 2-buffer 9-tap Gaussian2026-06-29
v3.6.7log-channelsPer-subsystem LogCategory channels (Core/Assets/Shaders/Renderer/Jobs/Physics/Scene/Editor, one spdlog logger each over shared sinks) threaded through every log site via LH_LOG(cat,level) (legacy LH_CORE_* kept as the Core alias); Console filters per-channel (all on, level filter hides Trace/Debug noise, errors bypass) with a per-row channel tag; stdout sink curated to INFO while Luth.log/editor keep full trace; reflection/per-asset/VMA spam demoted, Trace/Debug compiled out in Dist, SetConsoleOutputCP fixes em-dash mojibake2026-06-29
v3.6.8phosphor-iconsEditor icons rebuilt on Phosphor (Regular+Fill) behind a single semantic Icons.h remap + generated headers, dropping Font Awesome + the orphaned luth_icons font; Blender-style color-coded hierarchy entities (lights/camera/bone filled), filled transport/carets/render-mode states, sphere/circle render-mode glyphs, Scene/Material-Graph tab icons; new serialized Component::Bone marks skeleton joints2026-06-29
v3.6.9gizmo-optionsNon-transform gizmos gain the physics Selected/All scope model (lights/cameras/bounds/bones/fog/wind) with shared unselected-alpha; new per-entity Wind direction arrow + scoped Fog; fog/wind viewport billboard icons (click-select, hierarchy-purple); all scene icons distance-scaled + clamped off the 64-px fill bake (crisp) with an Icon Size slider; per-category labels de-duplicated2026-06-30
v3.7.0slang-shader-migrationCompletes the GLSL→Slang move: the remaining 58 non-material shaders ported (compute/raster/bindless/BDA), the dead RT-pipeline smoke path deleted, and libshaderc + the glslc toolchain removed — one shader language, one compiler, one reflection path2026-06-30
v3.7.1shade-modesDebug shade-mode overhaul: data views (normals/IDs/channels) bypass bloom/tonemap via a passthrough sentinel, radiance views keep tonemap, raw GI/DI/reflection flag magenta when off, ShadowCascades/AO decoupled from global toggles, flat unlit wireframe + a new Shaded Wireframe overlay, transparent debug parity; folds no-fallback-sun (#33), back-face outline (#35), new-scene light+camera (#38).2026-07-02
v3.7.3exposed-parametersNamed graph parameters (name/group/ui on value nodes, codegen-blind) editable in the Inspector through the gMatParams data path; graph undo re-derives GPU state; variant cap 16→64 with beyond-cap stock in all tiers; coalesced registry reloads2026-07-03
v3.7.3static-switchesCompile-time StaticSwitch node: the canonical DFS emits only the selected branch (the other dead-strips from source + params), state flips are structure edits hash-cached both ways; named switches surface as Inspector checkboxes2026-07-03
v3.7.3node-breadth16 new node types: math breadth, UV source + TextureSample UV pin, FBM Noise (graph_lib.slang, conditional import), fetch-context inputs (WorldPos/ViewDir/Time/Fresnel via ITexFetch getters filled per tier), sandboxed custom-Slang expression node; VertexColor deferred2026-07-03
v3.7.3effect-layerComposable layers on the graph codegen: a MaterialInputs "layer" wire with MakeLayer/LayerBlend/Output.Surface (per-channel mask blend, normalized-lerp normal) + Triplanar (WorldNormal-weighted 3-plane) and DetailNormal (RNM) effect nodes from common/effects.slang; derivative-free raster==RT, typed pins, effect scalars as gMatParams data, hash-stable slot-6. Closes #1572026-07-06
v3.7.4importer-hardeningModel-import hardening: project-wide texture resolution + convention auto-bind, nested glTF texture copy, vertex format v6 (tangent handedness sign, vertex colors consumed into albedo raster==RT), opt-in material reimport-refresh, InstantiateModel multi-child-root crash guard2026-07-10
v3.7.5async-blas-buildMain-thread load/import freezes removed: ray-tracing BLAS builds deferred onto the async-compute TLAS pass (non-blocking poll, ready-gen folds late builds in, per-mesh gating); asset import made non-blocking end to end (per-UUID in-flight guard, atomic artifact writes, fire-and-forget ImportDirty, worker-thread drag-drop ingest, defer-until-loaded thumbnails). Closes #1712026-07-10
v3.8.0rt-noiseReSTIR DI/GI noise + firefly reduction: RTXDI BASIC generalized-balance MIS (DI temporal+spatial, GI temporal) replacing biased 1/M, reuse final-visibility folded into post-spatial history, LDS group-outlier boiling filters, IGN-rotated Vogel reuse kernels, SVGF anti-firefly + roughness edge-stop + DI-spec demodulation + reservoir-confidence guides; jitter-consistent ReSTIR reconstruction removes the TAA-jitter flicker (de-jitter recon on all passes, a deliberate RTXDI deviation); TAA resolve hardening (NaN-safe history, Karis HDR-weighted blend, Catmull-Rom resample, sky reprojection). Closes #1722026-07-11
v3.9.0texture-compressionImport-time BCn block compression (~4x texture VRAM, fixes Sponza/Bistro OOM): bc7enc/rgbcx bake BC7 color/ORM + BC5 normals with a CPU mip chain, role-driven Auto policy + per-texture override (BC1/BC4/None), texture artifact V2 (MipLevels + version-reject, async LoadJob self-heal migration), transfer-queue pre-baked upload (no runtime blit), textureCompressionBC device gate, shader tangent-normal Z reconstruction. Closes #1732026-07-11
v3.10.0emissive-area-lightsEmissive-material triangles as sampled area lights: unified point+triangle local-light list + power-weighted alias table appended to the LightSSBO (no new binding/PC), (lightIndex, uv) DI reservoir re-instantiated per site (identity-shift, no Jacobian, area measure), MIS-free partition (DI owns emitter→primary, GI on-hit seed gated off via a geometry-table emitter bit + emitter NEE at the secondary hit, reflections keep on-hit + complementary DI-spec routing), render-side gatherer with instance-hash cache; PT-reference-validated. Closes #1742026-07-11
v3.11.0surface-detailTwo material-graph surface effects: parallax-occlusion (TangentViewDir + SampleLod fetch seam, repurposed-specular height slot, GraphParallax steep-march raster==RT) + UV-space decals (GPUMaterialData 80->96 decalIndex, GraphDecal inverse-affine box-masked placement, dual layer/value node); volume decals deferred to FUTURE. Closes #1752026-07-12
v3.12.0shading-modelsFour production shading models on the shared SurfaceBRDF, raster==RT (analytic + PathTrace reference): clear-coat + anisotropy, dielectric transmission (exact IOR Fresnel + Walter GGX BTDF + Beer-Lambert, RT refraction closing the transparent-as-opaque gap), Estevez Charlie sheen, Burley/random-walk subsurface; MaterialInputs/GPUMaterialData 96->160 B, per-model graph pins + glTF import, screen-space DI/reflections keep the primary surface isotropic/sheen/SSS-free (Option A). Closes #1762026-07-12
v3.13.0material-authoring-redesignConsolidation over the locked material surface (not a rewrite): terminology (IOR-driven dielectric F0 + specular weight, thickness split into glass + subsurfaceThickness, scatterRadius->subsurfaceRadius, glossary), HDRP-style inspector IA (Base group, packed Mask Map, feature-gated lobes), graph-declared Blackboard inputs (MaterialGraph::properties + PropertyRef -> fetch.Param, declared textures via a per-material gMatTexParams SSBO lifting the fixed-8-map ceiling, raster==RT==PT), + the deferred subsurfaceThickness graph pin. Closes #1772026-07-13

Planned Epics

Effort scale (scope/difficulty, not calendar time): S = small, contained · M = some design decisions · L = significant refactor or new system · XL = full new subsystem.

Closed series — rt-renderer (Mode A, v3.0.0 → v3.0.18) ✅

CLOSED — milestone Release "RT Renderer" published; every effort is in the completed table above.

RT-first renderer modernization arc. Clustered Forward+ with bindless throughout, hardware ray tracing for shadows / GI / reflections, full Wronski volumetrics, ReSTIR + SVGF denoising, path-traced reference mode. Target showcase: Bhaal Temple, fully RT-lit. RT-mandatory (raises minimum HW to RT-capable GPU — counted toward the MAJOR bump).

Mode A — series start bumps Version.h to 3.0.0 (bindless-migration). Intermediate efforts PATCH-bump from there (v3.0.1 onwards), tag-only — no per-effort Release. Milestone Release at series end.

Umbrella issue: #127 (sub-effort issues created on demand; commits use Part of #127 trailer).

Phase A — Modern foundation

EffortIssueSizeNotes
A.1 bindless-migration#128LBDA enabled + Set 1 sampler array + GPUMaterialData 8 maps + thumbnail_mesh bindless — shipped v3.0.0
A.2 slim-gbuffer#129MNormal RG16F + roughness R8 + motion RG16F + matID R16U; prev-VP/model/bones plumbed — shipped v3.0.1
A.3 forward-plus#54LClustered lighting + light assignment (existing #54 scope) — shipped v3.0.2
A.4 volumetric-fog#130LWronski frustum voxel; light injection + integrate + composite — shipped v3.0.3 (+ volumetric-fog-polish v3.0.5/v3.0.6 #132 added temporal resolve, split inject + canonical math contract, scatter intensity knob, noise modulation)
A.5 image-quality#135MTAA Karis14 YCoCg-clip recipe + specular AA Tokuyoshi19 + AgX/AgX Punchy tonemaps + blue-noise volumetric dither — shipped v3.0.7. Closes Phase A.

Phase B — Hardware RT foundation ✅

EffortIssueSizeNotes
B.1 rt-extensions#137M4 KHR RT extensions + feature validation + RT pipeline/SBT factories + RT-mandatory device check — shipped v3.0.8
B.2 blas-tlas#138LPer-mesh BLAS (static + skinned) + per-frame TLAS rebuild on AsyncCompute + hash dirty-skip — shipped v3.0.9
B.3 rt-shadows#140LRT sun shadows default; raster CSM retained as ShadowingMode compare toggle (not retired — A/B precedent) — shipped v3.0.10. Closes Phase B.

Arc-adjacent efforts shipped alongside Phase B (tag-only): gpu-debug-toolkit (v3.0.10) — GPU crash-debugging subsystem; vulkan-sync-hardening + gpu-device-lost (v3.0.11) — clean sync-val baseline + the skinned device-lost root-cause, ahead of Phase C's barrier-heavy work.

Phase C — RT global illumination

EffortIssueSizeNotes
C.1 restir-di#146XLBitterli 2020 — device-local reservoirs + rayQuery RIS + temporal/spatial reuse + demodulated DI — shipped v3.0.12
C.2 svgf-denoiser#147XLSchied 2017 SVGF + IDenoiser abstraction (NRD/RELAX swap reserved); feedbackTap=−1, A-SVGF + feedbackTap=1 deferred to a gated follow-up — shipped v3.0.13
C.3 restir-gi#127XLOuyang 2021 — path reservoirs + reconnection Jacobian + real secondary material (geometry table) + 2nd SVGF instance — shipped v3.0.14

Phase C.5 — Path-traced reference mode

EffortIssueSizeNotes
C.5 path-trace-reference#148MrayQuery megakernel + multi-bounce NEE + full Cook-Torrance BRDF + GGX VNDF lobe-MIS + fp32 progressive accumulation; RenderMode toggle — shipped v3.0.16

Phase D — RT reflections + atmospheric polish

EffortIssueSizeNotes
D.1 rt-reflections#149LGGX-VNDF reflections from the slim G-buffer + specular denoiser (hit-distance virtual reprojection) + pbr split-sum composite — shipped v3.0.17
D.2 volumetric-rt-shadows#150MPer-froxel RT shadow rays in the inject-scatter pass — point-light + arbitrary-occluder fog shadows; sun swaps CSM→RT ray; 1-spp softened by the temporal resolve (no denoiser); default-off toggle — shipped v3.0.18

Out of arc (deferred to follow-up series)

  • character-shading — skin (Jimenez15) + hair (Karis16 + Marschner03) + cloth (Estevez17). Triggered by adding a character to Bhaal Temple
  • gpu-driven — mesh shaders + meshlet baker + HiZ occlusion (Framework 5 alignment)
  • virtual-geometry — Nanite-class virtualized geometry; long-tail

Active series — material-system (Mode A, v3.1.0)

Extends the uber-shader and factors a shared evaluate-at-surface-point seam that both pbr.frag and the RT hit (geom_table.glsl) call, retiring the hand-duplicated BRDF. Node-graph deferred to a later authoring layer over the seam. Mode A — series-open MINOR bump to 3.1.0 (emissive-parity); intermediate efforts PATCH-bump, tag-only; milestone Release at series end.

Umbrella issue: #151 (sub-effort issues created on demand; commits use Part of #151).

EffortIssueSizeNotes
M.1 emissive-parity#152S–MEmissive factor + HDR strength; raster==RT emission; editor/import/preview + ShadeMode::Emission — shipped v3.1.0
M.2 material-eval-seamSpbr.frag migrated onto the existing common/brdf.glsl seam (rt-reflections built it); Pt* renamed; raster==RT BRDF parity structural — shipped v3.1.1
M.3 cutout-rtMPer-instance TLAS opaque flag + shared candidate-loop alpha-test (rayQuery-centric, not anyhit BLAS); cutout correct in RT shadows / GI / reflections / DI / fog; lone RT-pipeline sun-shadow pass rewritten to rayQuery-compute — shipped v3.1.2
M.4 transparency-tier#32M→LDedicated pass after the fog composite: PPLL OIT default + per-view sorted A/B, rayQuery sun shadow + fragment-depth fog, cull-masked RT visibility (shadow-ray-excluded, GI/reflection-visible; scope override: true OIT from the start, superseding "WBOIT only if layered") — shipped v3.1.3
M.5 rt-normal-maps#153SRT-hit normal-map TBN + occlusion parity (inverse-transpose normal matrix); applied in PT + rt-reflections, GI keeps its geometric secondary normal; folds the dead-index audit comment — shipped v3.1.4
M.6 restir-di-specular#154M→LReSTIR DI specular: combined diffuse+spec RIS target + demodulated F0-free shade output + dedicated 4th SVGF channel (surface-motion reproject) + pbr.frag F0-remod (not envBRDF — point-spec lobe applied at shade); research-backed (NRD/RTXDI/Bevy Solari) — shipped v3.1.5

The Slang IMaterial spike → GO (v3.2.0), opening the slang-material series below — the deferred node-graph authoring lands there.

gpu-particles (#57, L) — compute sim, showcase-sized (fire / ember / smoke / motes in god ray). Parallelizable — not a material-system dependency; drop into any renderer slot.

Active series — slang-material (Mode A, v3.2.0)

GLSL→Slang migration + a bounded IMaterial surface (link-time specialization) shared across raster / RT / path-trace, plus a node-graph authoring layer emitting into that surface — continues material-system's deferred node-graph. Mode A — series-open MINOR bump to 3.2.0 (the slang-spike gate); intermediate efforts PATCH-bump, tag-only; milestone Release at series end. Detailed design: local docs/development/epics/slang-material.md.

Umbrella issue: #157 (sub-effort issues on demand; commits use Part of #157).

PhaseEffortSizeNotes
0 ✅slang-spikeMPhase-0 gate GO — in-process compiler + rayQuery/BDA/bindless A/B + link-spec, all green — shipped v3.2.0
1 ✅slang-toolchainM.slang asset-pipeline dispatch (stage via reflection) + ShaderWatcher hot-reload + SlangParityGuard (deterministic SPIR-V NonUniform/caps gate; pixel A/B = diagnostic) — shipped v3.2.1
2 ✅slang-imaterialLbounded material.slang + two-tier generic eval shared by all 9 consumers; production seam deleted — shipped v3.2.2; hardening follow-ups (layout drift-guard + spike/geom_table retirement) shipped v3.2.3
3 ✅packed-texture routingLImport-side remap into the bounded channels: ORM aliasing, separate-map + spec-gloss derived-texture bake, DirectX-normal green-flip, TextureRole auto-detect + editor override; zero GPU-struct change, raster==RT by construction; shipped v3.2.4
4composable effect layerLLink-specialized stackable effects; the node editor emits into it (no longer blocks 5)
5 ✅node editor → bounded surfaceXLChannel-routing node graph → generated per-material Slang; raster per-material pipeline + RT variant-registry dispatch (raster==RT); done before Phase 4 — shipped v3.2.5

graph-param-buffer (v3.2.6) — constants→per-material-data + structure-hash-keyed variants (recompile-on-edit, the RT-recompile hitch, the 16-variant cap, and the per-edit pipeline leak all gone).

Materials arc (M1–M5)

Agreed forward order (2026-06-14) — dependency-clean, params as the keystone: a tunable in an effect unit (M3) or shading model (M4) must be a parameter, not a baked constant, or it reintroduces the anti-pattern graph-param-buffer just removed, so M2 precedes M3/M4. M1–M3 close slang-material (#157); M4–M5 open follow-on series. gpu-particles (#57) stays parallelizable throughout. One effort per conversation, plan-mode each.

Completion plan (2026-07-11): finish the whole remaining arc across conversations, in 3 shippable sub-arcs (Mode A, one branch + one MINOR each):

  • surface-detail (v3.11.0) ✅: parallax-occlusion first (adds the TangentViewDir fetch getter, foundation), then decals (UV-space; blood/grime, Bhaal-thematic).
  • shading-models (v3.12.0) ✅: clear-coat+anisotropy, then dielectric-transmission (RT refraction, closes the transparent-as-opaque gap), sheen-cloth, subsurface-skin; each extends MaterialInputs + brdf.slang, raster==RT.
  • material-authoring-redesign (v3.13.0) ✅: consolidation over the locked surface (not a rewrite) — terminology (dielectric F0 via IOR + specular, thickness split into glass + subsurfaceThickness, scatterRadiussubsurfaceRadius, glossary) → HDRP-style inspector IA (Base group, packed Mask Map, feature-gated lobes) → graph-declared (Blackboard) inputs (MaterialGraph::properties + PropertyRef, declared textures via gMatTexParams lifting the fixed-8-map ceiling, raster==RT==PT). Closes #177; virtual-texturing (v3.14.0) is next.
  • virtual-texturing (v3.14.0): separable XL capstone; may spin out as its own series.
  • hair deferred out of arc to the character vertical (asset-gated on hair geometry/cards).
  • Fable5 drives the BRDF/BTDF derivation + raster==RT seam on each shading-model effort; normal model for surface-detail plumbing (Fable5 verify pass on POM's raymarch).
ArcEffortSizeNotes
M1 authoringtransparent-graphMTransparent/OIT raster decode routed through the variant registry (raster==RT), same path the RT hit uses — shipped v3.2.7.
M1 authoringgraph-normal-previewS–MGraphed mi.normal honored raster==RT (tangent convention + ApplyTangentNormal) + lightweight graph-aware preview (Lambert-over-graph, self-contained UBO) — shipped v3.2.8.
M1 authoringnode-breadthM9 math nodes + UV source/pin + FBM Noise + WorldPos/ViewDir/Time/Fresnel context inputs + sandboxed custom-Slang node; VertexColor deferred (vertex-format effort), enum switch + UVTransform composable/deferred — shipped v3.7.3.
M1 authoringnode-uxSSearch / quick-add ✅ (v3.2.8); comments/frames + group-to-subgraph shelved (vendored GraphEditor widget lacks support — needs a widget effort); granular per-node undo deferred.
M2 params ⭐exposed-parametersMNamed/grouped params on value nodes (float/color/remap/tex-slot + bool hint), inspector-editable through the gMatParams data path; folds variant-cap parity + coalesced-reload hardening — shipped v3.7.3.
M2 params ⭐static-switchesMCompile-time StaticSwitch node (Off/On emission-time selection folded into the structure hash); named switches = Inspector checkboxes; enum/N-way deferred until a use case names its options — shipped v3.7.3.
M3 compositioneffect-layerLComposable layer substrate on the graph codegen (MaterialInputs bundle wire + MakeLayer/LayerBlend/Output.Surface); Triplanar + DetailNormal demonstrate it, raster==RT by construction. Shipped v3.7.3, closes #157.
M3 compositiontriplanar ✅ / detail-maps ✅ / parallax-occlusion ✅ / decalsM–LTriplanar + DetailNormal shipped with effect-layer (v3.7.3); POM + UV-space decals shipped on feat/surface-detail (#175: seam getters, height + decal slots, GraphParallax + GraphDecal raster==RT, layer-node decal); surface-detail sub-arc ready to close at v3.11.0.
M4 shadingclear-coat + anisotropyMLacquer / brushed metal; MaterialInputs 96->112 B + aniso GGX + coat lobe on a shared SurfaceBRDF, raster==RT (analytic + PathTrace reference); screen-space DI/reflections keep the primary surface isotropic (documented deviation). Shipped on feat/shading-models.
M4 shadingdielectric-transmissionLGlass: IOR + exact dielectric Fresnel + Walter GGX BTDF + Beer-Lambert, MaterialInputs/GPUMaterialData 112->128 B; PT refracts (4th glass lobe + medium tracking, RNG bit-identical at transmission 0), realtime raster gets screen-space refraction (per-view backdrop copy + roughness blur), transmission tint from attenuationColor not albedo (raster==RT); opacity was C++-side FORCE_OPAQUE (the gl_RayFlagsOpaqueEXT note was stale), colored shadows + refracted NEE deferred. Shipped on feat/shading-models.
M4 shadingsheen-clothMEstevez17: Charlie NDF + paper soft-V + analytic energy comp; MaterialInputs/GPUMaterialData 128->144 B, sheen lobe under the coat on the shared SurfaceBRDF, raster==RT (analytic + IBL env + PathTrace, no new PT RNG draw); graph pins + glTF import; DI/reflections keep the primary surface sheen-free (Option A). Shipped on feat/shading-models.
M4 shadingsubsurface-skinLJimenez15/Burley skin/wax/marble: a local diffusion-profile diffuse (Burley wrap + Frostbite thickness translucency) on the shared SurfaceBRDF, MaterialInputs/GPUMaterialData 144->160 B; PT random-walk oracle (Chiang albedo->sigma, collision walk reusing the glass medium), raster==RT, RNG-stream bit-identical at subsurfaceColor 0; subsurface + thickness texture maps + graph pins. Closes the shading-models sub-arc (v3.12.0). Shipped on feat/shading-models.
M4 shadinghairLMarschner03 / Karis16 — needs hair geometry/cards (= the deferred character-shading).
M5 texturingvirtual-texturing + sRGB-authoring revisitXLStreaming-scale texturing; revisit the v3.1.6 sRGB-authoring revert. Long-tail.

Gameplay enablement

PriEpicIssueTargetEffortDeps
4scripting (C# or Lua)NEWv3.2.0XLrt-renderer
5prefab-systemNEWv3.2.xMscripting

Scripting unblocks the PlayerControllerSystem stub deletion and is the prerequisite for most gameplay-side future ideas.

Animation maturity

PriEpicIssueTargetEffortDeps
6animation-controller-v2#94v3.2.0XLanimation-quick-pass

Polish (no fixed slot — opportunistic)

EpicIssueEffortNotes
procedural-skyNEWMIndependent; drop into any quiet renderer slot
jiggle-bones#61MBenefits from jolt-physics ✅ colliders
rg-aliasing (optional)NEWMDefer unless rt-renderer Phase A/D pressures transient VRAM

fxaa-taa (#72) — TAA absorbed into rt-renderer Phase A.5; FXAA dropped (TAA is strictly better given motion vectors land in Phase A.2).

Detailed design lives in docs/development/epics/<slug>.md (local, never committed) once an epic enters plan-mode. The arch docs (arch/) are the canonical reference for system invariants.


Versioning

Semantic Versioning (MAJOR.MINOR.PATCH):

  • MAJOR — fundamental architecture changes or engine rewrites
  • MINOR — each completed epic with user-visible changes
  • PATCH — bug fixes and polish between epics

Version is centralized in luth/source/luth/core/Version.h.


Future Ideas

Long-tail wishlist of unscheduled work lives in FUTURE.md. Categories: physics maturity, gameplay enablement, rendering (beyond planned-epic deps), animation maturity, audio, editor & tools, profiling / memory.