Merge cut sim-contact (Soul verdict cut-sim-contact.s2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
f85a0b0e17
7 changed files with 561 additions and 26 deletions
79
Assets/Scripts/ServerShared/Contacts.cs
Normal file
79
Assets/Scripts/ServerShared/Contacts.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/* 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.Linq;
|
||||
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
|
||||
// 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
|
||||
// GameplaySettings.ContactRestitution.
|
||||
// Presentations read Zone.Contacted; no Unity collision callback writes an Entity.
|
||||
public readonly struct ContactEvent
|
||||
{
|
||||
public readonly Entity A;
|
||||
public readonly Entity B;
|
||||
public readonly float2 Point;
|
||||
// Unit vector from A to B.
|
||||
public readonly float2 Normal;
|
||||
|
||||
public ContactEvent(Entity a, Entity b, float2 point, float2 normal)
|
||||
{
|
||||
A = a;
|
||||
B = b;
|
||||
Point = point;
|
||||
Normal = normal;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Contacts
|
||||
{
|
||||
// The farthest corner of any occupied hull cell from the centre of mass the schematic frame uses, in world
|
||||
// units: a true bound on how far the hull reaches from Position (the half-diagonal of the bounding box is not,
|
||||
// as FireControl.Detonate's comment records). A cell at (x, y) spans x +/- .5 and y +/- .5.
|
||||
public static float Radius(Entity e)
|
||||
{
|
||||
var shape = e.HullData.Shape;
|
||||
var com = shape.CenterOfMass;
|
||||
var far = 0f;
|
||||
foreach (var c in shape.Coordinates)
|
||||
foreach (var (dx, dy) in new[] { (-.5f, -.5f), (.5f, -.5f), (-.5f, .5f), (.5f, .5f) })
|
||||
far = max(far, length(float2(c.x + dx, c.y + dy) - com));
|
||||
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
|
||||
// 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
|
||||
// total momentum does not. A docked entity (it has a Parent and takes its parent's pose) and an inactive one
|
||||
// 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 radii = entities.Select(Radius).ToArray();
|
||||
for (var i = 0; i < entities.Length; i++)
|
||||
for (var j = i + 1; j < entities.Length; j++)
|
||||
{
|
||||
var a = entities[i];
|
||||
var b = entities[j];
|
||||
var offset = b.Position.xz - a.Position.xz;
|
||||
var distance = length(offset);
|
||||
// Coincident centres have no normal.
|
||||
if (distance <= 0f || distance >= radii[i] + radii[j]) continue;
|
||||
var n = offset / distance;
|
||||
var closing = dot(n, b.Velocity - a.Velocity);
|
||||
if (closing >= 0f) continue;
|
||||
|
||||
var impulse = -(1f + a.ItemManager.GameplaySettings.ContactRestitution) * closing / (1f / a.Mass + 1f / b.Mass);
|
||||
a.Velocity -= n * (impulse / a.Mass);
|
||||
b.Velocity += n * (impulse / b.Mass);
|
||||
zone.Contacted.OnNext(new ContactEvent(a, b, a.Position.xz + n * radii[i], n));
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/ServerShared/Contacts.cs.meta
Normal file
11
Assets/Scripts/ServerShared/Contacts.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7c1e5a3b9d4f4e2a8b6c0d1f2e3a4b5c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -268,6 +268,10 @@ public class GameplaySettings
|
|||
// (pSpread) to turn a hull's cell footprint into a real-world silhouette size at range.
|
||||
public float SchematicCellSize = 2f;
|
||||
|
||||
// Restitution of a hull striking a hull (Contacts.Step): the speed at which a pair separates along the contact
|
||||
// normal is this times the speed at which it closed. 0 is perfectly inelastic, 1 perfectly elastic.
|
||||
public float ContactRestitution = .5f;
|
||||
|
||||
// The floor an AI's own predicted hit probability (FireControl.HitProbability) must clear before it counts
|
||||
// a firing solution worth taking (Combat.cs, TurretController.cs). Player fire is gated on arc alone
|
||||
// (Weapon.ArcAllowsFire, Q2) -- this threshold is an AI fire-discipline heuristic, not part of the roll.
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ public class Zone
|
|||
public List<PendingShot> PendingShots = new List<PendingShot>();
|
||||
public Subject<ShotOutcome> ShotCommitted = new Subject<ShotOutcome>();
|
||||
public Subject<ShotOutcome> ShotResolved = new Subject<ShotOutcome>();
|
||||
// Contacts.Step publishes each hull-on-hull contact it resolves; presentations are the only readers.
|
||||
public Subject<ContactEvent> Contacted = new Subject<ContactEvent>();
|
||||
private int _nextShotId;
|
||||
public int NextShotId() => ++_nextShotId;
|
||||
|
||||
|
|
@ -241,6 +243,10 @@ public class Zone
|
|||
StepMines(deltaTime);
|
||||
StepClouds(deltaTime);
|
||||
|
||||
// Hull contact (cut sim-contact): after every entity has moved and the mines and clouds have taken
|
||||
// their toll, so only hulls still in the zone touch, and before shots resolve against the result.
|
||||
Contacts.Step(this);
|
||||
|
||||
// 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
|
||||
// resolves in this same call.
|
||||
|
|
|
|||
Loading…
Reference in a new issue