The blueprint has carried water since task 03 -- WBID per-pixel body id,
WBTB body table, WSRF per-pixel surface level. Nothing ever drew it. Now
the runtime does. No new water data: the blueprint is the authority on
where water is and at what level, and this only reads it.
WHY WATER IS ITS OWN MESH, not a block in the terrain field. The terrain
is a Marching-Cubes iso-surface over ChunkData.Densities. Writing WATER
into that field would not lay a sheet on top of the seabed -- it would
move the iso-surface, fusing the sea into the terrain as if it were solid
ground. A second surface is the only way water can sit at ITS level
independent of the ground under it. So ChunkRenderer builds a water
MeshInstance3D as a child of the chunk's terrain mesh.
TRANSPARENCY IS SHIPPED, NOT DEFERRED (Step 2.4's cheap branch). Because
water is a distinct MeshInstance3D it carries its own StandardMaterial3D,
so alpha is one flag and Godot sorts transparent surfaces after opaque
ones by itself. Zero mesher changes. Alpha runs 0.62 shallow -> 0.97 deep,
so the shelved coast stays readable and open ocean closes up.
WHICH DATA DROVE IT: WSRF for the level (per-pixel, quantised to 1/32768
raw ~ 7.7 mm, far under the 1 m voxel, and no table lookup), WBID for
presence (it is the water stage's own classification output -- the same
set the biome grid's Ocean/Lake pixels form by construction), WBTB as the
fallback when a column is flagged wet but carries the WSRF no-water
sentinel. A body the table does not know leaves the column DRY rather
than guessing a level.
Details worth keeping:
- Water Y uses Constants.HEIGHT_SCALE, the SAME mapping as the terrain
surface, named so the sheet and the seabed cannot drift apart if the
vertical band is ever retuned.
- Depth for shading comes from BLUEPRINT heights, not rendered geometry:
the terrain render clamps its floor at Y=2, so every deep-ocean column
would otherwise read as one flat ~36 m and the gradient would die
exactly where the ocean gets interesting.
- A cell is drawn if ANY corner is wet, flat at the highest wet level.
Drawing onto a partly-dry cell is deliberate -- it carries the sheet
under the shoreline where the opaque terrain hides it. Only-fully-wet
cells retreat the waterline a metre and leave a dry gap around every
coast and lake.
- Non-metallic, roughness 0.35: the scene environment is minimal, and a
metallic surface reads near-black when there is nothing to reflect.
BlockRegistry gains WATER (id 11). Wire-safe: block IDs are never
serialized -- the blueprint's section table stores heights, biome ordinals
and water data, never block IDs, and chunks are not persisted yet.
MEASURED on seed 1825907253: 241,415 water columns across 454 of 4096
chunks; surface Y 37.6..37.6 m (flat, = 0.15 x 251 -- the flat sea model,
rendered); depth to 32.3 m, matching an independent read of the blueprint
exactly. Both an ocean and a lake fall in the default view.
NO REGRESSION to the 2D pipeline, verified section by section: a
regeneration of the working seed is byte-identical to task 12's run in
TCRV, TDTL, HGTS, BIOM, WBID, WBTB, WSRF, TOWN and all four road
sections; only PRMS differs, and only in its write timestamp. All four
snapshot PNGs md5-identical.
No storms, no waves, no animation, no flow -- water at rest. Those are C2+.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every README was read first, checked against the code in its directory, then
rewritten to describe what the code actually does now.
Corrected throughout: map size is config-driven (8192 default) not a fixed
4096; chunks are 24x24x256 not 32x32; road carving is implemented for all four
tiers, not a 'next step'; Data/ and Resources/ are empty, not populated.
Also fixed MATH_MARCHING_CUBES.md, which documented the density sign convention
exactly backwards — it claimed positive was underground, where the code treats
positive as above the surface and counts a corner inside when density < iso.
Getting that backwards inverts every normal, a bug this project has hit before.
The root README now carries accurate run steps: F5 runs the map generator (not
the game), F6 on Scenes/Main.tscn runs the 3D world, and ChunkRadius is a load
radius that should be dropped to 4-8 while iterating.
Docs only. No code changed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All four road tiers now carve into the 3D world. Rugged and Trail were
generated, exported and parsed all along, but nothing ever consumed them —
which is why county roads visible on the 2D map did not exist in 3D.
Tier identity is now carried into the carve via a RoadSegment struct, so each
tier gets its own width, shoulder and grade-smoothing from one tunable block in
Constants: highways widest and holding a grade, trails narrow and hugging the
land. Rugged and Trail surface as dirt rather than asphalt.
Because tiers now have different reach, nearest-by-distance was no longer a
sound way to pick the governing road — a nearby footpath could shadow a highway
whose shoulder still covered the column. The carve now considers only roads
whose shoulder actually reaches, and takes the closest of those.
The per-chunk cull pad is derived from the widest shoulder plus a margin, so
widening a road cannot silently truncate it at chunk edges.
Untouched: density field, Marching Cubes interpolation/welding, chunk dims,
the .dat contract, the 2D A* generation, and mesher normals.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
HeightAtPixel read a single heightmap cell after truncating the coordinate with
(int). Road path points are fractional and spaced under a metre apart, so
consecutive points truncated into the same cell (flat), then tipped into the
next one (a full inter-cell jump) — a metre-scale staircase under F2's fix.
It now reads the four surrounding cells and blends them bilinearly using the
fractional part of the coordinate, so height varies continuously with position.
Edge clamping is preserved: at the map border the second cell clamps back onto
the first, making the blend a no-op rather than an out-of-range read.
Only the sampling helper changed. Signature and both callers are untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Road elevation is now sampled at the point on the road segment nearest the
column being carved, instead of at the segment's midpoint. The old behaviour
gave every column near a segment that segment's single midpoint height, so each
stretch of road was one flat plank and consecutive planks stepped like a
staircase wherever the road crossed a gradient.
Elevation now varies continuously along the segment, and because neighbouring
segments share an end point the height matches exactly at the joins. A new
ROAD_GRADE_SMOOTHING constant dials between holding a straight grade
(cut-and-fill) and hugging the land, per D-021.
Untouched: density field, Marching Cubes, chunk dimensions, the .dat contract,
and the 2D A* road network itself. Only how existing paths are carved into 3D.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Vertex colour is now chosen from the true, unrounded surface height and faded
between adjacent materials across a tunable band, instead of switching at one
rounded integer depth. This removes the horizontal contour banding.
Colour path only: density field, Marching Cubes interpolation/welding, chunk
dimensions, the .dat contract and all road logic are untouched. BlockID
classification is unchanged and still drives non-visual use.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>