Tests: a Sensor reads its intercept factor from its lot's emission-discipline; the Longinus broadside ceiling has its own fitted factor

ASensorReadsItsInterceptFactorFromItsLotsEmissionDiscipline equips a catalog-style sensor from a lot filled at .5, .2 and .8 and reads Sensor.InterceptFactor (1, .6168, 1.5534). The Longinus half of ReachIsWhatTheShipCanDo takes LonginusBroadsideFactor (1.27, from reach 12.74 over measured 10.40) in place of the Djinni's. Builds not verified until the verify job; no catalog change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-13 23:15:35 +02:00
commit b63cdf5d2b
3 changed files with 34 additions and 2 deletions

View file

@ -59,6 +59,16 @@ public sealed partial class ContactRevealTests : IDisposable
// PingRange (300) over the intercept factor.
cache.Upsert(Eye("Shout", sensitivity: 2f, interceptFactor: 2f, beamWidth: 10f));
cache.Upsert(Eye("Roar", sensitivity: 2f, interceptFactor: .5f, beamWidth: 10f));
// The catalog's sensor alpha (AetherDb sensor-stat-set): .5 to 2 over one Quality term on the equipped lot's
// emission-discipline role, exponent log2(3) so a middling part (.5) reads 1.
var disciplined = Eye("Disciplined", sensitivity: 2f);
disciplined.Roles.Add(new ItemRole { Name = "emission-discipline" });
disciplined.Behaviors.OfType<SensorData>().Single().InterceptFactor = new PerformanceStat
{
Min = .5f, Max = 2f,
Terms = { new StatTerm { Source = StatSource.Quality, Exponent = MathF.Log(3f) / MathF.Log(2f), Role = "emission-discipline" } }
};
cache.Upsert(disciplined);
cache.Upsert(Listener("Listener", 3f));
cache.Upsert(new ConsumableItemData { Name = "Earpiece", Duration = 30f, Shape = new Shape(), Behaviors = { new ListenerData { Multiple = Const(3f) } } });
cache.Upsert(Listener("Wide", 5f));

View file

@ -314,4 +314,23 @@ public sealed partial class ContactRevealTests
Assert.Equal(1f, locker.Equipment.SelectMany(i => i.Behaviors).OfType<LockWeapon>().Single().InterceptFactor);
Assert.Equal(4f, hushed.Equipment.SelectMany(i => i.Behaviors).OfType<LockWeapon>().Single().InterceptFactor);
}
// A catalog-style sensor reads its intercept factor through the lot the ship equips it from: the lot's
// emission-discipline fill is the input (the workmanship is a decoy at .123), so a Sensor that read only the quiet or only
// the loud end, or the workmanship, reads the wrong number. The expectations are lerp(.5, 2, q^log2(3)): a middling part
// reads 1, the Lucent mean (.2) reads about .62 and the Finch mean (.8) about 1.55.
[Theory]
[InlineData(.5f, 1f)]
[InlineData(.2f, .6168f)]
[InlineData(.8f, 1.5534f)]
public void ASensorReadsItsInterceptFactorFromItsLotsEmissionDiscipline(float discipline, float expected)
{
var lot = _items.CreateLot(_openCache.GetByName<EquippableItemData>("Disciplined"), _maker, .123f);
_items.Lots[lot].Roles = new List<RoleFill> { new RoleFill { Role = "emission-discipline", Quality = discipline } };
var ship = Add(float3(0, 0, 0), Array.Empty<string>());
Assert.True(ship.TryEquip(new EquippableItem { Data = _items.ItemData.RefOf<ItemData>(_items.ItemData.GetByName<ItemData>("Disciplined")), Durability = 10, Lot = lot }, new int2(0, 0)));
_zone.Update(0f);
Assert.Equal(expected, SensorOf(ship).InterceptFactor, 3);
}
}

View file

@ -296,7 +296,10 @@ public sealed partial class RunStartTests
return (Half(float2(1, 0)), Half(float2(0, 1)));
}
private const float DjinniNoseOnFactor = 1.75f, DjinniBroadsideFactor = 1.36f;
// Each factor is fitted to its own ship's fleet fit: reach over measured motion. Djinni: 1.69 nose-on and 1.31 broadside at
// the last measurement, each with about 4% to spare. Longinus broadside: 1.23 (reach 12.74 over measured 10.40) with
// about 3.5% to spare, so an inflation of its envelope of 4% or more breaks its half.
private const float DjinniNoseOnFactor = 1.75f, DjinniBroadsideFactor = 1.36f, LonginusBroadsideFactor = 1.27f;
[Fact]
public void ReachIsWhatTheShipCanDo()
@ -342,7 +345,7 @@ public sealed partial class RunStartTests
// with other rolls). The ceiling is that, from the columns
// the allocator solves with, not an open bound: an envelope inflated by a factor (doubled: 2.7 and 2.3) breaks it.
Assert.InRange(reach.noseOn, measured.noseOn * .9f, allocated.noseOn * 1.5f);
Assert.InRange(reach.broadside, measured.broadside * .9f, measured.broadside * DjinniBroadsideFactor);
Assert.InRange(reach.broadside, measured.broadside * .9f, measured.broadside * LonginusBroadsideFactor);
Assert.InRange(measured.noseOn, allocated.noseOn * .9f, allocated.noseOn * 1.15f);
Assert.InRange(measured.broadside, allocated.broadside * .85f, allocated.broadside * 1.15f);
}