baseline: working salvaged prototype on Godot 4.7.1 (pre-F1)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stewart Howe 2026-08-05 01:45:41 -04:00
commit 7f0f43b5fb
48 changed files with 2913 additions and 0 deletions

22
Server/README.md Normal file
View file

@ -0,0 +1,22 @@
# Server Module - IslaApocalypse
**Phase 2 Status:** Active (Authoritative Chunk Management Online)
## Overview
The `Server` directory contains the authoritative logic for the 3D Voxel Engine. It acts as the bridge between the static 2D `WorldBlueprint` residing in RAM and the physical 3D environment generated around the player. This architecture ensures that in a future multiplayer environment, the server dictates the terrain and sends chunk data to the clients.
## Key Architecture & Components
### 1. The Authoritative Node (`ServerChunkManager.cs`)
Attached to the root of the `Main.tscn` world scene, this script manages the lifecycle of the entire 3D environment.
* **Initialization:** Upon boot, it reads the `WorldBlueprint` and dynamically locates the exact X/Y pixel coordinates of the `Capitol` city to use as the initial world spawn point.
* **Coordinate Translation:** Converts 2D pixel coordinates into 3D chunk grid coordinates (e.g., Map Pixel [2048, 2048] becomes Chunk [64, 64]).
* **Memory Management:** Maintains an `_activeChunks` dictionary to track which chunks are currently loaded in RAM, preventing duplicate generation and allowing for future chunk culling/unloading when the player moves away.
### 2. Terrain Math & Generation
* **True Vertical Scaling:** Height calculation directly multiplies the raw `FastNoiseLite` float (0.0 to 1.0) against `Constants.CHUNK_HEIGHT` (256 meters). This avoids heightmap compression and allows for towering, AAA-scale mountain cliffs.
* **Density Calculation:** Uses exact surface interpolation to calculate the signed 3D distance of every voxel relative to the surface. This prevents terrain collapsing on horizontal edges and creates mathematically perfect slopes for the Marching Cubes algorithm.
* **Biome Injection:** Samples the `BiomeMap` at the exact X/Z world coordinate to assign a specific `BlockID` (via `BiomePalette`) to every voxel in the chunk.
## Next Immediate Steps (Phase 2, Step 4)
* **A* Road Carving:** The Server Chunk Manager needs to be updated to cross-reference the `Highways` and `BranchRoads` vectors from the blueprint. If a chunk contains a road, the server must mathematically flatten the `exactSurfaceY` and override the `BlockID` to `ASPHALT`.

View file

@ -0,0 +1,225 @@
using Godot;
using System.Collections.Generic;
using IslaApocalypse.Core;
namespace IslaApocalypse.Server
{
public partial class ServerChunkManager : Node
{
private WorldBlueprint _blueprint;
private Dictionary<Vector2I, ChunkData> _activeChunks = new Dictionary<Vector2I, ChunkData>();
public int chunkSize = 24;
public override void _Ready()
{
// 1. LOAD CONFIGURATION and the seed-based blueprint data from the MapDataParser!
ConfigManager.LoadConfig();
chunkSize = ConfigManager.ChunkRadius; // Update chunk radius from config file
_blueprint = MapDataParser.LoadMapData(ConfigManager.WorldSeed.ToString());
if (_blueprint != null)
{
GD.Print("[Server] Blueprint loaded. Locating Capitol City...");
// 2. Find the Capitol in the parsed data
Vector2 capitolPos = new Vector2(2048, 2048); // Safe fallback center
foreach (var town in _blueprint.Towns) {
if (town.Tier == TownTier.Capitol) {
capitolPos = town.Position;
break;
}
}
// Old original png map coords
// Vector2 capitolPos = new Vector2(2405, 3296); // hardcoded for now since we know exactly where it is in this seed, which is the "paradise" biome hub
GD.Print($"[Server] Capitol found at {capitolPos}. Generating chunks...");
// 3. Convert pixel coordinates to Chunk coordinates
int capitolChunkX = (int)(capitolPos.X / Constants.CHUNK_SIZE_X);
int capitolChunkZ = (int)(capitolPos.Y / Constants.CHUNK_SIZE_Z);
int radius = ConfigManager.ChunkRadius;
for (int x = capitolChunkX - radius; x < capitolChunkX + radius; x++)
{
for (int z = capitolChunkZ - radius; z < capitolChunkZ + radius; z++)
{
GenerateChunk(new Vector2I(x, z));
}
}
// 5. Teleport the Camera to look down at our creation!
Camera3D cam = GetNodeOrNull<Camera3D>("Camera3D");
if (cam != null)
{
// Put the camera 120 meters in the air above the Capitol
cam.GlobalPosition = new Vector3(capitolPos.X, 120f, capitolPos.Y);
// Point it straight down at the ground
cam.LookAt(new Vector3(capitolPos.X, 0, capitolPos.Y));
}
}
}
public void GenerateChunk(Vector2I chunkCoord)
{
if (_activeChunks.ContainsKey(chunkCoord)) return;
ChunkData newChunk = new ChunkData(chunkCoord);
int startX = chunkCoord.X * Constants.CHUNK_SIZE_X;
int startZ = chunkCoord.Y * Constants.CHUNK_SIZE_Z;
// --- NEW: SPATIAL CULLING FOR ROADS ---
// Create a bounding box for this chunk, plus a 15-meter padding to account for the road's dirt shoulders
Rect2 chunkBounds = new Rect2(startX - 15, startZ - 15, Constants.CHUNK_SIZE_X + 30, Constants.CHUNK_SIZE_Z + 30);
List<Vector2[]> localRoadSegments = new List<Vector2[]>();
// Filter Highways: Only save the segments that actually cross this specific chunk!
foreach (var highway in _blueprint.Highways) {
for (int i = 0; i < highway.Length - 1; i++) {
Vector2 a = highway[i];
Vector2 b = highway[i+1];
Rect2 segBounds = new Rect2(Mathf.Min(a.X, b.X), Mathf.Min(a.Y, b.Y), Mathf.Abs(b.X - a.X), Mathf.Abs(b.Y - a.Y));
if (chunkBounds.Intersects(segBounds.Grow(1.0f))) {
localRoadSegments.Add(new Vector2[] { a, b });
}
}
}
// Filter Branch Roads
foreach (var branch in _blueprint.BranchRoads) {
for (int i = 0; i < branch.Length - 1; i++) {
Vector2 a = branch[i];
Vector2 b = branch[i+1];
Rect2 segBounds = new Rect2(Mathf.Min(a.X, b.X), Mathf.Min(a.Y, b.Y), Mathf.Abs(b.X - a.X), Mathf.Abs(b.Y - a.Y));
if (chunkBounds.Intersects(segBounds.Grow(1.0f))) {
localRoadSegments.Add(new Vector2[] { a, b });
}
}
}
// ---------------------------------------
for (int x = 0; x <= Constants.CHUNK_SIZE_X; x++)
{
for (int z = 0; z <= Constants.CHUNK_SIZE_Z; z++)
{
int globalX = startX + x;
int globalZ = startZ + z;
if (globalX >= _blueprint.MapSize || globalZ >= _blueprint.MapSize || globalX < 0 || globalZ < 0)
continue;
float GetExactSurface(int gX, int gZ, out bool isRoad)
{
isRoad = false;
if (gX >= _blueprint.MapSize) gX = _blueprint.MapSize - 1;
if (gZ >= _blueprint.MapSize) gZ = _blueprint.MapSize - 1;
float raw = _blueprint.HeightMap[gX, gZ];
float baseHeight = Mathf.Clamp(raw * (Constants.CHUNK_HEIGHT - 5), 2.0f, Constants.CHUNK_HEIGHT - 2.0f);
// If no roads are in this chunk, skip the heavy math entirely!
if (localRoadSegments.Count == 0) return baseHeight;
float finalHeight = baseHeight;
float minDist = 9999f;
Vector2 currentPos = new Vector2(gX, gZ);
float roadRadius = 4.0f; // The flat asphalt part (8 meters total width)
float shoulderRadius = 12.0f; // The sloped dirt/rock carving into the mountain
float closestRoadElevation = baseHeight;
foreach (var seg in localRoadSegments)
{
float dist = DistanceToLineSegment(currentPos, seg[0], seg[1]);
if (dist < minDist)
{
minDist = dist;
if (dist <= shoulderRadius)
{
// Sample the heightmap precisely at the center of the road segment
// so the whole road stays at a uniform elevation, ignoring the sloped mountain under it.
Vector2 midPoint = (seg[0] + seg[1]) / 2.0f;
float roadRaw = _blueprint.HeightMap[(int)midPoint.X, (int)midPoint.Y];
closestRoadElevation = Mathf.Clamp(roadRaw * (Constants.CHUNK_HEIGHT - 5), 2.0f, Constants.CHUNK_HEIGHT - 2.0f);
}
}
}
// THE BULLDOZER: Carve the terrain
if (minDist <= roadRadius) {
finalHeight = closestRoadElevation; // Flatten it completely!
isRoad = true;
} else if (minDist <= shoulderRadius) {
// Smoothly interpolate from the flat road up/down to the natural mountain height
float t = (minDist - roadRadius) / (shoulderRadius - roadRadius);
t = t * t * (3f - 2f * t); // SmoothStep equation for a beautiful curved slope
finalHeight = Mathf.Lerp(closestRoadElevation, baseHeight, t);
}
return finalHeight;
}
// Pass the boolean out for the main voxel so we can paint it!
bool isMainRoad = false;
float exactSurfaceY = GetExactSurface(globalX, globalZ, out isMainRoad);
// We discard the 'out' variable for the normals using an underscore '_'
float surfaceRight = GetExactSurface(globalX + 1, globalZ, out _);
float surfaceFwd = GetExactSurface(globalX, globalZ + 1, out _);
Biome columnBiome = _blueprint.BiomeMap[globalX, globalZ];
float hRight = surfaceRight - exactSurfaceY;
float hFwd = surfaceFwd - exactSurfaceY;
float slopeX = hRight / Constants.VOXEL_SCALE;
float slopeZ = hFwd / Constants.VOXEL_SCALE;
float len = Mathf.Sqrt(slopeX * slopeX + 1.0f + slopeZ * slopeZ);
for (int y = 0; y <= Constants.CHUNK_HEIGHT; y++)
{
float verticalDist = y - exactSurfaceY;
float density = verticalDist / len;
newChunk.Densities[x, y, z] = density;
int blockY = Mathf.RoundToInt(exactSurfaceY);
// THE PAINT: Pass isMainRoad into the Biome Palette!
newChunk.BlockIDs[x, y, z] = BiomePalette.GetVoxelID(columnBiome, blockY, y, isMainRoad);
}
} // End of Z loop
} // End of X loop
_activeChunks.Add(chunkCoord, newChunk);
var renderer = new IslaApocalypse.Client.ChunkRenderer();
AddChild(renderer);
renderer.RenderChunk(newChunk);
}
/// <summary>
/// Calculates the shortest distance from a point to a line segment defined by v and w.
/// </summary>
private float DistanceToLineSegment(Vector2 point, Vector2 v, Vector2 w)
{
float l2 = v.DistanceSquaredTo(w);
if (l2 == 0) return point.DistanceTo(v); // v == w case
// Consider the line extending the segment, parameterized as v + t (w - v).
// We find projection of point p onto the line.
// It falls where t = [(p-v) . (w-v)] / |w-v|^2
float t = Mathf.Max(0, Mathf.Min(1, (point - v).Dot(w - v) / l2));
// Projection falls on the segment
Vector2 projection = v + t * (w - v);
return point.DistanceTo(projection);
}
}
}

View file

@ -0,0 +1 @@
uid://c1dxqbgohxr6k