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

View file

@ -0,0 +1,63 @@
using Godot;
namespace IslaApocalypse.Core
{
public static class BiomePalette
{
/// <summary>
/// Determines the exact Block ID for a specific voxel in the 3D world based on its depth and biome.
/// </summary>
/// <param name="biome">The 2D biome pixel from the MapData.</param>
/// <param name="surfaceHeight">The maximum height of the terrain at this X/Z coordinate.</param>
/// <param name="currentY">The Y coordinate of the specific voxel we are painting.</param>
/// <param name="isRoad">Is this coordinate part of the highway/branch road network?</param>
public static byte GetVoxelID(Biome biome, int surfaceHeight, int currentY, bool isRoad)
{
// 1. Air Check: If we are above the terrain, it's Air.
if (currentY > surfaceHeight) return BlockRegistry.AIR;
// 2. Bedrock Check: The very bottom of the world is indestructible.
if (currentY == 0) return BlockRegistry.BEDROCK;
// 3. Infrastructure Check: If this is a road, and we are exactly on the surface, paint asphalt.
if (isRoad && currentY == surfaceHeight) return BlockRegistry.ASPHALT;
int depthBelowSurface = surfaceHeight - currentY;
// 4. Subsurface Logic (Dirt layer vs Stone layer)
// The top 4 blocks are usually dirt/sand, everything below that is solid stone.
if (depthBelowSurface > 4) return BlockRegistry.STONE;
// 5. Surface & Shallow Subsurface Logic (Based on Biome)
switch (biome)
{
case Biome.Beach:
case Biome.Crater:
return BlockRegistry.SAND; // Craters and Beaches are deep sand
case Biome.Snow:
case Biome.Mountain:
if (depthBelowSurface == 0) return BlockRegistry.SNOW; // Only the top layer is snow
return BlockRegistry.STONE; // Mountains have shallow topsoil, mostly stone
case Biome.Wasteland:
return BlockRegistry.WASTELAND_DIRT;
case Biome.Jungle:
if (depthBelowSurface == 0) return BlockRegistry.GRASS_JUNGLE;
return BlockRegistry.DIRT;
case Biome.Paradise:
if (depthBelowSurface == 0) return BlockRegistry.GRASS_PARADISE;
return BlockRegistry.DIRT;
case Biome.Tropical:
if (depthBelowSurface == 0) return BlockRegistry.GRASS_TROPICAL;
return BlockRegistry.DIRT;
default:
return BlockRegistry.DIRT; // Fallback
}
}
}
}

View file

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

20
Core/Scripts/BlockData.cs Normal file
View file

@ -0,0 +1,20 @@
using Godot;
namespace IslaApocalypse.Core
{
public struct BlockData
{
public byte ID;
public string Name;
public bool IsSolid;
public Color BaseColor; // We use color for now; later we replace this with Texture Array IDs for the Shader
public BlockData(byte id, string name, bool isSolid, Color baseColor)
{
ID = id;
Name = name;
IsSolid = isSolid;
BaseColor = baseColor;
}
}
}

View file

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

View file

@ -0,0 +1,53 @@
using Godot;
using System.Collections.Generic;
namespace IslaApocalypse.Core
{
public static class BlockRegistry
{
// A dictionary holding all the block types in the game, searchable by their byte ID
public static readonly Dictionary<byte, BlockData> Blocks = new Dictionary<byte, BlockData>();
// Hardcoded IDs for easy reference in code
public const byte AIR = 0;
public const byte BEDROCK = 1;
public const byte STONE = 2;
public const byte DIRT = 3;
public const byte SAND = 4;
public const byte GRASS_TROPICAL = 5;
public const byte GRASS_JUNGLE = 6;
public const byte GRASS_PARADISE = 7;
public const byte SNOW = 8;
public const byte WASTELAND_DIRT = 9;
public const byte ASPHALT = 10; // For our roads!
// This static constructor runs automatically the first time the registry is accessed
static BlockRegistry()
{
// ID 0 is ALWAYS Air in voxel engines.
Blocks.Add(AIR, new BlockData(AIR, "Air", false, Colors.Transparent));
// The Foundation Blocks
Blocks.Add(BEDROCK, new BlockData(BEDROCK, "Bedrock", true, new Color(0.1f, 0.1f, 0.1f)));
Blocks.Add(STONE, new BlockData(STONE, "Stone", true, new Color(0.5f, 0.5f, 0.5f)));
Blocks.Add(DIRT, new BlockData(DIRT, "Dirt", true, new Color(0.4f, 0.3f, 0.2f)));
Blocks.Add(SAND, new BlockData(SAND, "Sand", true, new Color(0.8f, 0.7f, 0.4f)));
// The Biome Surface Blocks
Blocks.Add(GRASS_TROPICAL, new BlockData(GRASS_TROPICAL, "Tropical Grass", true, new Color(0.2f, 0.6f, 0.2f)));
Blocks.Add(GRASS_JUNGLE, new BlockData(GRASS_JUNGLE, "Jungle Grass", true, new Color(0.1f, 0.4f, 0.1f)));
Blocks.Add(GRASS_PARADISE, new BlockData(GRASS_PARADISE, "Paradise Grass", true, new Color(0.4f, 0.8f, 0.4f)));
Blocks.Add(SNOW, new BlockData(SNOW, "Snow", true, new Color(0.9f, 0.9f, 0.9f)));
Blocks.Add(WASTELAND_DIRT, new BlockData(WASTELAND_DIRT, "Wasteland Dirt", true, new Color(0.5f, 0.2f, 0.5f)));
// Infrastructure
Blocks.Add(ASPHALT, new BlockData(ASPHALT, "Asphalt", true, new Color(0.15f, 0.15f, 0.15f)));
}
public static BlockData GetBlock(byte id)
{
if (Blocks.TryGetValue(id, out BlockData data)) return data;
return Blocks[AIR]; // Safe fallback so the game doesn't crash if an ID is missing
}
}
}

View file

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

29
Core/Scripts/ChunkData.cs Normal file
View file

@ -0,0 +1,29 @@
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];
// For our future delta-save system
public bool IsPlayerProtected = false;
public bool NeedsSaving = false;
public ChunkData(Vector2I position)
{
ChunkPosition = position;
}
}
}

View file

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

View file

@ -0,0 +1,111 @@
using Godot;
using Godot.Collections; // Required for Godot's built-in JSON parser
namespace IslaApocalypse.Core // Change this if your namespace is different
{
public static class ConfigManager
{
// Default Fallbacks
public static int WorldSeed = 1063685222;
// public static int WorldSeed = (int)GD.Randi(); if 0 also randomizes
public static int MapSize = 8192;
public static float CraterRadius = 400f;
public static float DensityMultiplier = 1.0f; // <-- Replaces TownCount
public static int ChunkRadius = 24; // Default chunk size, can be overridden by config
public static void LoadConfig()
{
string path = "res://ServerConfig.json";
if (!FileAccess.FileExists(path))
{
GD.PrintErr("[ConfigManager] ServerConfig.json not found! Defaulting to 8K.");
return;
}
// Read the file
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
string content = file.GetAsText();
// Parse the JSON
var json = new Json();
var error = json.Parse(content);
if (error != Error.Ok)
{
GD.PrintErr($"[ConfigManager] JSON Parse Error: {json.GetErrorMessage()}");
return;
}
var data = (Dictionary)json.Data;
// Extract the Seed
if (data.ContainsKey("WorldSeed"))
{
WorldSeed = (int)data["WorldSeed"];
}
// Extract the MapProfile and run your Switch/Case logic!
string profile = "8K";
if (data.ContainsKey("MapProfile"))
{
profile = (string)data["MapProfile"];
}
// Extract the TownDensity and run your Switch/Case logic!
string townDensity = "Normal";
if (data.ContainsKey("TownDensity"))
{
townDensity = (string)data["TownDensity"];
}
// Extract the ChunkRadius
if (data.ContainsKey("ChunkRadius")) {
ChunkRadius = (int)data["ChunkRadius"];
}
switch (profile)
{
case "4K":
MapSize = 4096;
CraterRadius = 400f; // Scaled down crater
break;
case "6K":
MapSize = 6144;
CraterRadius = 600f;
break;
case "8K":
MapSize = 8192;
CraterRadius = 800f; // Your original crater size spread over an 8K map
break;
case "10K":
MapSize = 10240;
CraterRadius = 1000f;
break;
default:
GD.PrintErr($"[ConfigManager] Unknown MapProfile '{profile}'. Defaulting to 8K.");
MapSize = 8192;
CraterRadius = 800f;
break;
}
switch (townDensity)
{
case "Sparse":
DensityMultiplier = 0.5f;
break;
case "Normal":
DensityMultiplier = 1.0f;
break;
case "Dense":
DensityMultiplier = 2.0f;
break;
default:
GD.PrintErr($"[ConfigManager] Unknown TownDensity '{townDensity}'. Defaulting to Normal.");
DensityMultiplier = 1.0f;
break;
}
GD.Print($"[ConfigManager] Successfully loaded {profile} Profile. MapSize set to {MapSize}.");
}
}
}

View file

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

14
Core/Scripts/Constants.cs Normal file
View file

@ -0,0 +1,14 @@
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
}
}

View file

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

8
Core/Scripts/Enums.cs Normal file
View file

@ -0,0 +1,8 @@
namespace IslaApocalypse.Core
{
// The master list of shared identifiers for the game
public enum Biome { Ocean, Lake, Beach, Paradise, Tropical, Jungle, Wasteland, Crater, Mountain, Snow }
public enum TownTier { Village, Hub, Capitol, Outpost, IslandLoot, MiniPOI }
public enum MapHalf { Any, Left, Right }
}

View file

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

View file

@ -0,0 +1,55 @@
# 📐 The Math of Marching Cubes (Smooth Voxels)
## Overview
In a standard blocky game like *Minecraft* (Greedy Meshing), if a coordinate has a block, you draw a square. If it doesn't, you draw nothing.
In a smooth voxel game like *7 Days to Die*, we use an algorithm called **Marching Cubes**. Instead of looking at a single block, the algorithm looks at the **8 corners** of an invisible 3D grid cell (a cube) and asks: *"Which of these 8 corners are underground, and which are in the air?"*
Based on the answer, it draws a specific set of triangles *through* the invisible cube to create a smooth surface.
---
## 🔑 Key Concepts
### 1. Density (The "Float" Value)
In smooth voxels, a point in space isn't just "Solid" (1) or "Empty" (0). It has a continuous **Density** value.
* ` 1.0` = Deep underground (Solid Rock).
* ` 0.5` = Slightly underground (Dirt).
* ` 0.0` = The exact surface of the ground.
* `-0.5` = Slightly above ground (Air).
* `-1.0` = High in the sky (Empty Space).
### 2. The Iso-Level (The Threshold)
The Iso-Level is the exact density value where the "skin" of the world is drawn. Usually, this is `0.0`.
* If a corner's density is **greater** than `0.0`, it is "Inside" the terrain.
* If a corner's density is **less** than `0.0`, it is "Outside" in the air.
### 3. The Triangulation Table (The Magic Array)
A cube has 8 corners. Each corner can either be Inside or Outside.
That means there are **2^8 = 256 possible combinations**.
Instead of writing 256 `if/else` statements, the algorithm uses a hardcoded **Lookup Table** (an array of integers).
* *Example:* If corner 0 is inside, but corners 1-7 are outside, the table instantly says: *"Draw a single small triangle slicing off corner 0."*
* *Example:* If corners 0, 1, 2, and 3 are inside (the whole bottom half), the table says: *"Draw a flat quad (two triangles) straight across the middle of the cube."*
### 4. Linear Interpolation (Why it looks Smooth)
If the algorithm just drew triangles exactly halfway between the inside and outside corners, the terrain would still look a bit jagged (like a low-poly PS1 game).
To make it perfectly smooth, we **Interpolate**:
* Corner A has a density of `1.0` (Very solid).
* Corner B has a density of `-0.1` (Just barely in the air).
* Because `-0.1` is much closer to our `0.0` Iso-Level than `1.0` is, the algorithm slides the triangle vertex much closer to Corner B. This creates gentle slopes and sharp cliffs dynamically.
---
## ⚙️ The Execution Loop (What our C# Script will do)
When the Server tells the Client to render a 16x16x64 chunk, the mesher does this:
1. Loops through every X, Y, Z coordinate in the chunk.
2. Checks the 8 corners of the current voxel.
3. Creates an `8-bit integer` (a byte) based on which corners are solid (e.g., `00001111`).
4. Plugs that byte into the **Triangulation Table**.
5. The table spits out a list of edges.
6. The script calculates the exact interpolated point on those edges.
7. It connects those points into triangles and adds them to Godot's `ArrayMesh`.
8. *It Marches* to the next cube and repeats.

View file

@ -0,0 +1,129 @@
using Godot;
using System.IO;
using System.Collections.Generic;
namespace IslaApocalypse.Core
{
// 1. The Data Container
// This holds the unpacked data in RAM so the Server can ask it questions
// without having to re-read the hard drive every single frame.
public struct TownLocation
{
public Vector2 Position;
public TownTier Tier;
}
public class WorldBlueprint
{
public int MapSize;
public float[,] HeightMap;
public Biome[,] BiomeMap;
public List<TownLocation> Towns = new List<TownLocation>();
// All 4 road tiers!
public List<Vector2[]> Highways = new List<Vector2[]>();
public List<Vector2[]> BranchRoads = new List<Vector2[]>();
public List<Vector2[]> RuggedRoads = new List<Vector2[]>();
public List<Vector2[]> TrailRoads = new List<Vector2[]>();
}
// 2. The Parser Utility
public static class MapDataParser
{
public static WorldBlueprint LoadMapData(string seedStr)
{
string filePath = ProjectSettings.GlobalizePath($"user://MapData_Seed_{seedStr}.dat");
if (!File.Exists(filePath))
{
GD.PrintErr($"[MapDataParser] CRITICAL ERROR: Map file not found at {filePath}");
return null;
}
WorldBlueprint blueprint = new WorldBlueprint();
using (FileStream stream = File.OpenRead(filePath))
{
using (BinaryReader reader = new BinaryReader(stream))
{
// 1. Header Validation (CRITICAL: Must read string first to align bytes!)
string header = reader.ReadString();
if (header != "ISLA_V1")
{
GD.PrintErr("[MapDataParser] ERROR: Invalid map version or corrupted file.");
return null;
}
// 2. Read Map Dimensions & Initialize Arrays
blueprint.MapSize = reader.ReadInt32();
blueprint.HeightMap = new float[blueprint.MapSize, blueprint.MapSize];
blueprint.BiomeMap = new Biome[blueprint.MapSize, blueprint.MapSize];
// 3. Read Raw Voxel Data (Height & Biome)
for (int x = 0; x < blueprint.MapSize; x++)
{
for (int y = 0; y < blueprint.MapSize; y++)
{
blueprint.HeightMap[x, y] = reader.ReadSingle();
blueprint.BiomeMap[x, y] = (Biome)reader.ReadInt32();
}
}
// 4. Read Logistical Anchors (Towns & POIs)
int townCount = reader.ReadInt32();
for (int i = 0; i < townCount; i++)
{
TownLocation town = new TownLocation();
town.Position = new Vector2(reader.ReadSingle(), reader.ReadSingle());
town.Tier = (TownTier)reader.ReadInt32();
blueprint.Towns.Add(town);
}
// 5. Read Road Networks
// Highway
int highwayCount = reader.ReadInt32();
for (int i = 0; i < highwayCount; i++)
{
int pathLength = reader.ReadInt32();
Vector2[] path = new Vector2[pathLength];
for (int p = 0; p < pathLength; p++) { path[p] = new Vector2(reader.ReadSingle(), reader.ReadSingle()); }
blueprint.Highways.Add(path);
}
// Branch
int branchCount = reader.ReadInt32();
for (int i = 0; i < branchCount; i++)
{
int pathLength = reader.ReadInt32();
Vector2[] path = new Vector2[pathLength];
for (int p = 0; p < pathLength; p++) { path[p] = new Vector2(reader.ReadSingle(), reader.ReadSingle()); }
blueprint.BranchRoads.Add(path);
}
// Rugged
int ruggedCount = reader.ReadInt32();
for (int i = 0; i < ruggedCount; i++)
{
int pathLength = reader.ReadInt32();
Vector2[] path = new Vector2[pathLength];
for (int p = 0; p < pathLength; p++) { path[p] = new Vector2(reader.ReadSingle(), reader.ReadSingle()); }
blueprint.RuggedRoads.Add(path);
}
// Trails
int trailCount = reader.ReadInt32();
for (int i = 0; i < trailCount; i++)
{
int pathLength = reader.ReadInt32();
Vector2[] path = new Vector2[pathLength];
for (int p = 0; p < pathLength; p++) { path[p] = new Vector2(reader.ReadSingle(), reader.ReadSingle()); }
blueprint.TrailRoads.Add(path);
}
}
}
GD.Print($"[MapDataParser] Successfully loaded Blueprint for Seed {seedStr}. Dimension: {blueprint.MapSize}x{blueprint.MapSize}.");
return blueprint;
}
}
}

View file

@ -0,0 +1 @@
uid://6qednxmubxia

View file

@ -0,0 +1,522 @@
using Godot;
using System;
using System.Collections.Generic;
namespace IslaApocalypse.Core
{
public static class MarchingCubes
{
/// <summary>
/// The Edge Table (256 values).
/// This array uses an 8-bit index (representing the 8 corners of a cube)
/// to look up a 12-bit value. Each bit in the returned value corresponds to
/// one of the 12 edges of the cube, telling the engine if that edge is intersected.
/// </summary>
public static readonly int[] EdgeTable = new int[256] {
0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c,
0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac,
0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c,
0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc,
0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c,
0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc ,
0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0
};
/// <summary>
/// The Triangulation Table (256 rows, 16 columns).
/// Once we know which edges are intersected, this table tells us exactly
/// how to connect those intersections to draw up to 5 triangles.
/// A value of -1 means "stop drawing triangles".
/// </summary>
public static readonly int[,] TriTable = new int[256, 16] {
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1},
{3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1},
{3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1},
{3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1},
{9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1},
{9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},
{2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1},
{8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1},
{9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},
{4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1},
{3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1},
{1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1},
{4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1},
{4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1},
{9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
{5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1},
{2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1},
{9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
{0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},
{2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1},
{10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1},
{4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1},
{5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1},
{5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1},
{9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1},
{0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1},
{1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1},
{10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1},
{8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1},
{2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1},
{7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1},
{9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1},
{2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1},
{11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1},
{9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1},
{5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1},
{11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1},
{11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},
{1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1},
{9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1},
{5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1},
{2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},
{0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},
{5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1},
{6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1},
{3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1},
{6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1},
{5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1},
{1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},
{10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1},
{6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1},
{8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1},
{7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1},
{3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},
{5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1},
{0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1},
{9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1},
{8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1},
{5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1},
{0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1},
{6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1},
{10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1},
{10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1},
{8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1},
{1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1},
{3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1},
{0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1},
{10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1},
{3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1},
{6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1},
{9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1},
{8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1},
{3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1},
{6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1},
{0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1},
{10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1},
{10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1},
{2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1},
{7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1},
{7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1},
{2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1},
{1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1},
{11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1},
{8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1},
{0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1},
{7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
{10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},
{2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},
{6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1},
{7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1},
{2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1},
{1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1},
{10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1},
{10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1},
{0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1},
{7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1},
{6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1},
{8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1},
{9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1},
{6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1},
{4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1},
{10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1},
{8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1},
{0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1},
{1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1},
{8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1},
{10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1},
{4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1},
{10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
{5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},
{11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1},
{9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},
{6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1},
{7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1},
{3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1},
{7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1},
{9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1},
{3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1},
{6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1},
{9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1},
{1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1},
{4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1},
{7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1},
{6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1},
{3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1},
{0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1},
{6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1},
{0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1},
{11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1},
{6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1},
{5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1},
{9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1},
{1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1},
{1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1},
{10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1},
{0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1},
{5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1},
{10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1},
{11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1},
{9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1},
{7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1},
{2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1},
{8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1},
{9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1},
{9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1},
{1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1},
{9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1},
{9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1},
{5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1},
{0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1},
{10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1},
{2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1},
{0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1},
{0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1},
{9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1},
{5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1},
{3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1},
{5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1},
{8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1},
{0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1},
{9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1},
{1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1},
{3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1},
{4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1},
{9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1},
{11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1},
{11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1},
{2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1},
{9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1},
{3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1},
{1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1},
{4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1},
{4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1},
{0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1},
{3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1},
{3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1},
{0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1},
{9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1},
{1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}
};
/// <summary>
/// The local offsets of the 8 corners of a single voxel cube.
/// </summary>
public static readonly Vector3[] CornerOffsets = new Vector3[8] {
new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(1, 0, 1), new Vector3(0, 0, 1),
new Vector3(0, 1, 0), new Vector3(1, 1, 0), new Vector3(1, 1, 1), new Vector3(0, 1, 1)
};
/// <summary>
/// Defines which two corners make up each of the 12 edges.
/// </summary>
public static readonly int[,] EdgeConnections = new int[12, 2] {
{0,1}, {1,2}, {2,3}, {3,0}, // Bottom face edges
{4,5}, {5,6}, {6,7}, {7,4}, // Top face edges
{0,4}, {1,5}, {2,6}, {3,7} // Vertical pillars
};
/// <summary>
/// Maps block IDs to vertex colors for the generated mesh. You can customize this to match your BiomePalette or block types!
/// </summary>
/// <param name="blockID"></param>
/// <returns></returns>
private static Color GetBlockColor(byte blockID)
{
// These map your BiomePalette selections to actual visual colors!
if (blockID == BlockRegistry.SAND) return new Color(0.8f, 0.7f, 0.4f);
if (blockID == BlockRegistry.SNOW) return new Color(0.9f, 0.9f, 0.9f);
if (blockID == BlockRegistry.STONE) return new Color(0.4f, 0.4f, 0.4f);
if (blockID == BlockRegistry.WASTELAND_DIRT) return new Color(0.35f, 0.25f, 0.25f);
if (blockID == BlockRegistry.ASPHALT) return new Color(0.15f, 0.15f, 0.15f);
// Grass variations
if (blockID == BlockRegistry.GRASS_JUNGLE) return new Color(0.1f, 0.4f, 0.1f);
if (blockID == BlockRegistry.GRASS_PARADISE) return new Color(0.4f, 0.8f, 0.4f);
if (blockID == BlockRegistry.GRASS_TROPICAL) return new Color(0.2f, 0.6f, 0.2f);
// Default Dirt
return new Color(0.4f, 0.3f, 0.15f);
}
/// <summary>
/// A deterministic, integer-based key for our Dictionary welder.
/// </summary>
private struct EdgeKey : IEquatable<EdgeKey>
{
public Vector3I P1, P2;
public EdgeKey(Vector3I p1, Vector3I p2)
{
// Always order them from lowest to highest so neighboring chunks generate the exact same key!
if (p1.X < p2.X || (p1.X == p2.X && p1.Y < p2.Y) || (p1.X == p2.X && p1.Y == p2.Y && p1.Z < p2.Z)) {
P1 = p1; P2 = p2;
} else {
P1 = p2; P2 = p1;
}
}
public bool Equals(EdgeKey other) => P1.Equals(other.P1) && P2.Equals(other.P2);
public override int GetHashCode() => HashCode.Combine(P1, P2);
}
public static Vector3 VertexInterp(float isolevel, Vector3 p1, Vector3 p2, float valp1, float valp2)
{
if (Mathf.Abs(isolevel - valp1) < 0.00001f) return p1;
if (Mathf.Abs(isolevel - valp2) < 0.00001f) return p2;
if (Mathf.Abs(valp1 - valp2) < 0.00001f) return p1;
float mu = (isolevel - valp1) / (valp2 - valp1);
return new Vector3(
p1.X + mu * (p2.X - p1.X),
p1.Y + mu * (p2.Y - p1.Y),
p1.Z + mu * (p2.Z - p1.Z)
);
}
/// <summary>
/// Calculates the exact 3D slope (gradient) of the terrain at a specific grid coordinate.
/// </summary>
public static Vector3 GetDensityGradient(float[,,] field, int x, int y, int z)
{
// Clamp to valid array indices so we don't crash when checking the chunk borders
int x0 = Math.Max(0, x - 1), x1 = Math.Min(field.GetLength(0) - 1, x + 1);
int y0 = Math.Max(0, y - 1), y1 = Math.Min(field.GetLength(1) - 1, y + 1);
int z0 = Math.Max(0, z - 1), z1 = Math.Min(field.GetLength(2) - 1, z + 1);
// Calculate the difference in density across the X, Y, and Z axes
float gx = field[x1, y, z] - field[x0, y, z];
float gy = field[x, y1, z] - field[x, y0, z];
float gz = field[x, y, z1] - field[x, y, z0];
return new Vector3(gx, gy, gz).Normalized();
}
public static ArrayMesh GenerateMesh(float[,,] scalarField, byte[,,] blockIDs, float isolevel = 0.0f, float voxelScale = 1.0f)
{
var st = new SurfaceTool();
st.Begin(Mesh.PrimitiveType.Triangles);
st.SetSmoothGroup(1);
int width = scalarField.GetLength(0) - 1;
int height = scalarField.GetLength(1) - 1;
int depth = scalarField.GetLength(2) - 1;
float[] cornerDensities = new float[8];
int[] edgeVertexIndices = new int[12]; // We now store the integer index, not the raw Vector3!
// The bulletproof integer welder
Dictionary<EdgeKey, int> vertexCache = new Dictionary<EdgeKey, int>();
int currentIndex = 0;
int totalIndicesDrawn = 0; // For our diagnostic ratio
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
for (int z = 0; z < depth; z++)
{
cornerDensities[0] = scalarField[x, y, z];
cornerDensities[1] = scalarField[x + 1, y, z];
cornerDensities[2] = scalarField[x + 1, y, z + 1];
cornerDensities[3] = scalarField[x, y, z + 1];
cornerDensities[4] = scalarField[x, y + 1, z];
cornerDensities[5] = scalarField[x + 1, y + 1, z];
cornerDensities[6] = scalarField[x + 1, y + 1, z + 1];
cornerDensities[7] = scalarField[x, y + 1, z + 1];
int cubeIndex = 0;
if (cornerDensities[0] < isolevel) cubeIndex |= 1;
if (cornerDensities[1] < isolevel) cubeIndex |= 2;
if (cornerDensities[2] < isolevel) cubeIndex |= 4;
if (cornerDensities[3] < isolevel) cubeIndex |= 8;
if (cornerDensities[4] < isolevel) cubeIndex |= 16;
if (cornerDensities[5] < isolevel) cubeIndex |= 32;
if (cornerDensities[6] < isolevel) cubeIndex |= 64;
if (cornerDensities[7] < isolevel) cubeIndex |= 128;
if (EdgeTable[cubeIndex] == 0) continue;
Vector3 basePos = new Vector3(x, y, z) * voxelScale;
// Calculate the 12 edges
for (int i = 0; i < 12; i++)
{
if ((EdgeTable[cubeIndex] & (1 << i)) != 0)
{
int cornerA = EdgeConnections[i, 0];
int cornerB = EdgeConnections[i, 1];
// Create the flawless integer key
Vector3I gridPosA = new Vector3I(x + (int)CornerOffsets[cornerA].X, y + (int)CornerOffsets[cornerA].Y, z + (int)CornerOffsets[cornerA].Z);
Vector3I gridPosB = new Vector3I(x + (int)CornerOffsets[cornerB].X, y + (int)CornerOffsets[cornerB].Y, z + (int)CornerOffsets[cornerB].Z);
EdgeKey key = new EdgeKey(gridPosA, gridPosB);
// If this edge hasn't been drawn by a neighbor yet, calculate it and cache it!
if (!vertexCache.TryGetValue(key, out int vertIdx))
{
// 1. Calculate precise position
Vector3 exactPos = basePos + VertexInterp(isolevel, CornerOffsets[cornerA] * voxelScale, CornerOffsets[cornerB] * voxelScale, cornerDensities[cornerA], cornerDensities[cornerB]);
// 2. ANALYTICAL NORMALS: Calculate the exact 3D slope at both corners
Vector3 gradA = GetDensityGradient(scalarField, x + (int)CornerOffsets[cornerA].X, y + (int)CornerOffsets[cornerA].Y, z + (int)CornerOffsets[cornerA].Z);
Vector3 gradB = GetDensityGradient(scalarField, x + (int)CornerOffsets[cornerB].X, y + (int)CornerOffsets[cornerB].Y, z + (int)CornerOffsets[cornerB].Z);
// 3. Interpolate the normal using the exact same fraction used for the position
float valA = cornerDensities[cornerA];
float valB = cornerDensities[cornerB];
float mu = (isolevel - valA) / (valB - valA);
Vector3 exactNormal = (gradA + mu * (gradB - gradA)).Normalized();
// 4. THE COLOR FIX: Grab the BlockID from CornerA and paint the vertex!
// (We just reuse the gridPosA we already declared above the if-statement!)
byte blockID = blockIDs[gridPosA.X, gridPosA.Y, gridPosA.Z];
st.SetColor(GetBlockColor(blockID));
// 5. Feed the perfect normal AND the position to Godot!
// (SetColor and SetNormal MUST be called immediately before AddVertex)
st.SetNormal(exactNormal);
st.AddVertex(exactPos);
// 6. Cache it so neighbors don't redraw it!
vertIdx = currentIndex++;
vertexCache[key] = vertIdx;
}
// Save the index for this specific cube to use in the TriTable
edgeVertexIndices[i] = vertIdx;
}
}
// Connect the cached indices!
for (int i = 0; TriTable[cubeIndex, i] != -1; i += 3)
{
st.AddIndex(edgeVertexIndices[TriTable[cubeIndex, i + 2]]);
st.AddIndex(edgeVertexIndices[TriTable[cubeIndex, i + 1]]);
st.AddIndex(edgeVertexIndices[TriTable[cubeIndex, i]]);
totalIndicesDrawn += 3;
}
}
}
}
// Diagnostic Print
// GD.Print($"[MarchingCubes] Vertices: {currentIndex}, Indices: {totalIndicesDrawn}, Ratio: {(float)totalIndicesDrawn / Math.Max(1, currentIndex):F2}");
return st.Commit();
}
}
}

View file

@ -0,0 +1 @@
uid://4p2co3ctt0is

46
Core/Scripts/README.md Normal file
View file

@ -0,0 +1,46 @@
# 📜 Core Scripts Directory - Isla Apocalypse
## Overview
The `/Core/Scripts` directory is the dedicated code repository for all pure C# data structures, universal enumerations, and static mathematical utilities. Everything in this folder is compiled into the shared assembly used by both the authoritative `/Server` and the rendering `/Client`.
**Core Philosophy: "Pure Data, Zero State."**
Scripts in this folder define *what* things are and *how* to calculate them, but they never remember *current* game events. They do not track who is online, what chunks are loaded, or what time of day it is. They are stateless, universal blueprints.
---
## 🧩 Current Systems (Phase 2)
### 1. The Voxel Palette System
* **`BlockData.cs`**: The fundamental `struct` defining the properties of a single voxel (ID, Name, IsSolid, BaseColor).
* **`BlockRegistry.cs`**: The static master dictionary. Contains the hardcoded IDs for every block in the game (Air, Bedrock, Dirt, Asphalt) and provides a safe lookup method (`GetBlock`) for both the mesher and the physics engine.
* **`BiomePalette.cs`**: The translation layer between 2D and 3D. Contains the logic that dictates the vertical stacking of blocks based on biome and depth (e.g., ensuring mountains have stone beneath snow, and roads are topped with asphalt).
### 2. Universal Identifiers
* **`Enums.cs`**: The master repository for global enums (`Biome`, `TownTier`, `MapHalf`). By keeping these here, the offline Map Generator, the Server, and the Client are guaranteed to use the exact same integer values for biomes and POIs.
---
## 🚀 Future Roadmap & Planned Additions (Phase 2 & 3)
As we build the 3D Chunk Manager and multiplayer networking, this folder will expand to include:
### 3. The Blueprint Parser
* **`MapDataParser.cs`** *(Next Step)*: A static utility to open and decode the `MapData_Seed_[X].dat` binary file into usable C# arrays for the Server to ingest.
### 4. Chunk Data Structures
* **`ChunkData.cs`**: The raw 3D array (`byte[,,]`) that holds the voxel IDs for a specific 16x16x64 area.
* *Note:* This structure will include the `IsPlayerProtected` boolean flag to support the "Land Claim" delta-backup system, preventing player bases from being wiped out by chunk corruption.
### 5. Math & Coordinate Utilities
* **`VoxelMath.cs`**: Static helper functions for translating massive 3D World Coordinates into local Chunk Coordinates (e.g., finding out which specific chunk file to load when a player walks to X: 5000, Z: -200).
### 6. Network Packet Definitions
* Structs that define the exact byte layout of multiplayer messages (e.g., `PlayerDigPacket`, `ChatPacket`) to ensure precise Server/Client synchronization.
---
## ⚠️ Directory Rules & Best Practices
1. **No Godot Node Inheritance:** Scripts here should rarely, if ever, inherit from `Node` or `Node3D`. They are pure C# classes, structs, or static utilities.
2. **No `_Process` or `_Ready`:** Because these are not attached to active objects in the game world, they do not use Godot's frame-by-frame loop functions.
3. **Strictly Independent:** A script in `/Core/Scripts` cannot reference anything in `/Client` or `/Server`. The dependency flows one way: the Client and Server look *in* to the Core; the Core never looks *out*.