feat: 0_height hillshade snapshot + TCRV blueprint section + harness (terrain-water task 05)
0_height: hypsometric storm-ladder tint x Lambert NW hillshade over the curved heights, captured after topography in both curve modes — relief is finally visible. Renumbers nothing. TCRV (50 B, emitted when the curve is on): curve version + input knots + output bands, so blueprints are self-describing about the map their heights went through (H7 spirit). Parsed into WorldBlueprint.TerrainCurve (null = raw legacy profile); server treats it as metadata. Round-trip harness compares it when present. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
67868638be
commit
e7de292b3d
5 changed files with 142 additions and 1 deletions
|
|
@ -32,6 +32,7 @@ namespace IslaApocalypse.Core
|
|||
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"
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ namespace IslaApocalypse.Core
|
|||
writer.Write(BlueprintFormat.VERSION);
|
||||
|
||||
WriteSection(writer, BlueprintFormat.TAG_PARAMS, w => WriteParams(w, bp));
|
||||
if (bp.TerrainCurve != null)
|
||||
WriteSection(writer, BlueprintFormat.TAG_TERRAIN_CURVE, w => WriteTerrainCurve(w, bp.TerrainCurve));
|
||||
WriteSection(writer, BlueprintFormat.TAG_HEIGHTS, w => WriteHeights(w, bp));
|
||||
WriteSection(writer, BlueprintFormat.TAG_BIOMES, w => WriteBiomes(w, bp));
|
||||
|
||||
|
|
@ -122,6 +124,16 @@ namespace IslaApocalypse.Core
|
|||
}
|
||||
}
|
||||
|
||||
private static void WriteTerrainCurve(BinaryWriter writer, TerrainCurveInfo c)
|
||||
{
|
||||
writer.Write(c.Version);
|
||||
writer.Write(c.T1); writer.Write(c.T2); writer.Write(c.T3); writer.Write(c.T4);
|
||||
writer.Write(c.HMaxCal);
|
||||
writer.Write(c.Sea); writer.Write(c.OrangeCeil); writer.Write(c.RedCeil);
|
||||
writer.Write(c.PlateauLo); writer.Write(c.PlateauHi); writer.Write(c.PeakCap);
|
||||
writer.Write(c.TailSlope);
|
||||
}
|
||||
|
||||
private static void WriteWaterBodyIds(BinaryWriter writer, WorldBlueprint bp)
|
||||
{
|
||||
int n = bp.MapSize;
|
||||
|
|
|
|||
|
|
@ -58,6 +58,20 @@ namespace IslaApocalypse.Core
|
|||
public Vector2 Centroid; // map pixels
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The height-redistribution curve that shaped this blueprint's heights (v2 TCRV
|
||||
/// section, terrain-water task 05). Null when the blueprint was generated with the
|
||||
/// curve off (or predates it) — heights are then the raw legacy profile. Pure
|
||||
/// metadata: the server never re-applies the curve; exported heights are already
|
||||
/// curved.
|
||||
/// </summary>
|
||||
public class TerrainCurveInfo
|
||||
{
|
||||
public ushort Version;
|
||||
public float T1, T2, T3, T4, HMaxCal; // input knots
|
||||
public float Sea, OrangeCeil, RedCeil, PlateauLo, PlateauHi, PeakCap, TailSlope; // output bands
|
||||
}
|
||||
|
||||
public class WorldBlueprint
|
||||
{
|
||||
public int MapSize;
|
||||
|
|
@ -82,6 +96,9 @@ namespace IslaApocalypse.Core
|
|||
public ushort[,] WaterBodyIds; // 0 = no water, 1 = ocean, 2..N = lakes
|
||||
public List<WaterBodyInfo> WaterBodies = new List<WaterBodyInfo>();
|
||||
public ushort[,] WaterSurfaceQ; // quantized levels — BlueprintFormat.DecodeWaterLevel
|
||||
|
||||
// The curve that shaped HeightMap (v2 TCRV section); null = raw legacy profile.
|
||||
public TerrainCurveInfo TerrainCurve;
|
||||
}
|
||||
|
||||
// 2. The Parser Utility
|
||||
|
|
@ -204,6 +221,7 @@ namespace IslaApocalypse.Core
|
|||
else if (tag == BlueprintFormat.TAG_WATER_BODY_IDS) sectionOk = ParseWaterGrid(reader, blueprint, payloadLength, isSurface: false);
|
||||
else if (tag == BlueprintFormat.TAG_WATER_SURFACE) sectionOk = ParseWaterGrid(reader, blueprint, payloadLength, isSurface: true);
|
||||
else if (tag == BlueprintFormat.TAG_WATER_BODY_TABLE) sectionOk = ParseWaterBodyTable(reader, blueprint);
|
||||
else if (tag == BlueprintFormat.TAG_TERRAIN_CURVE) sectionOk = ParseTerrainCurve(reader, blueprint);
|
||||
else
|
||||
{
|
||||
// The property the redesign exists to buy: future sections (water,
|
||||
|
|
@ -366,6 +384,21 @@ namespace IslaApocalypse.Core
|
|||
return true;
|
||||
}
|
||||
|
||||
private static bool ParseTerrainCurve(BinaryReader reader, WorldBlueprint blueprint)
|
||||
{
|
||||
var c = new TerrainCurveInfo();
|
||||
c.Version = reader.ReadUInt16();
|
||||
c.T1 = reader.ReadSingle(); c.T2 = reader.ReadSingle();
|
||||
c.T3 = reader.ReadSingle(); c.T4 = reader.ReadSingle();
|
||||
c.HMaxCal = reader.ReadSingle();
|
||||
c.Sea = reader.ReadSingle(); c.OrangeCeil = reader.ReadSingle();
|
||||
c.RedCeil = reader.ReadSingle(); c.PlateauLo = reader.ReadSingle();
|
||||
c.PlateauHi = reader.ReadSingle(); c.PeakCap = reader.ReadSingle();
|
||||
c.TailSlope = reader.ReadSingle();
|
||||
blueprint.TerrainCurve = c;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ParseRoadTier(BinaryReader reader, List<Vector2[]> into)
|
||||
{
|
||||
int pathCount = reader.ReadInt32();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue