Add a systematic C++ wrapper layer for the WebGPU backend, mirroring the
vulkan backend's vkbuilder patterns so invalid API usage is rejected at
compile time / type level instead of via scattered runtime flags.
New layer (src/modules/graphics/webgpu/):
- wgpu_types.h: cross-platform webgpu_cpp.h include + sv() helpers
- Capabilities: read-only snapshot of adapter/device limits (GetLimits),
sample-count/format/bind-group predicates
- InitFlow: typed Instance->Adapter->Device state machine wrapping the async
request + ProcessEvents busy-wait (skipping a step is a compile error)
- SamplerBuilder/BindGroupLayoutBuilder/PipelineBuilder: build the C++ wrapper
descriptors directly (zero-init == WebGPU defaults), eliminating the
writeMask=0 / sampleMask=0 / alphaMode default-mask traps
Graphics.cpp migration:
- initHeadless/initWithWindow now drive InitFlow; drop the 4 std::atomic<bool>
flags, waitForAdapter/Device, createInstanceAndAdapter/requestDevice
- convert 8 bind-group-layout, 7 pipeline-layout, 3 2D-pipeline functions and
makeWgslModule to build C++ wrapper descriptors directly (reinterpret_cast
127 -> 114; remaining texture/buffer/renderpass conversions deferred)
Design spec: docs/dev/superpowers/specs/2026-08-26-webgpu-vkbuilder-style-cpp-layer.md
Verified: build/webgpu-native (native Dawn, MSVC Debug+Ninja) compiles and
links eve.exe (1633/1633). SSAO/decal stub-tightening (PR4) was dropped as
obsolete - dev already has real implementations.
AnimSkin can now write CPU-skinning results back to the render mesh so skeletal animation actually plays from scripts:
- capture bind normals at fromModel and skin them per pose (updateSkinnedNormals / getSkinnedNormals, cofactor inverse-transpose + normalize)
- packed getSkinnedPositions() array getter, matching AnimLattice's script pattern
- one-call applyToMesh(gfx, mesh, pose): skin positions+normals then in-place gpu.updateMeshVertices
- Renderable3D.getMesh() script binding so createRenderable meshes are reachable
- Mesh::gpuVertexCount so getVertexCount() is correct for non-morph meshes (both backends)
- tests: packed positions == skinPositionsTo, normals follow pose and stay unit length, applyToMesh render test with in-place updates
- docs: animation/graphics user guides + design spec
WebGPU NDC is Y-up while the engine feeds Vulkan-convention (Y-down) clip
matrices, so every 3D pass (mesh, clustered, gbuffer, shadow, voxel) rendered
vertically flipped - the floor appeared on top and lighting/AO landed in the
wrong places. Mirror clip Y in the vertex shaders.
The default env cubemap was pure white while RenderSystem3D passes
envIntensity=1.0 by default, washing the whole scene toward white; make the
default black so only an explicitly set envMap contributes IBL.
Also fix the half-res AO UV mapping (was overrunning into [0,2] and clamping
to the AO edge) and add the missing Vulkan setMesh3DSSAO override so the
native desktop build compiles.