islaApocalypse/Core/Scripts/BlueprintFormat.cs
beezm 3bf822dac6 feat: WBID/WBTB/WSRF blueprint sections + parser + harness extension (terrain-water task 03)
Three new v2 tagged sections carrying the water-bodies stage output:
WBID (MapSize^2 u16 body ids; 0 none, 1 ocean, 2..N lakes), WBTB
(count-prefixed 20-byte records: id, type, provisional salinity,
transitional flat surface level, pixel count, centroid), WSRF
(MapSize^2 u16 quantized levels; 0 = no-water sentinel, 1 + L*32768
encoding, ~7.7 mm world resolution). Written only when the blueprint
carries water; absent on legacy re-encodes. Parser registers all
three (length + type checks) into new WorldBlueprint members —
consumed by nothing at runtime. Round-trip harness now compares the
water sections whenever the source carries them; v1-source baseline
stays green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 01:31:50 -04:00

74 lines
3.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

namespace IslaApocalypse.Core
{
/// <summary>
/// The single registry of constants for the v2 blueprint container — magic, version,
/// section tags, validation bounds, and the sentinel values used when a v2 file is
/// re-encoded from a v1 source that cannot supply real generation parameters.
///
/// The byte-accurate layout lives in Core/Scripts/BLUEPRINT_FORMAT.md. Writer:
/// BlueprintWriter.WriteV2. Reader: MapDataParser (v2 branch). Tags are FourCC codes
/// stored as little-endian u32, so the raw bytes on disk read as ASCII ("PRMS", "HGTS",
/// ...) in a hex dump.
/// </summary>
public static class BlueprintFormat
{
// "ISLA" as raw bytes 'I','S','L','A' == little-endian u32 0x414C5349.
// Deliberately NOT a length-prefixed .NET string: the v1 header starts with the
// 7-bit length prefix 0x07, so the first byte alone (0x49 vs 0x07) identifies the
// format and the reader can dispatch without ambiguity.
public const uint MAGIC = 0x414C5349;
public const uint VERSION = 2;
// Section tags (FourCC, little-endian). Reader rule: known tag -> parse,
// unknown tag -> skip by length and continue. Never reuse a retired tag value.
public const uint TAG_PARAMS = 0x534D5250; // "PRMS"
public const uint TAG_HEIGHTS = 0x53544748; // "HGTS"
public const uint TAG_BIOMES = 0x4D4F4942; // "BIOM"
public const uint TAG_TOWNS = 0x4E574F54; // "TOWN"
public const uint TAG_ROADS_HIGHWAY = 0x57484452; // "RDHW"
public const uint TAG_ROADS_BRANCH = 0x52424452; // "RDBR"
public const uint TAG_ROADS_RUGGED = 0x47524452; // "RDRG"
public const uint TAG_ROADS_TRAIL = 0x4C544452; // "RDTL"
public const uint TAG_WATER_BODY_IDS = 0x44494257; // "WBID"
public const uint TAG_WATER_BODY_TABLE = 0x42544257; // "WBTB"
public const uint TAG_WATER_SURFACE = 0x46525357; // "WSRF"
// WSRF quantization: u16, 0 reserved as the no-water sentinel. A real level L
// (raw blueprint height units) encodes as 1 + round(L × 32768), so a genuine
// level can never encode to 0. Decodes back via (q 1) / 32768. Covers
// [0 .. ~1.99997] raw at 1/32768 raw resolution ≈ 7.7 mm of world height
// (1 raw unit = 251 m) — far below the 1 m voxel.
public const float WSRF_SCALE = 1f / 32768f;
public static ushort EncodeWaterLevel(float level)
{
return (ushort)Godot.Mathf.Clamp(1 + Godot.Mathf.RoundToInt(level * 32768f), 1, ushort.MaxValue);
}
public static float DecodeWaterLevel(ushort quantized)
{
return (quantized - 1) * WSRF_SCALE;
}
// MapSize sanity bounds, checked before any allocation on read.
public const int MIN_MAP_SIZE = 256;
public const int MAX_MAP_SIZE = 32768;
// Sentinels written into the params section when the source blueprint came from a
// v1 file (a re-encode cannot know the original generation inputs). WorldSeed 0 is
// also the config "randomize" sentinel, which no real resolved seed ever is.
public const int SENTINEL_WORLD_SEED = 0;
public const float SENTINEL_CRATER_RADIUS = -1f;
public const float SENTINEL_DENSITY_MULTIPLIER = -1f;
public const float SENTINEL_IMPACT_CENTER = -1f; // both components
/// <summary>Renders a tag as its 4 ASCII characters, for log messages.</summary>
public static string TagToString(uint tag)
{
return new string(new[] {
(char)(tag & 0xFF), (char)((tag >> 8) & 0xFF),
(char)((tag >> 16) & 0xFF), (char)((tag >> 24) & 0xFF)
});
}
}
}