Server/README.md: why water presence is not just WBID. Records the classify-vs-render identity that makes the second clause both necessary (inside the crater carve) and safe (provably inert everywhere else), so the next reader does not have to re-derive it from the commit log — or, worse, "simplify" the second clause away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
74 lines
5 KiB
Markdown
74 lines
5 KiB
Markdown
# 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
|
||
1. Loads `ServerConfig.json` and the seed's `.dat` blueprint (v2 or legacy v1 — the parser
|
||
dispatches automatically; see `Core/Scripts/BLUEPRINT_FORMAT.md`).
|
||
2. **Cross-checks the blueprint's embedded params** (v2 only) against the config and logs a
|
||
prominent `BLUEPRINT/CONFIG DESYNC` warning if the seed or MapSize disagree — a config edited
|
||
after generation is loud now, not silent.
|
||
3. **Finds the Capitol** in the parsed town list and uses it as the world origin point.
|
||
4. Converts its pixel position to chunk coordinates (`pixel / CHUNK_SIZE`).
|
||
5. Builds a `(2 × ChunkRadius)²` grid of chunks around it — **synchronously, all at boot**.
|
||
6. Teleports the `Camera3D` to 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.
|
||
- **Water per column** (tasks 13, 15) — the blueprint is the **authority** on where water is and at
|
||
what level; the server only reads it. `WSRF` gives the surface level per pixel, and the `WBTB`
|
||
body table is the fallback when a column is flagged wet but carries the `WSRF` no-water sentinel.
|
||
|
||
**Presence takes two clauses.** `WBID` (the water stage's own classification output) OR *the
|
||
rendered ground being under the ocean's surface*. The second exists because `WBID` was classified
|
||
from the **uncurved** heightmap while the mesh renders the **curved** one. Outside the crater
|
||
those agree exactly — the curve is identity at sea and monotonic, so `Apply(raw) < sea` iff
|
||
`raw < sea`. Inside it they do not: the carve lerps two different bases toward one target
|
||
(classify from `raw`, rendered from `Apply(raw)`, and `Apply(raw) < raw` in the lowland band), so
|
||
the rendered surface sinks faster and leaves a ring rendering below the waterline that `WBID`
|
||
still calls dry — 375,824 px on the C1 seed, all of it inside the carve. The second clause tests
|
||
the height the mesh actually uses against the **ocean body's own level from `WBTB`**, so the
|
||
runtime still derives nothing, and by the identity above it can only ever fire inside the carve. A body the table does not know leaves the column **dry** rather than
|
||
guessing a level. The level is scaled by the same `HEIGHT_SCALE` as the terrain, so the sheet and
|
||
the seabed cannot drift apart. Depth for shading is taken from **blueprint** heights, not rendered
|
||
geometry — the terrain render clamps its floor at `Y = 2`, which would otherwise flatten every
|
||
deep-ocean column to one value. Each run logs what it drew (`[Server] Water at rest: …`).
|
||
- **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.
|