- EVENGINE_ENABLE_STRICT_WARNINGS option: MSVC /W4 /WX, GCC/Clang
-Wall -Wextra -Werror, with documented noise suppressions and
-isystem/-external:I for third-party headers; enabled on the
Windows/Linux debug CI jobs.
- Fix warnings across engine, modules and tests exposed by the gate,
including real bugs (incomplete-type deletes, Timespan microsecond
overflow, overload hiding, enum switch gaps, dead code).
- Add Linux ASan+UBSan job, example smoke step, python-tools test
job, ruff config (scripts+tools), weekly Linux coverage job and a
CodeQL workflow.
* 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)
Re-add test/hair.cpp and test/runtime.cpp (fixing the missing zeroerr include and enum comparisons), add graphics.resourceLifetime tests for the new release APIs, a test-source manifest check, module declared-layer check with unit tests, and a raw-string-literal regression test.
- graphics.water.render.planar (env-gated): render the scene from a mirror
camera into an offscreen canvas via begin3DFrameToCanvas, then have the
water plane sample it at screen UV. Confirms the mirror-camera capture
(red box in the reflection canvas) and a coherent final frame (sky above,
sky-reflecting water below).
- graphics.render3d.toCanvas: read the canvas once via newImageData instead of
per-pixel getPixel (each getPixel did a waitIdle + full image copy + staging
alloc, making the loop O(N) readbacks and looking like a hang).
The render-to-canvas path (already committed) is what makes this possible; the
offscreen render + subsequent present works once the readback is done once.
Add begin3DFrameToCanvas / end3DFrameToCanvas on Graphics: open a 3D render
pass targeting an offscreen Canvas (color + D32 depth) at the canvas size,
using setMesh3DViewProj/View/CameraPos/Env as the camera. Rendered on a
dedicated command buffer and submitted directly to the graphics queue, so it
does not touch the swapchain / present flow. Enables render targets such as a
mirror-camera planar reflection (reflection captured, though compositing it
into a live scene is a follow-up).
Vulkan: offscreen3D render pass + mesh3d pipeline + pool/cb/fence, canvas
gets a depth attachment + color+depth framebuffer; currentPresentCb/frameToken
route to the offscreen cb during the pass. WebGPU: unsupported (throws).
Test: graphics.render3d.toCanvas renders a red box into a canvas and reads it
back to confirm the scene lands in the offscreen target.
The engine's 3D path cannot render into a user Canvas (begin3DFrame refuses
while a Canvas is active), and 3D always targets the fixed-size scene-color
target, so a mirror-camera planar reflection into an offscreen texture is not
achievable without a renderer-level render-to-texture-with-custom-camera
feature. Remove the fragile demo that relied on double-rendering to screen +
scene-color capture (final present came out black). SSR (committed) and env
cubemap reflection remain the water reflection options.
Add a screen-space reflection (SSR) fullscreen pass and hook it into the Water
shader as an optional, off-by-default reflection overlay.
- ScreenSpaceReflection: ray-marches the reflected ray over the lit scene color
+ hardware depth, reconstructing the world normal from depth (mirrors the
SSGI pass). Outputs reflected color with A = hit validity into a canvas.
Graphics::newScreenSpaceReflection() + script binding; quality knobs
(strength/steps/stepLen/thickness/distance) and enabled toggle.
- Water::setScreenSpaceReflection(enabled, strength) + setViewport: the water
shader samples the reflection texture (bound via the height slot, binding 6)
at screen UV and blends it over the env-cubemap reflection. Off by default.
- New ssr.frag + compile script; vulkan SPIR-V regenerated.
Note: SSR reflects only geometry that is on-screen. For a horizontal water plane
viewed from above the reflected geometry sits in the void below the plane (no
depth there), so SSR finds no hits there; it works for on-screen-reflective
surfaces (glossy floors/walls, grazing water). Env-cubemap reflection remains
the default; SSR is additive when enabled.
The middle-drop ripples were a hard-edged expanding ring (band ~0.055, radius
growing to cover half the plane), which read as an ugly big circle and did not
look like water. Replace with an expanding damped wavelet: a smooth gaussian
envelope times a crest+trough cosine, fading as it grows. Tune scene amplitudes
lower (wave 0.30, ripple 0.55) for calmer, more natural water.
Render a proper skybox: a huge sphere centered on the camera (raised far
plane so it isn't clipped) shaded purely by the env cubemap, so the sky is
clearly visible behind the water plane. Use a vertical-gradient cubemap
(pale horizon -> deep blue zenith) instead of a flat blue. The frame now
reads as bright sky (top) over a sky-reflecting water plane (bottom), with
no dark background.
Add a Waterfall helper (graphics/Waterfall.{h,cpp}) built on the custom
Mesh3D shader path that renders a vertical sheet as falling water:
- Downward flow: scrolling fractal noise + elongated vertical streaks
(streakScale) to convey falling velocity.
- White-water foam: turbulent crests in the body plus foam bands at the
top lip (topFoam) and the bottom splash pool (bottomFoam).
- Sky reflection: Fresnel-weighted reflection of the environment cubemap
plus a specular glint from the primary directional light.
- Bottom transparency fade so the sheet melts into the pool.
Push-constant params drive time, flow speed, turbulence, streak
count/scale, top/bottom foam, foam amount, water color, reflection
intensity and sun intensity. Exposed via Graphics::newWaterfall() and
the Squirrel binding (Waterfall class). Also adds Graphics::saveFramePng
to dump the presented frame to a PNG.
Includes a runnable demo (examples/waterfall-demo) and tests: params
round-trip plus a GPU render test proving the waterfall renders, changes
over time (flowing), and differs between calm and foamy configurations.
Replace the tiled-cube grid with one flat water plane carrying the water
shader (createPlane). A water surface is a plane; the cube tiling was
unnecessary. Renders brighter and cleaner (mean luma ~90, still strong
ripple/Fresnel variation) and fills the frame without block-gap gaps.
- Boost the water shader's sky-reflection blend baseline so the surface
clearly reflects the sky even at non-grazing angles (better first impression).
- Add an env-gated render test (EVENGINE_WATER_RENDER_PNG) that tiles a grid of
unit-cube meshes, each carrying the water shader, and saves a PNG so the water
scene can be previewed. The rendered frame reads as blue sky-reflected water
with visible ripple/block variation (grid std ~53, mean luma ~73).
Add a Water helper (graphics/Water.{h,cpp}) built on the custom Mesh3D
shader path that renders a plane as dynamic water:
- Sky reflection: Fresnel-weighted reflection of the environment cubemap,
blurred more at grazing angles; LOD clamped to available mip levels so a
non-mipmapped env cubemap stays safe.
- Edge ripples: animated shore waves strongest near the water boundary and
fading inward.
- Middle ripples: occasional expanding ring drops near the centre, staggered
by an interval, fading over time.
- Sun glint from the primary directional light + soft foam at the edge.
Push-constant params drive time, wave speed/amplitude/scale, ripple
amplitude/count/interval, edge falloff, water color, reflection tint/intensity
and sun intensity. Exposed via Graphics::newWater() and the Squirrel binding
(Water class). Water::draw() plus mesh+shader accessors let it be attached to a
Renderable3D for the scene graph.
Tests: params round-trip and a GPU render test proving the water renders,
changes over time (dynamic ripples), and differs between flat and rippled
configurations. Verified no regression on procgen + graphics render tests.
Add procedural cloud generation and dynamic cloud shadows that darken the
ground as clouds drift overhead.
Procgen (CPU, fully testable):
- CloudField: deterministic, seamlessly-tiling, time-animated 2D cloud
coverage over the XZ plane (domain-warped value-noise fBm, wind drift,
wisp detail). coverageAt(x,z,t), sample(...).
- CloudShadow: projects clouds along the sun onto the ground plane
(coverageAt / shadowFactorAt / bake), accounting for sun direction and
cloud altitude.
- New recipes: tex.cloud (cloud cover), tex.cloud_shadow (projected
ground shadow coverage).
- Procgen module + script API: newCloudField/newCloudShadow,
cloudCoverageAt/cloudShadowFactor, sampleCloud/sampleCloudShadow.
Graphics (real-time):
- setCloudShadows() packs {strength, worldCell, time, wind, coverage,
detail} into the Mesh3D UBO (default strength 0 = no change).
- mesh3d PBR shaders (Vulkan GLSL + regenerated SPIR-V, and WebGPU WGSL)
sample an inline seamless cloud field in world XZ and modulate the
primary directional light.
Tests: CloudField reproducibility/animation/seamless tiling, wind drift,
shadow projection & factor bounds, recipe generation, module API, and a GPU
render test proving clouds darken the ground and the effect resets when
disabled. Verified no regression on the default PBR path (ClassicScenes
pbrChart + hexplanet render tests).
Add 10 new albedo texture recipes (rock, wood, ripple, cloth, ornament,
spot, zebra, wall, cement, mud) alongside the existing soil/stone/marble/
water/sky_cloud, and introduce full metallic-roughness PBR generation.
A texture is now described by a single TextureRecipeDef (albedo ramp +
displacement field + PBR knobs), so both an albedo ImageData recipe
(tex.*) and a complete PBR set (pbr.*: albedo/normal/roughness/metallic/
height/ao) are derived from the same pixel-aligned height field, with
seamless tiling support.
Expose generatePbrMaterial + pbr recipe listing on the Procgen module and
script bindings. Add tests for expanded recipes, PBR reproducibility,
map ranges, and module/error paths.
Assert registration, per-kind validity (indices/normals/finite positions),
reproducibility, uniform scaling, linear vertex growth with segments, and
seam alignment (unit spans exactly [0, segLength]) for all six recipes.
Add mesh.skyscraper recipe producing a deterministic setback-tiered tower
with a window grid and optional spire. Window quads are raised a thin lip
above each facade so openings read under directional lighting without
coplanar z-fighting.
- Register recipe in MeshRecipeRegistry
- Add topology/height/reproducibility unit tests
- Add render smoke test outputting out/skyscraper.png
- Add interactive example examples/skyscraper-generator/
- Update procgen and module design docs