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> |
||
|---|---|---|
| .. | ||
| Scripts | ||
| README.md | ||
Server Module
Authoritative world building. Turns the static WorldBlueprint in RAM into physical 3D chunks. In a
future multiplayer setup this is the side that dictates terrain and ships chunk data to clients.
ServerChunkManager.cs
Attached to the World root node of Scenes/Main.tscn. Runs the whole 3D world at boot.
Startup
- Loads
ServerConfig.jsonand the seed's.datblueprint (v2 or legacy v1 — the parser dispatches automatically; seeCore/Scripts/BLUEPRINT_FORMAT.md). - Cross-checks the blueprint's embedded params (v2 only) against the config and logs a
prominent
BLUEPRINT/CONFIG DESYNCwarning if the seed or MapSize disagree — a config edited after generation is loud now, not silent. - Finds the Capitol in the parsed town list and uses it as the world origin point.
- Converts its pixel position to chunk coordinates (
pixel / CHUNK_SIZE). - Builds a
(2 × ChunkRadius)²grid of chunks around it — synchronously, all at boot. - Teleports the
Camera3Dto 120 m above the Capitol, looking down.
⚠ Two things to know about startup. The chunk grid is built in one blocking pass with no
streaming or unloading, so ChunkRadius directly controls boot cost — 32 means 4,096 chunks and
roughly 3.6 GB. And the camera LookAt points straight down, which is a degenerate case: the up
vector ends up parallel to the view direction, so camera roll is undefined and Godot logs a warning.
Per-chunk generation
- Road culling first. Only the road segments whose bounding box reaches this chunk are kept,
each tagged with its
RoadTier. The padding is derived from the widest shoulder any tier has plus a margin, so widening a road cannot silently truncate it at chunk edges. - Surface height per column (
GetExactSurface) — the blueprint height scaled into the chunk's usable vertical band, then modified by any road carving. - Density per voxel —
(y − surfaceY)normalised by the local slope, giving a signed distance to the surface. Positive above, negative below. - Block IDs per voxel via
BiomePalette, plus the per-column data the renderer needs. - Hands the finished chunk to a
ChunkRenderer.
Road carving
All four tiers carve, each with its own character (widest and smoothest for highways, narrow and
terrain-hugging for trails — values in Constants.cs).
For each column, the carve finds the nearest road whose shoulder actually reaches it — not simply the nearest road, since tiers have different reach and a nearby footpath must not shadow a highway still covering the column. It then reads the roadbed height at the closest point along that segment, blending between a straight ramp between the segment's endpoints ("holds a grade") and the terrain directly beneath ("hugs the land") according to the tier.
Inside the road radius the column is flattened to that height and flagged with the tier's surface material; out to the shoulder radius it eases back to natural ground with a smoothstep.
HeightAtPixel samples the heightmap bilinearly — road path points are fractional, and nearest-cell
sampling produced a metre-scale staircase along the roadbed.
Not here yet
No chunk streaming or unloading, no collision, no networking, no player. The "server" is currently a node in the same scene as the renderer — the server/client split is structural, not a process boundary.