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>
72 lines
3.7 KiB
C#
72 lines
3.7 KiB
C#
using Godot;
|
|
|
|
namespace IslaApocalypse.Core
|
|
{
|
|
public class ChunkData
|
|
{
|
|
public Vector2I ChunkPosition; // The X/Z coordinate of this chunk (e.g., 0,0 or 1,0)
|
|
|
|
// We need Width+1 and Depth+1 so the Marching Cubes mesh connects seamlessly to the neighbor chunks!
|
|
// public float[,,] Densities = new float[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_HEIGHT, Constants.CHUNK_SIZE_Z + 1];
|
|
// public byte[,,] BlockIDs = new byte[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_HEIGHT, Constants.CHUNK_SIZE_Z + 1];
|
|
// OLD:
|
|
// Densities = new float[Constants.CHUNK_SIZE_X, Constants.CHUNK_HEIGHT, Constants.CHUNK_SIZE_Z];
|
|
// BlockIDs = new int[Constants.CHUNK_SIZE_X, Constants.CHUNK_HEIGHT, Constants.CHUNK_SIZE_Z];
|
|
|
|
// NEW (Fix 3): We expand by 1 to sample the neighbor's border perfectly without seams!
|
|
public float[,,] Densities = new float[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_HEIGHT + 1, Constants.CHUNK_SIZE_Z + 1];
|
|
public byte[,,] BlockIDs = new byte[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_HEIGHT + 1, Constants.CHUNK_SIZE_Z + 1];
|
|
|
|
// --- PER-COLUMN DATA, FOR VERTEX COLOURING ONLY ----------------------
|
|
// One entry per X/Z column of this chunk (same +1 padding as above).
|
|
//
|
|
// Why these exist: BlockIDs are classified against a ROUNDED surface
|
|
// height, which is fine for storage but makes the rendered colour jump
|
|
// in whole-metre steps (the "contour band" artefact). The renderer
|
|
// instead reads the TRUE, unrounded surface height from here, so the
|
|
// material fade follows the real ground.
|
|
//
|
|
// These are read by the mesher only. Nothing about geometry, collision
|
|
// or storage depends on them.
|
|
public float[,] SurfaceHeights = new float[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_SIZE_Z + 1];
|
|
public Biome[,] ColumnBiomes = new Biome[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_SIZE_Z + 1];
|
|
|
|
// What this column's road surface is paved with — asphalt for highways,
|
|
// dirt for trails, and 0 (BlockRegistry.AIR) for "not a road at all".
|
|
// Carries the tier's material rather than a plain yes/no, so a footpath
|
|
// doesn't get painted like a motorway.
|
|
public byte[,] ColumnRoadMaterial = new byte[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_SIZE_Z + 1];
|
|
|
|
// --- WATER AT REST (terrain-water task 13) ---------------------------
|
|
// Per column: the world-space Y of the water SURFACE, or Constants.NO_WATER
|
|
// where the blueprint says this column is dry. Filled by the server from
|
|
// the blueprint's WSRF/WBID/WBTB — the runtime never decides where water
|
|
// is, it only draws what the blueprint already classified.
|
|
//
|
|
// WaterDepthM is the TRUE depth in metres (water level minus seabed, both
|
|
// in blueprint units), kept separately because the rendered seabed is
|
|
// clamped at Y=2 and would flatten every deep-ocean column to the same
|
|
// value. Colour reads this; geometry reads WaterSurfaceY.
|
|
public float[,] WaterSurfaceY = new float[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_SIZE_Z + 1];
|
|
public float[,] WaterDepthM = new float[Constants.CHUNK_SIZE_X + 1, Constants.CHUNK_SIZE_Z + 1];
|
|
|
|
/// <summary>True if any column in this chunk carries water — lets the
|
|
/// renderer skip building a water mesh for the many inland chunks.</summary>
|
|
public bool HasAnyWater = false;
|
|
|
|
// For our future delta-save system
|
|
public bool IsPlayerProtected = false;
|
|
public bool NeedsSaving = false;
|
|
|
|
public ChunkData(Vector2I position)
|
|
{
|
|
ChunkPosition = position;
|
|
|
|
// float[,] defaults to 0, which is a legal-looking water height. Start
|
|
// every column explicitly dry instead.
|
|
for (int x = 0; x <= Constants.CHUNK_SIZE_X; x++)
|
|
for (int z = 0; z <= Constants.CHUNK_SIZE_Z; z++)
|
|
WaterSurfaceY[x, z] = Constants.NO_WATER;
|
|
}
|
|
}
|
|
}
|