Fire control 12.4 fix batch: tangent disc, candidate cull, lethal blast
F1: RectDiskOverlap counted a whole square when a cell edge sat exactly r from the disc centre (`h < yhi`, `-h > ylo`). The comparisons are inclusive now, so the chord bounds the piece. A proximity burst on an integer centre of mass delivered 127.32 of 100 at BlastRadius 1 and 100.80 at 3. F2: delete Detonate's distance prefilter and the radius <= 0 guard. The prefilter measured from the centre of mass with the bounding box's half-diagonal, which is not a bound on how far a hull's cells reach; it culled real overlap on an L-shaped hull and on the shipped Longinus corners. RectDiskOverlap is the single decider. An untouched entity is still skipped (its covered list is never allocated). F3: Detonate iterates a snapshot of zone.Entities, as Zone.Update does. A lethal blast removed the entity from the live collection and the next MoveNext threw, leaving the shot pending so it detonated again the next tick. Tests: tangent discs through Fire and Detonate, an L-shaped hull, grazing blasts at the farthest corner of Longinus and LonginusX (expected values from a numeric overlap), and a lethal blast that must resolve once. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
6b222564ee
commit
8b4bf01eac
2 changed files with 190 additions and 22 deletions
|
|
@ -1073,23 +1073,20 @@ public static class FireControl
|
|||
// DamageHull call.
|
||||
public static void Detonate(Zone zone, float2 worldPlanar, float radius, float damage, DamageType damageType)
|
||||
{
|
||||
// F3 (Soul, Cut 12.3 fold-in): Detonate cannot be reached with a nonpositive radius. Fire only ever
|
||||
// freezes a Fuse when BlastRadius > 0 (12.4(a)), so this guard matters only for a direct caller (a
|
||||
// test, or Mine.Explode with a zero BlastRange).
|
||||
if (radius <= 0f) return;
|
||||
|
||||
foreach (var entity in zone.Entities)
|
||||
// No distance prefilter: RectDiskOverlap is the one decider of whether the disc touches a hull. A
|
||||
// prefilter measured from Position (the centre of mass) with the bounding box's half-diagonal is not a
|
||||
// bound on how far a hull's cells reach from it -- it culled real overlap on an L-shaped hull and on the
|
||||
// shipped Longinus corners. A nonpositive radius needs no guard either: the overlap of a disc with no
|
||||
// area is zero for every cell, so nothing is covered and nothing is delivered.
|
||||
//
|
||||
// The pass runs over a snapshot: a lethal blast fires an entity's Death, which removes it from
|
||||
// Zone.Entities, and the live enumeration threw on the next MoveNext (the same idiom Zone.Update uses).
|
||||
foreach (var entity in zone.Entities.ToArray())
|
||||
{
|
||||
var cellSize = entity.ItemManager.GameplaySettings.SchematicCellSize;
|
||||
var hullData = entity.ItemManager.GetData(entity.Hull) as HullData;
|
||||
var shape = hullData.Shape;
|
||||
|
||||
// Candidates: |Position.xz - P| <= radius + half the hull's own diagonal, in world units -- catches
|
||||
// a long hull whose end lies inside the blast even though its centre does not.
|
||||
var toEntity = length(entity.Position.xz - worldPlanar);
|
||||
var halfDiagonal = .5f * length(float2(shape.Width, shape.Height)) * cellSize;
|
||||
if (toEntity > radius + halfDiagonal) continue;
|
||||
|
||||
var centre = entity.ToSchematicPoint(worldPlanar);
|
||||
var rCells = radius / cellSize;
|
||||
var normaliser = PI * rCells * rCells;
|
||||
|
|
@ -1097,17 +1094,18 @@ public static class FireControl
|
|||
// Shares, computed once, in hull Coordinates order -- the same order armour absorbs in below and
|
||||
// an item's pool deposits in.
|
||||
var coords = shape.Coordinates;
|
||||
var covered = new List<(int2 Cell, float Share)>();
|
||||
List<(int2 Cell, float Share)> covered = null;
|
||||
var totalShare = 0f;
|
||||
for (var i = 0; i < coords.Length; i++)
|
||||
{
|
||||
var overlap = CircleSquareOverlap(centre, rCells, coords[i]);
|
||||
if (overlap <= 0f) continue;
|
||||
var share = damage * overlap / normaliser;
|
||||
covered.Add((coords[i], share));
|
||||
(covered ??= new List<(int2 Cell, float Share)>()).Add((coords[i], share));
|
||||
totalShare += share;
|
||||
}
|
||||
if (covered.Count == 0) continue;
|
||||
// An entity the disc does not touch has nothing to decide: no shield is consulted, nothing is dealt.
|
||||
if (covered == null) continue;
|
||||
|
||||
var shield = entity.Shield;
|
||||
var shieldActive = shield != null && shield.Item.Active.Value;
|
||||
|
|
@ -1209,8 +1207,11 @@ public static class FireControl
|
|||
|
||||
var mid = .5f * (a + b);
|
||||
var h = sqrt(max(0f, r * r - mid * mid));
|
||||
var upperIsDisc = h < yhi;
|
||||
var lowerIsDisc = -h > ylo;
|
||||
// Inclusive: a rectangle edge exactly tangent to the disc (h == yhi at a piece whose midpoint is 0)
|
||||
// is the disc's own boundary, so the chord is the true bound there. A strict test sent that piece to
|
||||
// the whole-rectangle branch and counted the square in full -- a boundary flip that creates damage.
|
||||
var upperIsDisc = h <= yhi;
|
||||
var lowerIsDisc = -h >= ylo;
|
||||
var upper = upperIsDisc ? h : yhi;
|
||||
var lower = lowerIsDisc ? -h : ylo;
|
||||
if (upper <= lower) continue;
|
||||
|
|
|
|||
Loading…
Reference in a new issue