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>
153 lines
6.2 KiB
C#
153 lines
6.2 KiB
C#
using Godot;
|
|
|
|
namespace IslaApocalypse.Core
|
|
{
|
|
public static class Constants
|
|
{
|
|
// Chunk Dimensions
|
|
public const int CHUNK_SIZE_X = 24;
|
|
public const int CHUNK_SIZE_Z = 24;
|
|
public const int CHUNK_HEIGHT = 256; // Our agreed-upon depth!
|
|
|
|
// World Generation
|
|
public const float ISO_LEVEL = 0.0f; // The threshold for Marching Cubes
|
|
public const float VOXEL_SCALE = 1.0f; // 1 Pixel = 1 Meter
|
|
|
|
// --- APPEARANCE TUNING ---------------------------------------------
|
|
// How many metres it takes for one surface material to fade into the
|
|
// next (grass -> dirt -> stone) on the rendered mesh.
|
|
//
|
|
// TUNE THIS BY EYE: bigger = softer, more gradual fade.
|
|
// 0.0 = hard switch, exactly like the old banded look
|
|
// 2.0 = default, a gentle two-metre blend
|
|
// 4.0+ = very soft, materials smear into each other
|
|
//
|
|
// Colour only. This does NOT affect terrain shape, collision, or what
|
|
// block is actually stored in a voxel.
|
|
public const float BLEND_BAND_METERS = 2.0f;
|
|
|
|
// --- ROAD CHARACTER BY TIER (D-022) ---------------------------------
|
|
// A road is carved to match how BUILT it is. One gradient, four steps:
|
|
// a highway is engineered, a trail is a footpath worn into the ground.
|
|
//
|
|
// Three knobs per tier:
|
|
// RoadRadius - half-width of the flat carriageway, in metres.
|
|
// ShoulderRadius - how far out the carve eases back to natural
|
|
// ground. Must always be LARGER than RoadRadius.
|
|
// GradeSmoothing - 0.0 hugs the land (follows every bump)
|
|
// 1.0 holds a grade (straight ramp between path
|
|
// points, cutting bumps and filling dips).
|
|
// SurfaceMaterial- what the carriageway is paved with.
|
|
//
|
|
// TUNE THESE BY EYE. They are starting values chosen to make the
|
|
// hierarchy visible, not final ones. A trail being bumpy is CORRECT —
|
|
// nobody drives a truck down a footpath.
|
|
//
|
|
// NOTE: these affect road ELEVATION (terrain shape) and paint, not the
|
|
// terrain noise. Tune the highway properly once vehicles can drive it.
|
|
|
|
public const float HIGHWAY_ROAD_RADIUS = 4.0f; // 8 m carriageway
|
|
public const float HIGHWAY_SHOULDER_RADIUS = 12.0f;
|
|
public const float HIGHWAY_GRADE_SMOOTHING = 1.0f; // fully engineered
|
|
|
|
public const float BRANCH_ROAD_RADIUS = 3.0f;
|
|
public const float BRANCH_SHOULDER_RADIUS = 9.0f;
|
|
public const float BRANCH_GRADE_SMOOTHING = 0.7f;
|
|
|
|
public const float RUGGED_ROAD_RADIUS = 2.0f;
|
|
public const float RUGGED_SHOULDER_RADIUS = 6.0f;
|
|
public const float RUGGED_GRADE_SMOOTHING = 0.4f;
|
|
|
|
public const float TRAIL_ROAD_RADIUS = 1.5f;
|
|
public const float TRAIL_SHOULDER_RADIUS = 4.0f;
|
|
public const float TRAIL_GRADE_SMOOTHING = 0.1f; // barely built at all
|
|
|
|
/// <summary>
|
|
/// The widest reach any road has. The per-chunk road cull pads by this
|
|
/// (plus a margin) so a road just outside a chunk still carves into it.
|
|
/// Computed from the values above, so raising a shoulder can never
|
|
/// silently outgrow the cull and truncate roads at chunk edges.
|
|
/// </summary>
|
|
public static readonly float MAX_SHOULDER_RADIUS = Mathf.Max(
|
|
Mathf.Max(HIGHWAY_SHOULDER_RADIUS, BRANCH_SHOULDER_RADIUS),
|
|
Mathf.Max(RUGGED_SHOULDER_RADIUS, TRAIL_SHOULDER_RADIUS));
|
|
|
|
/// <summary>Safety margin added to the road cull, in metres.</summary>
|
|
public const float ROAD_CULL_MARGIN = 3.0f;
|
|
|
|
// --- WATER AT REST (terrain-water task 13) --------------------------
|
|
// The blueprint has carried water since task 03; this is only about
|
|
// DRAWING it. Water is a flat sheet at the body's own surface level —
|
|
// no waves, no flow, no animation. Those are C2+.
|
|
|
|
/// <summary>
|
|
/// Sentinel in ChunkData.WaterSurfaceY for "this column has no water".
|
|
/// Real surface heights are always >= 2 (the terrain clamp's floor), so a
|
|
/// negative value is unambiguous.
|
|
/// </summary>
|
|
public const float NO_WATER = -1.0f;
|
|
|
|
/// <summary>
|
|
/// Raw blueprint height -> world metres. One raw unit is this many metres,
|
|
/// and it is exactly the terrain mapping (CHUNK_HEIGHT - 5), named so the
|
|
/// water surface and the seabed cannot drift apart if the band is retuned.
|
|
/// </summary>
|
|
public const float HEIGHT_SCALE = CHUNK_HEIGHT - 5;
|
|
|
|
// Depth shading. Depth is measured from the BLUEPRINT heights, not the
|
|
// rendered geometry: the terrain render clamps its floor at Y=2, so deep
|
|
// ocean would otherwise all read as one flat ~36 m and the gradient would
|
|
// die exactly where the ocean gets interesting.
|
|
public const float WATER_DEEP_METERS = 60.0f; // depth at which the gradient bottoms out
|
|
public static readonly Color WATER_SHALLOW = new Color(0.38f, 0.76f, 0.78f);
|
|
public static readonly Color WATER_DEEP = new Color(0.05f, 0.17f, 0.42f);
|
|
|
|
/// <summary>
|
|
/// Water alpha, shallow -> deep. Shallows are clearer, so the shelved coast
|
|
/// and the seabed under it stay readable; depth closes the surface up.
|
|
/// </summary>
|
|
public const float WATER_ALPHA_SHALLOW = 0.62f;
|
|
public const float WATER_ALPHA_DEEP = 0.97f;
|
|
|
|
/// <summary>
|
|
/// Looks up the carve settings for a road tier. One place to tune.
|
|
/// </summary>
|
|
public static RoadProfile GetRoadProfile(RoadTier tier)
|
|
{
|
|
switch (tier)
|
|
{
|
|
case RoadTier.Highway:
|
|
return new RoadProfile(HIGHWAY_ROAD_RADIUS, HIGHWAY_SHOULDER_RADIUS,
|
|
HIGHWAY_GRADE_SMOOTHING, BlockRegistry.ASPHALT);
|
|
case RoadTier.Branch:
|
|
return new RoadProfile(BRANCH_ROAD_RADIUS, BRANCH_SHOULDER_RADIUS,
|
|
BRANCH_GRADE_SMOOTHING, BlockRegistry.ASPHALT);
|
|
case RoadTier.Rugged:
|
|
return new RoadProfile(RUGGED_ROAD_RADIUS, RUGGED_SHOULDER_RADIUS,
|
|
RUGGED_GRADE_SMOOTHING, BlockRegistry.DIRT);
|
|
default: // Trail
|
|
return new RoadProfile(TRAIL_ROAD_RADIUS, TRAIL_SHOULDER_RADIUS,
|
|
TRAIL_GRADE_SMOOTHING, BlockRegistry.DIRT);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// How one tier of road is carved. Values come from Constants above.
|
|
/// </summary>
|
|
public struct RoadProfile
|
|
{
|
|
public float RoadRadius;
|
|
public float ShoulderRadius;
|
|
public float GradeSmoothing;
|
|
public byte SurfaceMaterial;
|
|
|
|
public RoadProfile(float roadRadius, float shoulderRadius, float gradeSmoothing, byte surfaceMaterial)
|
|
{
|
|
RoadRadius = roadRadius;
|
|
ShoulderRadius = shoulderRadius;
|
|
GradeSmoothing = gradeSmoothing;
|
|
SurfaceMaterial = surfaceMaterial;
|
|
}
|
|
}
|
|
}
|