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
Completes the PBR surface-parameter layer: mesh3d.frag now blends normal / roughness / metallic / emissive (in addition to albedo) from the DecalLayer before lighting; decals are drawn as grouped instanced draws (112B per-instance SSBO, alpha-over non-premultiplied layer, per-decal uvRect/atlas + channel strengths + blend mode API); DecalManager gains frustum + distance culling and setUvRect/setTextures/setBlend; new headless readback readDecalLayerToImageData validates the pass without a swapchain. Fixes two Vulkan validation violations found by the layer (missing vertex/index binds on instanced draws, blend attachmentCount).