Aetheria/Assets/Scripts/ServerShared/ZoneData.cs
Meta Vens 3a5b3b0bab Gravity grid-lowering: the drawn height is one Gravity Field pass over the zone's wells; gravity brushes and brush inputs deleted
Zone.WellReference owns each body's reference zero (GetHeight uses it). Gravity.cginc mirrors GravityField.Potential;
Brushes/Gravity Field draws depth = -h on layer 9 from a StructuredBuffer that GravityLowering.Lower fills from the
zone each frame; GravityLowering.CreateField makes the one quad. GravityFieldParityVerify renders it and compares every
texel with -Zone.GetHeight (max error 0.0059 u), the gradient with GetForce (0.03 degrees) and the wiring.

Deleted: the Gravity Well children of Planet, Gas Giant, Sun and PlanetOutpost prefabs, Wormhole 1's three gravity
children, their overrides in Main Menu and TestScene, five wormhole/wave materials and two brush shaders,
Planet.GravityWellDepth/Radius, BodyData keys 6-7, PlanetSettings.GravityDepth/GravityRadius (and the Settings.asset
keys). Surface Displacement culls layer 9 only. Headless build and tests not yet run at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:11:25 +02:00

239 lines
8.6 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.

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using GameCult.Caching;
using MessagePack;
using Newtonsoft.Json;
using UniRx;
using CultMath;
using static CultMath.math;
using float2 = CultMath.float2;
using float3 = CultMath.float3;
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class ZonePack
{
[JsonProperty("planets"), Key(0)]
public List<CultRecordRef<BodyData>> Planets = new List<CultRecordRef<BodyData>>();
[JsonProperty("orbits"), Key(1)]
public List<CultRecordRef<OrbitData>> Orbits = new List<CultRecordRef<OrbitData>>();
[JsonProperty("entities"), Key(2)]
public List<EntityPack> Entities = new List<EntityPack>();
[JsonProperty("radius"), Key(3)]
public float Radius = 2000;
[JsonProperty("mass"), Key(4)]
public float Mass = 10000;
[JsonProperty("time"), Key(5)]
public double Time;
// Cut 2 (docs/mining-cut.md): chunk wear (damage taken, broken-until time), keyed by chunk. Nullable, not
// defaulted to an empty list: an older record has no key 6 at all, and MessagePack fills a missing trailing
// key with nil, which a reference-typed field reads back as null. ARunWithoutKeySixLoadsWhole pins that a
// record from before this key existed loads with no wear, rather than throwing or silently reading a
// default instance.
[JsonProperty("chunkWear"), Key(6)]
public List<ChunkWearPack> ChunkWear;
// True once this zone's boss has been spawned. The dead boss leaves no entity, so this is the one fact that tells a
// slain boss from one that never existed; RunGoal.ExitOpen reads it. Written only by ZoneGenerator, carried by PackZone.
[JsonProperty("bossSpawned"), Key(7)]
public bool BossSpawned;
}
// Cut 2 (docs/mining-cut.md): one packed wear entry for one chunk. "Field" names the body that owns the chunk
// (an asteroid belt today; the operator's ruling generalizes chunks to other kinds of debris field later), not
// "belt" -- the chunk surface itself must not bake in the one live field kind.
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class ChunkWearPack
{
[JsonProperty("field"), Key(0)]
public CultRecordKey Field;
[JsonProperty("index"), Key(1)]
public int Index;
[JsonProperty("damage"), Key(2)]
public float Damage;
[JsonProperty("brokenUntil"), Key(3)]
public double? BrokenUntil;
}
[JsonObject(MemberSerialization.OptIn)]
public abstract class BodyData
{
[CultName, JsonProperty("name"), Key(1)]
public string Name = "";
[JsonProperty("orbit"), Key(2)]
public CultRecordRef<OrbitData> Orbit;
[JsonProperty("mass"), Key(3)]
public float Mass = 0;
[CultReference(typeof(SimpleCommodityData), many: true), JsonProperty("resources"), Key(4)]
public Dictionary<CultRecordRef<SimpleCommodityData>, float> Resources = new Dictionary<CultRecordRef<SimpleCommodityData>, float>();
[JsonProperty("bodyRadiusMul")] [Key(5)]
public float BodyRadiusMultiplier = 1;
}
[CultDocument("aetheria.planetdata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class PlanetData : BodyData
{
}
[CultDocument("aetheria.asteroidbeltdata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class AsteroidBeltData : BodyData
{
[JsonProperty("asteroids"), Key(9)]
public Asteroid[] Asteroids;
// Mining Cut 3 (docs/mining-cut-refresh.md, Q16 B): the belt's field kind, chosen once and saved. Unset on a
// belt from before this key existed (MessagePack leaves a missing trailing key at its default); Zone assigns
// it on first load through FieldKinds.Ensure, the same function generation uses.
[JsonProperty("kind"), Key(10)]
public CultRecordRef<FieldKindData> Kind;
}
// Mining Cut 3 (docs/mining-cut-refresh.md, Q11): a kind of debris field, an authored catalog record. A chunk's
// reflectivity comes from its field's kind; Cut 5 adds the yield table.
[CultDocument("aetheria.fieldkinddata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class FieldKindData
{
[CultName, JsonProperty("name"), Key(0)]
public string Name = "";
// Reflectivity per schematic cell of chunk area: a chunk reflects CrossSection × its area in cells × the light
// on it (Zone.ChunkVisibility), the rule Reflector applies to a hull.
[JsonProperty("crossSection"), Key(1)]
public float CrossSection;
// How often generation picks this kind, relative to the others (FieldKinds.Assign). Zero never picks it.
[JsonProperty("generationWeight"), Key(2)]
public float GenerationWeight = 1;
}
// Mining Cut 3 (Q16 B): the one owner of which kind a belt is.
public static class FieldKinds
{
// A weighted pick among the catalog's kinds, by a stable hash of the belt's key: the same belt and the same
// catalog always give the same kind. Kinds are ordered by key so the pick does not depend on store order.
// Unset when the catalog has no kind with positive weight.
public static CultRecordRef<FieldKindData> Assign(CultRecordKey field, CultCache catalog)
{
var kinds = catalog.GetAll<FieldKindData>()
.Where(kind => kind.GenerationWeight > 0f)
.Select(kind => (Key: catalog.RefOf(kind).Key, Weight: kind.GenerationWeight))
.OrderBy(kind => kind.Key.Value, StringComparer.Ordinal)
.ToArray();
if (kinds.Length == 0) return default;
var pick = field.Value.StableHash() / 4294967296.0 * kinds.Sum(kind => (double) kind.Weight);
foreach (var kind in kinds)
{
if (pick < kind.Weight) return new CultRecordRef<FieldKindData>(kind.Key);
pick -= kind.Weight;
}
return new CultRecordRef<FieldKindData>(kinds[kinds.Length - 1].Key);
}
// Gives a belt with no kind its kind and saves it; a belt that has one keeps it, whatever the catalog now says.
// Generation (ZoneGenerator) and first load (Zone) both call this, so there is one assignment rule.
public static void Ensure(CultRecordKey field, AsteroidBeltData belt, CultCache cache)
{
if (belt.Kind.IsSet()) return;
belt.Kind = Assign(field, cache);
if (belt.Kind.IsSet()) cache.Upsert(belt);
}
}
[CultDocument("aetheria.gasgiantdata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class GasGiantData : BodyData
{
[JsonProperty("firstOffsetDomainRotationSpeed"), Key(9)]
public float FirstOffsetDomainRotationSpeed = 1;
[JsonProperty("firstOffsetRotationSpeed"), Key(10)]
public float FirstOffsetRotationSpeed = 1;
[JsonProperty("secondOffsetDomainRotationSpeed"), Key(11)]
public float SecondOffsetDomainRotationSpeed = 1;
[JsonProperty("secondOffsetRotationSpeed"), Key(12)]
public float SecondOffsetRotationSpeed = 1;
[JsonProperty("albedoRotationSpeed"), Key(13)]
public float AlbedoRotationSpeed = 1;
[JsonProperty("materialOverrides"), Key(18)]
public List<string> MaterialOverrides = new List<string>();
[JsonProperty("colors"), Key(19)]
public float4[] Colors = new float4[0];
}
[CultDocument("aetheria.sundata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class SunData : GasGiantData
{
[JsonProperty("lightColor"), Key(20)]
public float3 LightColor = float3.zero;
[JsonProperty("fogTintColor"), Key(21)]
public float3 FogTintColor = float3.zero;
[JsonProperty("lightRadiusMul")] [Key(22)]
public float LightRadiusMultiplier = 1;
}
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class Asteroid
{
[JsonProperty("distance"), Key(0)]
public float Distance;
[JsonProperty("phase"), Key(1)]
public float Phase;
[JsonProperty("size"), Key(2)]
public float Size;
[JsonProperty("rotationSpeed"), Key(3)]
public float RotationSpeed;
}
[CultDocument("aetheria.orbitdata", "1"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class OrbitData
{
[JsonProperty("parent"), Key(1)]
public CultRecordRef<OrbitData> Parent;
[JsonProperty("distance"), Key(2)]
public float Distance;
[JsonProperty("phase"), Key(3)]
public float Phase;
[JsonProperty("phase"), Key(4)]
public float2 FixedPosition = float2.zero;
// [JsonProperty("period"), Key(4)]
// public float Period;
public static float2 Evaluate(float phase)
{
phase *= PI * 2;
return new float2(cos(phase), sin(phase));
}
}