Contacts: hull contact is between ships only; orbitals take no part
Step pairs OfType<Ship>; stations and turrets sit on the orbital layer above the ship plane (ruling aetheria-gravity:orbitals-above-floaters-below). Tests: a ship flies through a station and a turret; two ships still bounce beside an orbital. Not yet compile-verified (verified once at the cut's head). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5a788833f0
commit
624d5420e9
2 changed files with 58 additions and 11 deletions
|
|
@ -7,8 +7,9 @@ using CultMath;
|
|||
using static CultMath.math;
|
||||
using float2 = CultMath.float2;
|
||||
|
||||
// A hull touching a hull (aetheria-release cut sim-contact; ruling unity-presents-only-r3): the simulation owns
|
||||
// contact between entities. Each hull is a disc about its centre of mass that bounds every occupied cell, and
|
||||
// A ship touching a ship (aetheria-release cut sim-contact; ruling unity-presents-only-r3): the simulation owns
|
||||
// contact between ships. Orbital entities (stations, turrets) sit on the orbital layer above the ship plane and floaters
|
||||
// below it, so neither touches a ship (ruling aetheria-gravity:orbitals-above-floaters-below). Each hull is a disc about its centre of mass that bounds every occupied cell, and
|
||||
// two discs that touch while closing along the line between their centres take the normal impulse
|
||||
// j = -(1 + e)(v_rel . n) / (1/m1 + 1/m2) (ruling ship-bounce-restitution-setting): momentum is conserved, the
|
||||
// result is frame-independent, and a pair that has separated is not bounced again. e is
|
||||
|
|
@ -47,7 +48,7 @@ public static class Contacts
|
|||
return far * e.ItemManager.GameplaySettings.SchematicCellSize;
|
||||
}
|
||||
|
||||
// For each unordered pair of the zone's entities, in list order: if their discs touch and are closing along
|
||||
// For each unordered pair of the zone's ships, in list order: if their discs touch and are closing along
|
||||
// the normal, the impulse moves each one's velocity along the normal only (the tangent components are
|
||||
// untouched), and the contact is published. Pairs resolve one after another in list order, each from the
|
||||
// velocities the earlier pairs left, so a body touching two others in one tick depends on that order; the
|
||||
|
|
@ -55,7 +56,7 @@ public static class Contacts
|
|||
// are not hulls in the zone. No positional push-out.
|
||||
public static void Step(Zone zone)
|
||||
{
|
||||
var entities = zone.Entities.Where(e => e.Active && e.Parent == null).ToArray();
|
||||
var entities = zone.Entities.OfType<Ship>().Where(e => e.Active && e.Parent == null).ToArray();
|
||||
var radii = entities.Select(Radius).ToArray();
|
||||
for (var i = 0; i < entities.Length; i++)
|
||||
for (var j = i + 1; j < entities.Length; j++)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ using int2 = CultMath.int2;
|
|||
|
||||
// Hull contact in the sim (aetheria-release cut sim-contact; rulings unity-presents-only-r3 and
|
||||
// ship-bounce-restitution-setting): Zone.Update steps Contacts, which applies the restitution impulse along the
|
||||
// normal of two touching, closing hull discs and publishes the contact. Every test
|
||||
// normal of two touching, closing ship discs and publishes the contact. Contact is ship on ship: orbital entities
|
||||
// sit above the ship plane and take no part in it (ruling aetheria-gravity:orbitals-above-floaters-below). Every test
|
||||
// drives Zone.Update, the path the game takes. SchematicCellSize is 2, so a solid 5x5 hull has a radius of
|
||||
// 2.5 * sqrt(2) * 2 = 7.07 and two such hulls ten apart touch.
|
||||
public sealed class ContactTests : IDisposable
|
||||
|
|
@ -70,6 +71,17 @@ public sealed class ContactTests : IDisposable
|
|||
return ship;
|
||||
}
|
||||
|
||||
// A station or turret: an orbital entity with no orbit, so it stays where it is put.
|
||||
public OrbitalEntity AddOrbital(string hull, float3 at, float2 velocity)
|
||||
{
|
||||
var hullRef = Items.ItemData.RefOf<ItemData>(Items.ItemData.GetByName<HullData>(hull));
|
||||
var orbital = new OrbitalEntity(Items, Zone, new EquippableItem { Data = hullRef, Durability = 1000000, Lot = Lot++ }, default, new EntitySettings());
|
||||
Zone.Admit(orbital, piloted: false);
|
||||
orbital.Position = at;
|
||||
orbital.Velocity = velocity;
|
||||
return orbital;
|
||||
}
|
||||
|
||||
public void Step() => Zone.Update(.01f);
|
||||
}
|
||||
|
||||
|
|
@ -339,21 +351,21 @@ public sealed class ContactTests : IDisposable
|
|||
Assert.Empty(lab.Events);
|
||||
}
|
||||
|
||||
// A ship against a 1000x heavier body: the heavy body takes (1 + e) m / (m + M) of the ship's closing speed
|
||||
// A ship against a 1000x heavier ship: the heavy ship takes (1 + e) m / (m + M) of the ship's closing speed
|
||||
// and the ship rebounds with the rest of its momentum.
|
||||
[Fact]
|
||||
public void AShipAgainstAStationRebounds()
|
||||
public void AShipAgainstAHeavyHullRebounds()
|
||||
{
|
||||
var lab = Build(.5f);
|
||||
var ship = lab.Add("Solid", float3(0, 0, 0), float2(10, 0));
|
||||
var station = lab.Add("Heavy", float3(10, 0, 0), float2(0, 0));
|
||||
var heavy = lab.Add("Heavy", float3(10, 0, 0), float2(0, 0));
|
||||
Assert.Equal(1000f, ship.Mass);
|
||||
Assert.Equal(1000000f, station.Mass);
|
||||
Assert.Equal(1000000f, heavy.Mass);
|
||||
lab.Step();
|
||||
|
||||
var n = NormalOf(ship, station);
|
||||
var n = NormalOf(ship, heavy);
|
||||
var closing = 10f * n.x;
|
||||
Assert.Equal(1.5f * closing * 1000f / 1001000f, dot(n, station.Velocity), 4);
|
||||
Assert.Equal(1.5f * closing * 1000f / 1001000f, dot(n, heavy.Velocity), 4);
|
||||
Assert.Equal(closing - 1.5f * closing * 1000000f / 1001000f, dot(n, ship.Velocity), 3);
|
||||
}
|
||||
|
||||
|
|
@ -371,6 +383,40 @@ public sealed class ContactTests : IDisposable
|
|||
Assert.Equal(-10f, b.Velocity.x, 4);
|
||||
}
|
||||
|
||||
// Orbital entities live above the ship plane (ruling orbitals-above-floaters-below): a ship closing on a station
|
||||
// and on a turret flies through both, and nothing is published.
|
||||
[Fact]
|
||||
public void AShipFliesThroughAStationAndATurret()
|
||||
{
|
||||
var lab = Build();
|
||||
var ship = lab.Add("Solid", float3(0, 0, 0), float2(10, 0));
|
||||
var station = lab.AddOrbital("Heavy", float3(10, 0, 0), float2(0, 0));
|
||||
var turret = lab.AddOrbital("Solid", float3(-10, 0, 0), float2(10, 0));
|
||||
lab.Step();
|
||||
Assert.Empty(lab.Events);
|
||||
Assert.Equal(float2(10, 0), ship.Velocity);
|
||||
Assert.Equal(float2(0, 0), station.Velocity);
|
||||
Assert.Equal(float2(10, 0), turret.Velocity);
|
||||
}
|
||||
|
||||
// An orbital overlapping a colliding pair changes nothing about the pair (ruling ship-bounce-restitution-setting):
|
||||
// one contact, between the two ships, with the restitution split of the pair alone.
|
||||
[Fact]
|
||||
public void TwoShipsStillBounceBesideAnOrbital()
|
||||
{
|
||||
var lab = Build(.5f);
|
||||
var a = lab.Add("Solid", float3(0, 0, 0), float2(10, 0));
|
||||
var b = lab.Add("Solid", float3(10, 0, 0), float2(-10, 0));
|
||||
lab.AddOrbital("Heavy", float3(5, 0, 0), float2(0, 0));
|
||||
lab.Step();
|
||||
|
||||
var contact = Assert.Single(lab.Events);
|
||||
Assert.Contains(a, new[] { contact.A, contact.B });
|
||||
Assert.Contains(b, new[] { contact.A, contact.B });
|
||||
var n = NormalOf(a, b);
|
||||
Assert.Equal(.5f * 20f * n.x, dot(n, b.Velocity - a.Velocity), 3);
|
||||
}
|
||||
|
||||
// Pairs resolve one after another in list order, so a body touching two others in one tick can leave the pile
|
||||
// differently when the list is reversed (order independence is not claimed); what the order does not change is
|
||||
// the momentum of the pile and the two contacts it publishes.
|
||||
|
|
|
|||
Loading…
Reference in a new issue