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).
Phase 3 of the surface fluid simulation:
- Bingham yield stress in the CPU solver and integrate kernel: effective
viscosity grows as local shear rate drops, so mud piles up and freezes
instead of spreading like water.
- FluidSurfaceRenderer: screen-space fluid rendering pipeline (splat depth+
thickness with atomic min/add, bilateral depth smoothing, view-space normal
reconstruction from depth gradients, water/mud shading), mirrored 1:1 in
FluidSsfKernels.h and driven through the gpgpu compute queue with a CPU
fallback and PPM debug output.
- Script API: Fluids.newSurfaceRenderer + FluidSurface (render/setCamera/
setMode/writePpm).
- Design + usage spec under docs/dev/specs.
- Tests: yield-stress freeze, SSF single-particle depth/normal/thickness,
water-vs-mud shading, GPU SSF pipeline with PPM artifact (17 fluids cases).
Add §17b–§17d API detail tables to docs/dev/测试覆盖.md for map, rpg and
building (all rows now '已展开'), backed by a fresh audit of the existing
70+ map cases, 28 rpg cases and 15 building cases.
Fill the audit gaps with two compact accessor tests:
- map.path.accessorRoundTrips: diagonal, blockEmpty, cellCost getter,
unblockGid/clearBlockedGids, invalidateCache, setBlocked/isWalkable.
- map.fov.accessorRoundTrips: cornerPeek, eyeOffset, cliffBlock,
verticalRange, perceptionRadiusScale, revealerPosition3/perception,
revealer count and unknown-id behavior.
Local win32-debug: map/map_fov/map_path bundles pass.
The blocking-wake path was already covered by event.workerPushWakesWait;
add the queued-message fast path (wait returns immediately). Update
docs/dev/测试覆盖.md §9: wait marked 已覆盖.
Fill the last thread-module API gaps: blocking demand() and cross-thread
postMain() into the Event queue (same-thread, empty-name throw, worker
thread). 46/46 thread bundle cases pass locally on win32-debug. Update
docs/dev/测试覆盖.md: thread row to '已展开' with a full API detail table.
Add a dedicated Linux step that runs the six headless GPU bundles
(graphics_font, TextureSampler, gpgpu, tensor, virtualgeometry_gpu,
graphics_headless) with no display server — proving the headless-vs-window
policy in docs/dev/测试覆盖.md §0. The regular xvfb run keeps covering the
full suite including windowed demo tests.
Introduce the headless-vs-window test policy (docs/dev/测试覆盖.md §0):
functional GPU tests run via Graphics::initHeadless + offscreen canvas
readback; visual/demo tests (ClassicScenes, RenderImageAudit, animation,
voxel, particles, UI, swapchain-readback cases) keep real windows.
Mechanics:
- Graphics::initHeadless is now re-entrant: the process-wide singleton
updates the logical viewport on repeat calls instead of throwing, so
multiple cases in a bundle can share one headless device.
- test/Fixtures.h adds openHeadlessGfx() and a useHeadless mode for
GfxFixture; EVENGINE_TEST_VIEW_W/H overrides apply to both paths.
Migrated (window-free, no behavior change to assertions):
- test/graphics_font.cpp: atlas + print-to-canvas pixel readback; the
interactive debug-preview loop is guarded by isHeadless().
- test/TextureSampler.cpp: sampler parameter round-trips.
graphics_headless.cpp now asserts the re-entrant resize semantics.
- Add RGBA-float drawSolidRect/drawTexturedRect overloads so the C++ names
match the script-facing API (script already bound those names).
- Add string-based setTextureSampler(Texture*, filter, mipmap, aniso, lodBias)
overload with strict magic-string validation (unknown filter/mipmap now
throws instead of silently falling back to linear); keep
setTextureSamplerParams as a deprecated alias. Script binding now targets
the overload with an explicit cast.
- Document error contracts: DataModule encode/decode (new[] ownership +
throws), Filesystem setSource/mount/unmount (false on recoverable failure,
no throw), ModelData getMesh vs numeric getters (nullptr vs throw), plus a
two-state error-semantics section in API-CONVENTIONS.md.
- Tests: TextureSampler string validation (valid + deprecated alias + typo
throws), headless test now exercises the RGBA-float overloads.
Runtime::initialize() now adds an eve.reflect sub-table binding the reflection layer (classes/classInfo/scan/scripts, createInstance, inspect/read/write/readObject, array*/table* editing) so Squirrel tools and editors can introspect and edit script classes and instances with the same semantics as the C++ Runtime API.
Scalar reads/writes keep the slot's own type; container/instance slots are read as live script objects; mutators return false instead of throwing on missing or method members. Adds test/ReflectScript.cpp covering the full surface.