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.
avatar/database/dialogue/sceneloader/stylize/virtualgeometry now list
getName(); camera spells getTargetX/Y/Z out in full. These chapters are
gated by scripts/check_bindings.py --strict in CI.
- New user docs: avatar, buildingfx, camera, database, dialogue, grid,
housegen, sceneloader, stylize, virtualgeometry (all written against the
actual addFunc surfaces; API quick-refs included). Wire them into MODULES.md.
- examples/voxel: streaming terrain + place/break demo (procedural atlas,
setTerrain + streamAround, DDA raycast picking, orbit camera). Script logic
verified locally (no script errors; 16 chunks / 3117 rects selected).
- README gallery links in both Readme.md and Readme.en.md.
- Script binding fixes discovered while writing the docs/examples:
* Graphics.setCanvas accepts null (and Canvas instances) so canvas sessions
can be ended from scripts; begin3DFrame/setMesh3DViewProj/setMesh3DView/
setMesh3DCameraPos now exposed for script-driven 3D (voxel etc.).
* SceneLoader.load/host exposed (previously only reload/status were bound).
- 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.
Render 14 examples with the prebuilt eve host and capture the actual
swapchain frame via gfx.saveFramePng (capture_root.nut wrapper), then
publish the PNGs under docs/images/examples with a gallery README and a
link from the user guide.
Adds scripts/capture_example.ps1 + scripts/capture_root.nut for
regenerating the captures on any example.
- devlab: flagship --debug example (console/snapshot/AI panel/pause/hot
reload/error slice), make devlab target with RUN_ARGS support
- ai-game: agent-drivable live game + agent_demo.py (pause/read/mutate/
verify/snapshot/restore/screenshot loop)
- ai-editor: headless eve mcp editor host demo (JSON View + ViewModel MVVM)
- examples: fill all missing READMEs + config.nut, add examples/ index
- load.nut: add key_just_pressed/clampf/persist helpers; dedupe 19 examples
- rpg/cardgame: move JSON definitions to data files
- fix eve mcp crash: ModuleManager::requireAll() instantiates modules in
the headless host (IEditorHost etc. were null)
- graphics: actionable screenshot error when readback was just enabled
- CI: smoke all examples (devlab with --debug) + DevLab hot-reload test
Fixes surfaced by the new CI smoke (examples had never been smoke-tested):
- scene_director kit: camera slot doubled as function + instance and was
nulled by reset(); move the live Camera3D into cameraObj
- readTextFile used non-existent readn()/len(); use file.read()
(rpg/cardgame/hex-levels)
- daynight used non-existent keyboard.wasPressed() -> key_just_pressed
- outline passed ints to float-only drawSolidRect; linear-structures
slider passed an int value
- scene-ecs beginNode() requires (id, name); ai-stage guarded compilestring
- smoke_examples.sh: timeout -k 3 so SIGTERM-ignoring hosts cannot hang CI
- Harden the doxygen-awesome dark-mode toggle against blocked localStorage
(sandboxed webviews / file://): storage failures no longer break theme
initialization or the toggle click.
- Add a CSS-only prefers-color-scheme fallback so dark mode still follows the
OS/app scheme when scripting is unavailable.
- Readme (zh/en): make "download prebuilt SDK, no build" the primary path for
game developers; move build-from-source content under an engine-developers
section.
- Rewrite docs/usr/README.md: download/run, capabilities, debugging,
packaging, and a "things to keep in mind" section.
- Unify wording in MODULES/TRIMMING/dev docs and the SDK README.
- Replace the JS treeview with a plain-HTML static sidebar (home, user guide,
module handbook grouped into 5 categories, API indexes) so docs navigate
without JavaScript.
- Add a Doxygen landing page and page-hierarchy wrappers generated by
docs/CMakeLists.txt; tune Doxyfile for the static layout.
dev is the repo's feature branch (main is release-gated to promote/docs PRs).
Resolved JobSystemThreadPool.cpp to our completeJob ordering (decrement
outstanding counters after fireCompletion/releaseDependents) — dev's merged
PR #134 version kept the pre-fix order that can reset the frame arena under a
still-running worker.
Port the deferred FrameGraph integration onto main's split Vulkan backend:
- Graphics3D.cpp owns buildDeferredFrameGraphs / recordDeferredFrameGraph /
recordShadowCascadePass / recordGBufferPassDraws (parallel JobSystem
recording, one command pool per frame-slot per pass).
- begin3DFrame / beginSwapchainRenderPass / flushToSwapchain 2D-only path
call recordDeferredFrameGraph() instead of the serial pending passes.
- Drop the superseded serial recordPendingShadowPasses/GBufferPass and the
duplicate culling implementation (main's RenderSystem3D already carries
the per-camera / per-cascade frustum culling).
- VKBuilder submodule -> 51514d9 (master; our spec-safe pool/lifecycle PR
was merged upstream as sunxfancy/VKBuilder#2).
CPU trilinear lattice deformation for procedural 3D scale/squash-stretch
animation: per-control-point scale + offset, model/array binding, deformed
position/normal cache, script API (anim.newLattice + AnimLattice class) and
gfx.newMeshFromArrays / gfx.updateMeshVertices script wrappers so scripts can
upload deformed meshes per frame. Includes unit tests and the
examples/lattice-deform demo.
TSan (WSL) proves the JobSystem + executor + framegraph planning are
data-race-free; on Windows the parallel record path AVs inside
amdvlk64.dll from vkb::FrameGraph::record's recordOne on two workers, and
still crashes after device->waitIdle() — an AMDVLK concurrent-recording
defect, not a pending-CB reuse issue. Engine keeps serial recording; the
executor stays available for drivers/CI builds that support it.
Stage D (shadow part): each in-flight slot's FrameGraph now owns both the
shadow and G-buffer passes as declarative, dependency-free passes in one
layer. The CSM array is imported per cascade via its layer view with
arrayLayers=3 so barriers cover the whole array; every cascade ends in
DepthStencilReadOnlyOptimal for the engine-side forward pass to sample.
- recordPendingShadowPasses / recordPendingGBufferPass are gone, merged
into recordDeferredFrameGraph(): graph record + submit before the
swapchain pass, with a once-per-slot-per-frame guard so repeated
render3D() calls in one script frame never re-record the same command
buffers (resetting in-flight CBs hung the driver).
- buildDeferredFrameGraphs() rebuilds one graph per slot from the current
shadow array + G-buffer targets; shadows can be enabled without a
G-buffer pass and vice versa (graphs are built on demand).
- JobSystem fix (PR #134 scheduler bug): completeJob now decrements
outstanding/outstandingFrame only after fireCompletion +
releaseDependents have finished using the job. Previously a waiter
(beginFrame/endFrame) could observe outstandingFrame == 0 and reset the
per-frame arena while a worker still touched the job (use-after-free:
lost forked job -> hang, or crash). Two stress tests
(render_graph.jobSystemGroupStress, render_graph.jobSystemEngineFramePattern)
cover the engine's exact beginFrame/parallel_for/task-group frame
pattern.
- Recording stays serial (graph->record()) for now: the JobSystem
help-execution path still has a residual timing-sensitive race that can
leave a worker stuck in a pass callback. The parallel executor
(jobSystemPassExecutor) is ready and CPU-tested; enable it once that
race is root-caused upstream (documented in FrameGraphJobs.h).
Verified locally (win32-debug, full clean rebuild): render_graph.*,
thread.*, Shadow3D.*, RenderSystem3D.*, Lighting3D.*, Mesh.* and
AO/AA/Outline/GI — 77 cases pass.
- GpuDrivenCaps: probe + enable Vulkan 1.2 core features (drawIndirectCount,
descriptor indexing, non-uniform indexing, update-after-bind) at device
creation; the toolchain's Vulkan headers omit the core drawIndirect bit so
it is assumed supported and left untouched.
- Bindless Set1: sampler2D array (4096) + samplerCube array (64) + SSBO table
array (64) with PARTIALLY_BOUND | UPDATE_AFTER_BIND; textures register on
creation, placeholder-filled with the 1x1 white texture/cubemap.
- GPU tables: GpuMeshRecord (bounds + index/vertex ranges) filled at mesh
upload and GpuMaterialRecord built from Material params, uploaded to
host-visible SSBOs with fixed capacity.
- FrameArena: per-frame-slot host-visible ring allocator, reset each
begin3DFrame; unused until phase 1 (indirect draw).
- Tests: GpuDriven.capsAvailable / bindlessTextureRegistration /
meshTableRegistration (GPU runtime verification pending driver recovery).
Rendering is unchanged: nothing consumes the new infrastructure yet.
Stage B of the FrameGraph migration: the G-buffer fill is now a
declarative FrameGraph pass recorded with the JobSystem executor and
submitted before the swapchain pass, replacing the hand-rolled
render-pass + manual begin/end layout tracking in recordPendingGBufferPass.
Design:
- One vkb::FrameGraph per in-flight present slot (framesInFlight=1),
importing the engine-owned ColorTarget images via importTexture. The
engine keeps image ownership, so renderEntityIdMask /
readGBufferToImageData and the AO/outline/AA descriptor wrappers work
unchanged.
- Each graph declares addPass("gbuffer") with 3 color attachments + 1
depth attachment (clear ops matching the old path). compilePlan derives
the dependency/barrier/layout plan; attachment->sampled transitions
fold into render-pass initial/final layouts (ShaderReadOnly /
DepthStencilReadOnly after the pass).
- recordPendingGBufferPass now runs recordFrameGraphWithJobSystem() +
graph->submit() inside a JobSystem frame bracket; the graph's command
buffer is recorded (serial for one pass today; parallel once shadow
cascades join in Stage D) and submitted on the graphics queue before
the swapchain pass. The graph's RP is render-pass compatible with the
engine's gbuffer pipelines (same formats/samples/subpass layouts).
- Command-buffer safety without a swapchain-driven FrameGraph: each
graph's CB is only reused two frames later, by which time the present
slot fence (waited in Present::begin) guarantees the previous graph
submit completed (same queue, submitted before the present CB).
An upstream VKBuilder patch (swapchain-less frame advancement + fences)
would let this collapse back to one graph.
Verified locally (win32-debug, full clean rebuild): render_graph.*,
thread.*, Shadow3D.*, RenderSystem3D.*, Lighting3D.*, Mesh.* and
AO/AA/Outline/GI (75 cases) all pass. Also fixed the test build's
stale-obj layout mismatch that made the new member corrupt ownedTextures
until the graphics module was rebuilt from scratch.
PR #134's secondary-command-buffer recording violated the Vulkan state
machine (secondaries began their own render pass instances without
pInheritanceInfo; the spec-compliant fix 1291df8 was reverted). Replace
that path with the groundwork for the vkb::FrameGraph migration:
- Drop the illegal secondary-CB deferred recording and restore the
branch base's legal inline recording for shadow/GBuffer passes.
- Fix frame-prep culling regressions from PR #134:
* per-item camera frustum culling (item.culled) instead of always
testing against the main camera;
* default-camera culling for the G-buffer pass (item.culledMain);
* restore per-cascade CSM caster masks (item.cascadeMask).
Camera view-proj matrices are snapshotted on the main thread before
the parallel job forks; workers only read immutable data. The light
job's CSM output is joined before the entity job forks so cascade
culling never races with the shadow-upload write.
- Add FrameGraphJobs.h: vkb::PassRecordExecutor backed by the JobSystem
(layer-internal fork/join via frame task groups, layers sequential),
the exact executor the render backend will hand to FrameGraph::record().
- Add device-free tests (test/render_graph.cpp): the engine's target
pass topology (3 CSM cascades + GBuffer in one parallel layer ->
forward -> present) plans with attachment->sampled barrier folding
(zero explicit barriers on forward/present), and the JobSystem
executor records every pass exactly once, in parallel per layer.
Verified locally (win32-debug): render_graph.*, thread.*, Shadow3D.*,
RenderSystem3D.*, Lighting3D.* and Mesh.* all pass (58/58). Bundle-mode
SEGFAULTs are the known pre-existing zeroerr single-process issue, not
this change. See docs/dev/superpowers/specs/2026-08-20-framegraph-mt-render.md
for the staged migration plan (GBuffer into the graph next, then the
dedicated render thread).
- Image: expose newImageData(newImageDataFromFile)/newEmptyImageData/isCompressed and a
full ImageData script class (getWidth/Height/Format/Size, pixel get/set, paste, clone,
rotate). ImageData is now registered once by the Image module; drop the duplicate
minimal registrations in Font and Model3D that could overwrite each other.
- Joystick: expose getJoystick/getJoystickFromID/getIndex/addJoystick/removeJoystick and a
Pad class with raw axes/buttons/hats, GameController axis/button names, device info,
and vibration. Pads stay owned by the module (non-owning script handles).
- Mouse: expose setRelativeMode/getRelativeMode plus setVisible/setPosition/setX/setY/
setGrabbed/isGrabbed (SDL backend already implemented them).
- Tests: add image.nut/image_script.cpp and joystick.nut/joystick_script.cpp script tests,
extend mouse.nut with relative-mode/visibility checks, wire into test CMake and the nut
embed template.
- Docs: update image/joystick/mouse user chapters, MODULES.md and the REVIEW log.
* chore(ci): add clang-format gate, CODEOWNERS and agent collaboration rules
* refactor(modules): clear remaining back-edges and split the largest TUs
Three known dependency back-edges are gone (module_depgraph.py KNOWN_BACK_EDGES
is now empty; --check passes with 0 violations, 0 cycles):
- window -> graphics: window queries the new IWindowSurfaceHost capability
(common/WindowSurfaceHost.h) instead of holding a Graphics pointer;
Window::setGraphics was removed and all call sites updated.
- window -> image: Window::setIcon was replaced by setIconRGBA(const uint8_t*,
w, h); the window no longer decodes ImageData.
- scene -> graphics: pickScreenAt / collectFrustumIdsAt implementations moved
to graphics/ScenePicking.cpp (graphics -> scene is a legal downward edge);
scene now depends only on spatial.
TU splits (pure moves, verified identical method sets and brace balance):
- vulkan/Graphics.cpp (5118 lines) -> Graphics.cpp (lifecycle), GraphicsPipeline,
Graphics2D, Graphics3D, GraphicsMesh + shared GraphicsInternal.h.
- scene/Scene.cpp -> Scene.cpp (script layer / host lifecycle / serialization)
+ SceneNodes.cpp + shared SceneInternal.h.
Verified with a real build (MSVC 19.51, prebuilt third-party): full build green;
window.* and Scene.* suites pass, including scene picking/culling and GPU cases.
* refactor(graphics): narrow Graphics interfaces and clear public-header leaks
Step 4 — interface segregation:
- New lean interfaces: IGraphics2D, IGraphics3D, IResourceFactory, IPostFX
(pure virtuals, forward-declared types only; graphics/Color.h extracted from
Canvas.h so modules that only need the color type stop pulling Canvas.h).
- Graphics now implements all four facets; consumers can depend on the narrow
interface they use instead of the 129-method god class.
- Migrated voxel (VoxelWorld::drawVisible -> IGraphics3D*) and model3d
(ModelRenderer::buildRenderable(s) -> IResourceFactory&).
- Graphics.h slims from 24 sub-header includes to a handful of small type
headers + forward declarations; the destructor moved out-of-line so
unique_ptr effect members are destroyed where types are complete.
Step 5 — header leaks (module_depgraph.py '*' marks: 14 -> 7):
- map/TileLayer.h, particles/ParticleEmitter.h, avatar/AvatarInstance.h,
animation/SpineAnim.h, map/TileSystem.h, model3d/ModelData.h,
model3d/ModelRenderer.h, audio/Audio.h + Source.h, image/ImageData.h +
Image.h, dialogue/Dialogue.h, procgen Palette/OutputSpec/TextureRecipe/
PbrMaterial -> forward declarations, includes moved to .cpp.
- voxel/VoxelWorld.h: terrain sampler Pimpl'd (unique_ptr + out-of-line
destructor), removing the procgen header dependency.
- gpgpu/vulkan backend headers stop pulling graphics/vulkan/Graphics.h.
- Remaining '*' marks are deliberate: tiny POD/enum headers (GridConfig,
MapObject, BlendMode, Color), an internal backend header
(GraphicsInternal.h -> filesystem), and lean interface references.
Verified with a full clean build (MSVC) and tests: window/Scene/voxel/
RenderSystem/Quad/shader/TextureSampler/resourceLifetime pass; broad suite
only shows the pre-existing PHYSFS-init filesystem-environment failures.
* feat(resources): revive ResourceManager as unified IAssetReloader-backed cache
* fix(resources): leak ResourceManager singleton to avoid exit-time teardown crashes
Cached CPU resources may own third-party handles (FreeType faces, Assimp
scenes, image decode handlers) whose libraries are torn down at process
exit in an unspecified TU order; destroying cached entries from the
singleton destructor can segfault at exit (seen on CI Windows in
model3d.materialApi.objMtl). Keep the singleton alive until OS reclaim;
explicit unload()/clear() still release entries during the run.
* refactor(devtools): consume business modules through capability interfaces
DevTools / cmdline no longer include business-module headers, so trimming
scene / physics / procgen / particles / audio / ui / graphics no longer
requires disabling devtools, and devtools can be switched off with
-DEVENGINE_MODULE_DEVTOOLS=OFF.
New capability interfaces in common/:
- ISceneQuery (scene) - host/node inspection + mutation + transform sync
- IRenderCapture (graphics) - render status, PNG capture, camera control,
visible-entity snapshot, entity-ID mask, G-buffer PNGs
- IPhysicsQuery (physics) - 2D world lifecycle + ray casts
- IProcgenQuery (procgen) - recipe listing, map/mesh generation
- IParticlesQuery (particles) - emitter status + one-shot emit
- IAudioQuery (audio) - volume / stop-all
- IEditorHost (ui) - declarative editor host (used by cmdline too)
Providers register in their module constructors. DevTools rewires McpServer
(render/scene/procgen/physics/particles/audio/host tools), SceneInspect
(now delegates to ISceneQuery + IRenderCapture) and RenderVision (uses
IRenderCapture::capturePngDataUrl); cmdline/Mcp.cpp uses IEditorHost.
manifest: devtools DEPS graphics scene physics procgen particles audio ui
image event filesystem -> event; cmdline DEPS filesystem ui -> filesystem.
Verified: full build green; devtools.mcp.* tests pass; broad suite shows
only the pre-existing PHYSFS-init filesystem-environment failures.
* style: apply clang-format to changed lines after dev merge
* fix(webgpu): include RenderControl/TextureSampler explicitly after Graphics.h slimming
* fix(webgpu): provide newFont/print stubs to satisfy IGraphics2D/IResourceFactory vtables
* fix(common): make ProcgenQuery.h self-sufficient (cstdint/utility for GCC)
- compute main's changes since the merge-base with dev instead of per-commit
cherry-picks, so merge/squash/rebase doc merges all work and dev-only work
is never touched;
- allow release version touchpoints (RELEASE_PATHS) in the non-doc gate, fixing
the spurious failure every sync-docs run after a non-ff release merge;
- sync by copying doc paths from main onto sync-docs/from-main and opening a
PR to dev (no direct push), matching the PR-only branch model;
- docs updated in 发布流程.md and the release-branch-model spec.
- install_sdk.cmake: export headers for exactly the modules the build enabled
(EVE_ENABLED_MODULES from cmake/module_manifest.cmake) instead of a stale
whitelist; package engine + vendored third-party licenses under
share/eve/licenses/.
- test-sdk.sh: hard-gate native C++ plugin compile (find_package(EVEngine) +
add_eve_plugin) on win32/linux/macosx; android now assembles an APK from the
SDK template (gradlew assembleDebug) instead of skipping.
- sdk-release.yml: run the android APK smoke; publish generates SHA256SUMS,
verifies every zip's share/eve/VERSION against the tag, uploads SHA256SUMS
with the SDKs, and (best-effort) attests build provenance.
- release.py: new cleanup-branches subcommand deletes merged vX.Y.Z /
promote/* / rebase/* branches (tags and main/dev are never touched);
new cleanup-branches.yml runs it weekly + on demand.
- docs: SDK packaging and branch cleanup sections in 发布流程.md and the
release-branch-model spec.
- release.py: use explicit refs/heads + refs/tags refspecs everywhere (short
refspecs are ambiguous when the release branch and tag share a name, and git
refuses to push them); rebase dev onto the branch's -dev write-back instead
of the official tag; add a real-git integration test suite.
- version single-sourcing: fix config.h.in PATCH macro bug and include -dev;
derive Android versionCode; sync required version touchpoints in start and
enforce them via check-versions in CI.
- SDK: exact version assertions in test-sdk.sh, publish-time VERSION cross-check.
- workflows: converge toolchain versions to repo vars, SHA-pin actions, add
Dependabot config.
finish no longer updates main or force-pushes dest. It opens promote/vX.X.X to main and rebase/vX.X.X to dest for a human merge, and posts the main-gate check so GITHUB_TOKEN PRs stay mergeable.