islaApocalypse/Client
beezm 9c255a4fc6 feat: render the water we already had (terrain-water task 13, Phase C1)
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>
2026-08-09 02:32:04 -04:00
..
Scripts feat: render the water we already had (terrain-water task 13, Phase C1) 2026-08-09 02:32:04 -04:00
README.md docs: bring all READMEs current with actual code (post-sweep-10 truth pass) 2026-08-05 05:23:58 -04:00

Client Module

The visual layer. Takes finished chunk data and turns it into Godot nodes. It calculates nothing — no densities, no terrain, no collision.

ChunkRenderer.cs

A MeshInstance3D created per chunk by the server, which then calls RenderChunk(data).

  • Mesh generation. Calls MarchingCubes.GenerateMesh(), passing the density field, the block IDs, and the per-column data the mesher needs to colour correctly: the true (unrounded) surface heights, the biome per column, and the road surface material per column.
  • Vertex colouring. Creates a StandardMaterial3D with VertexColorUseAsAlbedo = true, so Godot renders the per-vertex colours the mesher assigned rather than a texture. Colours fade between materials across a tunable band instead of switching at whole-metre steps.
  • World positioning. ChunkPosition × CHUNK_SIZE × VOXEL_SCALE on X and Z, y = 0 — chunks are placed by their footprint and carry their full height internally.
  • Backface rendering. CullMode is disabled, so the world is still visible from underneath or from inside terrain.

Notes

  • One material instance per chunk. Each renderer builds its own StandardMaterial3D. Since they are all identical, a single shared material would do — an easy win whenever performance work starts.
  • Chunk positioning is relative to this node's parent, so moving the World node in the scene moves the entire rendered world with it.

Not here yet

No textures or UV mapping (raw vertex colour only), no LOD, no player, no UI, no input handling.