67 Commits
Author SHA1 Message Date
西风逍遥游 db91852f75 Merge remote-tracking branch 'origin/dev' into codex/test-suite-representative-tests 2026-09-11 22:52:04 +08:00
西风逍遥游 016610c300 Merge branch 'dev' of github.com:EVEngine/EVEngine into dev 2026-09-11 22:50:28 +08:00
西风逍遥游 58e01f6d53 chore: update .gitignore, enhance imgui.ini layout, and refine castle and character motion examples 2026-09-11 22:50:22 +08:00
西风逍遥游 ef22dc9cf8 Merge remote-tracking branch 'origin/dev' into codex/test-suite-representative-tests
# Conflicts:
#	.github/workflows/ci.yml
2026-09-11 20:39:21 +08:00
西风逍遥游 07d9856556 Merge pull request #374 from EVEngine/codex/stylized-tree-foliage-clusters
feat(procgen): add blue-noise leaf clusters to mesh.tree
2026-09-11 17:36:29 +08:00
西风逍遥游 2628790bdb feat(procgen): add blue-noise leaf clusters to mesh.tree
The stylized tree's foliage was a stack of ellipsoid lobes (canopy) or loose
per-tip cards (cards): the first reads as a bunch of grapes, the second clumps
and leaves holes because every card is drawn independently.

Add a cluster foliage mode built the way stylized foliage is normally authored:

  * leaf centres on a plane are blue noise (Poisson disk), so the distribution
    has no clumps and no bare patches;
  * only the leaves emit geometry, which is how a CPU mesh expresses
    'leaves opaque, everything else transparent' without alpha sorting or
    transparent overdraw;
  * 8-10 such planes are placed around the cluster's Y axis with a small random
    tilt and offset, plus a few near-horizontal planes that close the poles;
  * vertex normals are transferred from the cluster sphere instead of the leaf's
    own plane, so a bundle of flat cards still shades as a rounded mass;
  * both windings are emitted, because the engine's mesh3d pipelines cull back
    faces unless a Material opts into two-sided rasterization.

Cluster centres are picked from the existing foliage anchors in a shuffled order
under a Poisson separation test, so they cover the crown evenly, stay inside a
hard geometry budget, and the twigs visibly enter the foliage.

FoliageCluster is a standalone implementation unit with its own
FoliageClusterDesc so other recipes can reuse it, and its disk sampling goes
through the module's existing Bridson sampler (poissonDiskPoints) instead of a
second blue-noise implementation.

Also fix the mesh.tree schema, which advertised clusters and colonization as
leafMode/branchAlgorithm choices while the generator rejected both, and was
missing canopy.

Tests live in their own translation unit (the shared procgen.cpp is already
~4500 lines): property tests for the sampler, the sphere normal transfer and
two-sided emission, a case asserting every cluster knob is exposed in the
schema and actually changes the mesh, and an opt-in step-by-step render dump
(EVENGINE_TREE_STEPS_DIR) that draws each construction stage.

Verified with ctest -R 'mesh\.|poisson' (136/136).
2026-09-11 15:17:29 +08:00
西风逍遥游 7e45cdaebd feat(graphics): add configurable stylized water 2026-09-11 14:54:02 +08:00
西风逍遥游 859845bbb4 Merge branch 'dev' into codex/test-suite-representative-tests 2026-09-11 14:25:00 +08:00
西风逍遥游 563fcff662 Merge pull request #364 from EVEngine/codex/level-design-tooling
Add a level design tool example with terrain references and play mode
2026-09-11 01:08:42 +08:00
西风逍遥游 87a1ec52c4 Add a level design tool example with terrain references and play mode
Introduce examples/level-designer: a 3D level design tool assembled from the
existing composable editor, procgen, physics and animation components.

- Terrain is referenced rather than embedded. Levels either regenerate it from
  sampler parameters, or point at a terrain asset on disk. Both encodings the
  runtime persists are decodable: the chunked EVTR archive and the raw EVTRN
  float32 heightfield written next to an eve.terrain/1 asset.
- Whitebox pieces come from the ProcgenKit recipe registry (75 prototype
  pieces). A level stores only the recipe id plus parameter values, so the mesh,
  renderable and play-mode collider are all derived and no geometry is
  duplicated into the level file. Piece parameters are driven by the recipe's
  own schema.
- Player spawn points are ordinary scene nodes carrying facing plus the
  character and motion set play mode should start with.
- Play mode builds a runtime world from the same authored data (terrain height
  field plus one static box per piece) and drives the player with the Motion
  Matching controller, using capsule collision for movement and a third-person
  follow camera. Authored data is never mutated by a play session.

Editor view control is an orbit camera around a pannable focus point: right
drag orbits, middle drag pans along the view's ground basis, Q/E zoom, and F
focuses the selection or frames the whole terrain. Play-mode view is the
third-person follow camera; camera yaw is also the locomotion basis, so mouse
look steers where WASD runs.

Add procgen terrain file decoding:

- TerrainFile.h/.cpp decode EVTR and EVTRN into a Heightmap plus spacing, height
  range and format metadata. Failures return structured diagnostics
  (not_found, parse_error, unsupported) and never a partially filled grid.
- procgen.loadTerrainFile(path, format) and procgen.loadTerrainBytes(bytes,
  format) expose it to script through the existing owning-handle proxy path.
  EVTR stores no metres-per-cell, so hasSpacing reports whether the decoded
  spacing is authoritative.

Verification: examples/level-designer/smoke.nut drives the live engine over MCP
(56 checks: terrain sampling and snapping, whitebox placement and parameter
rebuild, spawn resolution, panel construction in every tool state, play-mode
assembly and landing on the height field, camera-relative locomotion, character
and motion set switching, level document round trip and rejection, asset
terrain loading and rebuild atomicity). The new decoder has a unit test with 58
assertions covering both encodings, magic mismatches, truncated payloads and
dimension/size disagreement.
2026-09-10 23:22:28 +08:00
西风逍遥游 956570131f Merge dev and preserve resource format test contracts 2026-09-10 15:36:51 +08:00
西风逍遥游 7d513f767f test: replace redundant work with representative behavioral checks 2026-09-10 15:34:01 +08:00
西风逍遥游 76d80a1518 feat: add scalable terrain streaming and editor persistence 2026-09-10 08:11:01 +08:00
西风逍遥游 2da9c8fcc9 Merge remote-tracking branch 'origin/dev' into codex/classic-rendering-chain
# Conflicts:
#	.github/workflows/ci.yml
2026-08-30 16:10:43 +08:00
sunxfancy 26e46a2739 test(procgen): migrate terrain coverage to handle APIs 2026-08-29 21:33:10 +08:00
西风逍遥游 4e64f69f90 Merge remote-tracking branch 'origin/dev' into codex/classic-rendering-chain
# Conflicts:
#	cmake/module_manifest.cmake
#	src/modules/dialogue/Dialogue.h
#	src/modules/graphics/Graphics.h
#	src/modules/graphics/RenderSystem3D.cpp
#	src/modules/graphics/vulkan/Graphics.cpp
#	src/modules/graphics/vulkan/Graphics.h
#	src/modules/graphics/vulkan/Graphics2D.cpp
#	src/modules/graphics/vulkan/GraphicsPipeline.cpp
#	src/modules/graphics/webgpu/Canvas.cpp
#	src/modules/graphics/webgpu/Graphics.cpp
2026-08-29 11:52:46 +08:00
sunxfancy 9000984883 Merge remote-tracking branch 'origin/dev' into codex/terrain-hydrology-system
# Conflicts:
#	docs/usr/modules/procgen.md
#	docs/usr/modules/voxel.md
#	examples/terrain-editor/main.nut
#	src/modules/procgen/Procgen.cpp
#	src/modules/procgen/Procgen.h
#	src/modules/voxel/VoxelWorld.cpp
#	src/modules/voxel/VoxelWorld.h
#	test/procgen.cpp
#	test/voxel.cpp
2026-08-29 09:11:49 +08:00
sunxfancy 146f7244dc feat(procgen): add production terrain hydrology pipeline 2026-08-29 03:10:51 +08:00
西风逍遥游 63246b8c68 fix: complete architecture consolidation contracts 2026-08-28 00:01:55 +08:00
西风逍遥游 8ec65e796c fix: enforce checked handles across full CI build 2026-08-27 18:21:02 +08:00
西风逍遥游 a8b8e83128 refactor: consolidate engine architecture contracts 2026-08-27 12:54:48 +08:00
西风逍遥游 128fd5c95e feat(graphics): complete classic rendering chain 2026-08-26 22:35:20 +08:00
西风逍遥游 a2f7e75992 Merge branch 'dev' into codex/procgen-castle 2026-08-26 08:46:46 +08:00
sunxfancy 0ec8271bd0 fix(procgen): restore CI compatibility 2026-08-26 07:40:24 +08:00
西风逍遥游 a4ed45fa49 feat: add procedural civilization-style hex terrain 2026-08-26 02:50:01 +08:00
sunxfancy e3e7cc633e feat(procgen): add procedural castle generator 2026-08-26 02:23:36 +08:00
西风逍遥游 9e9c6f9874 merge(dev): integrate WebGPU parity with latest rendering changes 2026-08-25 15:05:07 +08:00
西风逍遥游 764cfc6fc0 fix(webgpu): wait for native texture readbacks 2026-08-25 12:03:02 +08:00
西风逍遥游 0fe519e483 feat(procgen): expose mesh recipe schemas 2026-08-25 06:09:09 +08:00
西风逍遥游 a8e92479ed feat(procgen): share schemas across material recipes 2026-08-25 06:09:09 +08:00
西风逍遥游 1acb447d1d feat(procgen): expose generator parameter schemas 2026-08-25 06:09:09 +08:00
西风逍遥游 c05c95c231 feat(procgen): improve procedural bush structure 2026-08-23 21:57:34 +08:00
西风逍遥游 811eff1c3a ci(quality): warnings-as-errors gate, sanitizers, smoke, Python lint/tests
- 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.
2026-08-22 02:27:37 +08:00
西风逍遥游 34d1fdb63b refactor(modules): clear remaining back-edges and split the largest TUs (#127)
* 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)
2026-08-20 05:53:33 +08:00
西风逍遥游 9dbb94479f test: initialize graphics in map.fov.gpuMaskTexture and graphics.water.paramsRoundTrip 2026-08-20 03:33:08 +08:00
西风逍遥游 adf32a4e78 test: re-enable hair/runtime suites, add resource-lifetime and tooling tests
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.
2026-08-19 22:44:53 +08:00
西风逍遥游 095556319e Merge branch 'main' into feature/waterfall-shader 2026-08-16 21:29:04 +08:00
西风逍遥游 3cd783305b Merge pull request #70 from EVEngine/feature/cloud-shadows
feat(procgen): dynamic clouds + real-time cloud shadows on ground
2026-08-16 21:24:25 +08:00
sunxfancy 2ef59e9091 test(graphics): planar reflection + optimize render-to-canvas readback
- 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.
2026-08-16 16:05:34 +08:00
sunxfancy 166a956724 feat(graphics): render 3D scene into an offscreen Canvas (render-to-canvas)
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.
2026-08-16 14:59:03 +08:00
sunxfancy 581193e622 test(graphics): drop planar-reflection demo (blocked by renderer limitation)
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.
2026-08-16 13:12:36 +08:00
sunxfancy badb0d945d feat(graphics): screen-space reflection pass + Water SSR integration
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.
2026-08-16 12:59:59 +08:00
sunxfancy 3592c05438 Merge remote-tracking branch 'origin/main' into feature/waterfall-shader
# Conflicts:
#	src/modules/graphics/Graphics.cpp
#	src/modules/graphics/Graphics.h
2026-08-16 12:56:54 +08:00
西风逍遥游 d3636c7e4e Merge branch 'main' into feature/cloud-shadows 2026-08-15 23:45:14 +08:00
sunxfancy 1d62dade5b fix(graphics): water ripples use a soft damped wavelet, not a hard ring
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.
2026-08-15 23:41:43 +08:00
sunxfancy fcac5f49fc test(graphics): water scene with visible gradient skybox
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.
2026-08-15 23:36:16 +08:00
sunxfancy 30d2fb13bb feat(graphics): flowing waterfall shader
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.
2026-08-15 23:33:53 +08:00
sunxfancy 92af87697f test(graphics): water scene uses a single flat plane, not tiled cubes
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.
2026-08-15 23:30:21 +08:00
sunxfancy 30588901cd feat(graphics): raise sky-reflection baseline; add tiled-block water demo
- 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).
2026-08-15 22:51:48 +08:00
sunxfancy 55334b7009 feat(graphics): dynamic water shader with sky reflection + ripples
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.
2026-08-15 22:35:16 +08:00