Vapour clouds: Zone owns them, a VapourDump behaviour vents one (union 42)

Zone.Vent is the one adder, StepClouds the one remover, Zone.Obscuration the
one reader: the product of (1 - opacity) over clouds whose disc the segment
touches (closest point of the segment to the centre within Radius), 1f when
none. Derived from live clouds at each call, never cached. VapourDump vents
once on its first run, drifting with its venter. Nothing reads Obscuration
yet (Sensor.Gain takes it in the next commit); this commit builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-09 20:22:00 +02:00
commit 6dcf33d11c
7 changed files with 426 additions and 0 deletions

View file

@ -20,6 +20,10 @@ public class Zone
// Laid mines, in lay order. Zone owns their existence: Lay is the one adder, the mine step the one remover,
// and every removal is a blast. Runtime state, never saved.
public ReactiveCollection<Mine> Mines = new ReactiveCollection<Mine>();
// Vented vapour clouds, in vent order. Zone owns their existence: Vent is the one adder, StepClouds the one
// remover, and Obscuration the one reader that detection uses. Runtime state, never saved.
public ReactiveCollection<VapourCloud> Clouds = new ReactiveCollection<VapourCloud>();
public Dictionary<CultRecordKey, BodyData> Planets = new Dictionary<CultRecordKey, BodyData>();
public Dictionary<CultRecordKey, Planet> PlanetInstances = new Dictionary<CultRecordKey, Planet>();
@ -225,6 +229,7 @@ public class Zone
foreach (var entity in Entities.ToArray()) entity.Update(deltaTime);
StepMines(deltaTime);
StepClouds(deltaTime);
// Cut 3: after every entity has had its chance to fire this tick, age and resolve the shots that
// firing queued. A shot fired this tick with a flight time shorter than CommitHorizon commits and
@ -238,6 +243,39 @@ public class Zone
Mines.Add(mine);
}
public void Vent(VapourCloud cloud)
{
cloud.Id = NextBodyId();
cloud.VentedAt = _time;
Clouds.Add(cloud);
}
// The fraction of a sight line's signal that survives the clouds between `a` and `b`: the product of
// (1 - opacity now) over every cloud whose disc the segment touches, so 1f when none does. Derived from the
// live clouds at each call and stored nowhere; Sensor.Gain is where it enters detection.
public float Obscuration(float2 a, float2 b)
{
var through = 1f;
for (var i = 0; i < Clouds.Count; i++)
{
var cloud = Clouds[i];
if (cloud.Touches(a, b)) through *= 1f - cloud.OpacityAt(_time);
}
return through;
}
// After StepMines: each cloud drifts as a KinematicBody on the zone's force, as a mine does, and is removed
// once its lifetime has run out. Nothing else removes a cloud.
private void StepClouds(float deltaTime)
{
var settings = _itemManager.GameplaySettings;
foreach (var cloud in Clouds.ToArray())
{
cloud.Body.Step(deltaTime, GetForce(cloud.Body.Position), settings);
if (cloud.VentedAt + cloud.Lifetime <= _time) Clouds.Remove(cloud);
}
}
// After the entity loop and before FireControl.Step, in lay order: each mine drifts as a KinematicBody, is
// triggered by the first eligible hull its armed disc touches, and detonates through FireControl.Detonate at
// its trigger plus fuse delay or at its lifetime, whatever its layer does (ruling mine-owner-leash). The