islaApocalypse/Core/Scripts/BlueprintFormat.cs
beezm 3b6d166458 revert: the D8 drainage incision, whole (terrain-water task 10)
The task-10 draft's PASS B shipped and produced the canonical grid
artifact: thousands of straight, disconnected, pooling scratches. Per-
cell steepest descent on a regular grid can only route along its eight
neighbour headings, so at map scale the "channels" read as hatching,
not drainage. Reverted entirely rather than tuned -- no K, p or mask
setting fixes a directional basis. Rivers and erosion move to Phase C
as a hydraulic-erosion pass over FINAL terrain.

Removed: FlowAccumulation / IncisionWeight / EdgeBlend and the INC_*,
SEA_CLAMP, SHELF_INC_WEIGHT and CRATER_* constants; RunIncisionPass and
the pass-2b call; the six incision fields from TDTL (writer, parser,
harness). KEPT, untouched and developer-approved: pass A, the shelf
micro-relief skin, and its ShelfReliefAmp dial.

The crater carve folds back into the single pass-2 loop it came from,
carving two LOCALS written once each. That is what the code did before
the draft split it out, and it makes the aliased double-carve that the
split introduced (fix 1b98fb5) structurally impossible rather than
merely fixed -- there is no longer a read-modify-write to get wrong.

TDTL bodies are now versioned (BlueprintFormat.TDTL_VERSION = 2) and the
parser skips a body version it does not know instead of misreading the
longer v1 layout into plausible nonsense. The only v1 payloads that
exist are in the reverted batch's own tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 20:30:44 -04:00

83 lines
3.9 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"
public const uint TAG_TERRAIN_CURVE = 0x56524354; // "TCRV"
public const uint TAG_TERRAIN_DETAIL = 0x4C544454; // "TDTL"
// TDTL body version. 1 = shelf micro-relief + D8 drainage incision (the
// task-10 draft; the incision was reverted, and the only v1 payloads in
// existence are in that batch's tree). 2 = the current body. A reader that
// meets an unrecognised body version skips it rather than misreading a
// differently shaped payload into plausible-looking nonsense.
public const ushort TDTL_VERSION = 2;
// 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)
});
}
}
}