Core.Lattice.PrimitiveField v1, opt-in per strate (legacy Maze/VerticalShaft stay legacy): resolves the real arrival and departure mouths first, plans a mandatory spine, then maze cells and two dead ends (9 rooms, 9 edges, stable IDs). The spine descends 20 m through four helix turns (6 m radius, 3 m clear width, 5 m pitch, 2.6 m clearance; max grade 10.0 deg); each feature is one filleted void (0.57 m measured). Final-field reachability passes at 883 stations; an orphaned shaft control is rejected; exact intervals (0 misses) and a whole-surface audit with 0 flags. Whole view 1.69 s (above the 1 s target). fast/medium/full PASS. Research R27 (self-checked): static stream-to-pool water design. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
12 KiB
VoxelForge R27 — revised self-check
Read-only review. I read the cited code and docs. I made no source changes and ran no scripts, tests, builds, or binaries.
What exists
Core: HeightfieldPointSample carries a stratum waterSurfaceSubvoxels and a point-level waterFillSubvoxels field (VoxelForgeCore.h:1297). Heightfield2D::point computes fill only for an enabled water plan and an air sample between the ground and the stratum-wide water level:
if(recipe_->evaluatorVersion>=2&&plan_->waterEnabled&&out.densitySubvoxels>=0
&&z>out.groundHeightSubvoxels&&z<plan_->waterSurfaceSubvoxels
&&out.groundHeightSubvoxels<plan_->waterSurfaceSubvoxels)
out.waterFillSubvoxels=std::min(z-out.groundHeightSubvoxels,
plan_->waterSurfaceSubvoxels-z);
(WorldAssemblerInternal.h:2610). The Unreal adapter sets that surface from BottomVoxel + HeightVoxels * WaterLevelRelative (VoxelForgeCoreAdapter.cpp:383).
The only waterFillSubvoxels consumer I found is the standalone evaluator’s determinism hash: it adds the value to the sample hash and compares results from 1, 8, and 16 workers (StandaloneViewer.cpp:8448). I found no local water-body records, water-specific interval query, or water BVH. The existing FieldInterval is for density (VoxelForgeCore.h:1301). R24’s separate occupancy channel and local mesh are proposals, not current behavior (R24 water notes).
Unreal: UVoxelContentManager::UpdateWater gets a water height for the player’s current stratum, using the Core snapshot when available and otherwise the legacy stratum lookup (VoxelContentManager.cpp:2530). It creates one UStaticMeshComponent using PlaneMesh:
WaterPlane->SetCollisionEnabled(ECollisionEnabled::NoCollision);
(VoxelContentManager.cpp:2575)
It scales the engine plane to cover the view distance (or uses a large fallback size) (VoxelContentManager.cpp:2585), snaps its center to a coarse player cell, and places it at the selected water height (VoxelContentManager.cpp:2592). It assigns the stratum’s WaterMaterial (VoxelContentManager.cpp:2597). The code comment calls it one global plane that follows the player (VoxelContentManager.h:94); VoxelWorld calls it each tick (VoxelWorld.cpp:7039). It is not local stream or pool geometry, and it has no collision. I found no swimming-volume or underwater post-process implementation in this plugin. The Underwater generator shares the tunnel density graph and an “water” flag; it does not implement swimming (VoxelGenerator.cpp:2638).
Reference image
I did not find the requested image in Docs/: the Docs tree in this checkout contains archived Markdown files and no image assets. The nearest water-named image I found is Standalone/data/owner_normals_pool_before.png, outside Docs. It shows a broad blue-grey surface against uneven rocky ground, but no clear stream entering a bounded pool. I can’t claim the design matches the missing reference. It does match the written R24 proposal for a local pool, fixed stream, separate water channel, and local mesh (R24).
Proposed design
Use a plan-time deterministic steepest-descent trace on the resolved terrain, then store its quantized points as an immutable polyline. Sample on a 1 m XY grid; at each step choose the neighboring sample with the greatest downhill grade, breaking ties by a fixed compass order. A local sink ends the trace and becomes the pool site. This follows the earlier drainage note and makes the result reproducible across peers. The polyline is stored data, not simulated flow. Reject a candidate that exceeds the grade limits below rather than altering its path at runtime.
Initial dimensions for one fixture:
- Stream: 20 m centerline; target surface fall 2 cm per horizontal metre (2%), with no segment above 5 cm/m (5%). Use a sloped sheet, linearly interpolated between fixed-point controls, meeting the pool level exactly. Flat-per-segment surfaces would introduce visible steps at joins.
- Channel: 0.60 m flat invert; 0.30 m maximum cut depth; banks at 1 vertical to 4 horizontal. Place the water 0.20 m above the invert, yielding about 2.2 m of wet surface width before edge smoothing.
- Pool: 4 m radius, up to 0.60 m deep, with a bowl bed rising to the waterline at its rim. Set the fixed, flat surface 0.05 m below the basin’s lowest spill saddle; blend the carved shoreline into native terrain. The stream’s final surface height equals this level.
Treat all of those dimensions as unvalidated starting values. Reject a trace that cannot meet the sheet grade or fit the banks under the resolved terrain.
Add immutable Core pool and stream records. Keep water occupancy W separate from rock density D; for example:
Wpool = min(z - hpool(r), zpool - z, 4.0 - r)
Wstream = min(z - bed(s,d), zw(s) - z, 1.1 - |d|, s, L - s)
W = max(Wpool, Wstream)
Here s is distance along the centerline, d is cross-stream distance, and positive W means inside the water. Keep the rock bed in D; clip rendered water to the air side of the bed.
Give each pool and stream segment an outward-rounded AABB in fixed-point units. The pool box covers its 4 m radius and bed-to-surface height. A stream occupancy box covers the segment, 1.1 m wet half-width, and bed-to-surface height; the separate bed-carve bounds should include the wider banks and blend. Query a BVH of these boxes, then conservatively bound W over each candidate using interval arithmetic and combine candidates with max. If a query misses every water box, it is dry; if a bound is unknown, don’t cull it. This follows R24’s proposed separate W, but adds concrete bounds and dimensions (R24).
Game references, at the player-facing level: Minecraft represents water as blocks; it spreads into nearby air, while generated rivers sit at sea level and are calm (water, rivers). Astroneer’s official notes describe its Simulated Aquarium as a decoration that needs no water, not a terrain stream (Megatech notes). Enshrouded is a more ambitious contrast: its update describes dynamically simulated, redirectable water and swimming/diving (Wake of the Water). Valheim documents ocean sailing by boat (FAQ). No Man’s Sky emphasizes water rendering and underwater play (Worlds Part II). Deep Rock Galactic’s cited update treats frozen lakes as slippery hazards (update notes). These examples inform the experience; they do not establish those games’ internal water implementations.
Unreal minimum
The smallest local implementation is a separate water surface mesh generated from the Core records. The plugin already uses URealtimeMeshComponent for terrain tiles and declares its module dependency (VoxelWorld.h:424, VoxelForge.Build.cs:75); reuse it for water tiles with separate material and no surface collision. The bed remains the walkable collider. For VR, give the surface clear reflection/Fresnel and refraction cues, subtle normal motion, and a readable shoreline. Keep wave and screen-space distortion modest.
Unreal’s Water plugin offers AWaterBodyRiver, AWaterBodyLake, and AWaterBodyCustom; its Water Zone generates water mesh tiles. Its automatic terrain brush works with Landscape edit layers, so it won’t carve these voxel beds for us. A custom water body can use a mesh for a pool, with the bed carved separately (Water Body Actors). The plugin is not listed in VoxelForge’s module dependencies; whether the host project enables it is unverified. UProceduralMeshComponent is another runtime mesh option (API), but the existing URealtimeMeshComponent is the smaller project fit.
This MVP’s 0.60 m pool and shallow stream don’t need swimming or underwater post-processing to read as water. If deeper pools are later allowed, APhysicsVolume exposes bWaterVolume and UCharacterMovementComponent supports swimming (APhysicsVolume, CharacterMovementComponent). Unreal’s Water Body underwater post-process relies on collision to detect submersion (UWaterBodyComponent).
Walkability
Keep the water path outside the route corridor by default. The current Core route audit checks a 0.34 m radius, 1.76 m capsule and a 15° maximum grade, but checks density and support, not water depth (API.md:140). Extend that audit to water.
I recommend allowing an intentional ford up to 0.30 m deep, measured from its walkable bed to the sheet, as a non-swimming crossing. Deeper water must be bypassed or bridged. Keep the 0.60 m pool outside the route corridor. If “dry route” means no wet crossing at all, use the bridge or bypass every time.
Slices and verification
- Core records and bounds: add fixed-point records, deterministic path output,
Wqueries, interval bounds, and BVH. Capture a plan view with the path and bounds overlaid; compare record digests across peers and worker counts. - Carved bed: apply the stream and pool cuts to density. Capture channel and pool cross-sections; audit route capsule clearance, grade, support, and water depth.
- Water mesh: emit a separate pool disk and stream sheet. Capture the stream-to-pool join, shoreline, and tile boundaries from above and at walking height.
- Unreal: stream local water tiles, apply the material, and add swimming volume/post-process only if deeper water is approved. Capture headset views of the ford, pool edge, and—if implemented—submersion.
I’m unsure whether the missing reference image exists outside this checkout, whether the host project has the Water plugin enabled, and whether the proposed dimensions and 2% grade suit the intended scene. The current route audit also has no water-depth check.
Owner questions: Can you provide the reference image’s path or attach it again? Is a 0.30 m ford acceptable, or must the mouth-to-mouth route stay entirely dry?