diff --git a/Dummies.UnitTests/CrossEngineReachabilityTests.cs b/Dummies.UnitTests/CrossEngineReachabilityTests.cs
new file mode 100644
index 0000000..ac2fc58
--- /dev/null
+++ b/Dummies.UnitTests/CrossEngineReachabilityTests.cs
@@ -0,0 +1,426 @@
+#region Usings declarations
+
+using NFluent;
+
+#endregion
+
+namespace Dummies.UnitTests;
+
+///
+/// A single scenario battery run against every interval-backed builder through a small per-type adapter,
+/// so an engine-level regression cannot hide in the one type-facade a hand-written test happens not to cover.
+/// Adding a builder means adding one row to , not a new file.
+///
+///
+///
+/// The suite closes the reachability blind spot the 2026-07-20 Dummies architecture audit named (§9.2/§9.3,
+/// issue #213): the existing tests assert membership (a generated value satisfies its constraints) but
+/// never reachability (the whole declared domain is actually generable). Two shipped defects survived
+/// that gap — never reaching the upper half of a range (#206) and the
+/// AnySingle/AnyHalf exclusion nudge stalling on satisfiable specs (#207). Both are guarded here
+/// structurally, across every engine at once, in addition to their dedicated regressions
+/// ( and
+/// , kept as focused, commented guards).
+///
+///
+/// Every scenario is deterministic: one fixed seed, a fixed draw count large enough that a correct uniform
+/// generator reaches the asserted region with overwhelming probability while a stuck one (half a range
+/// unreachable) fails every time. No randomness leaks in, so the suite is a stable CI guard, never a flaky one.
+///
+///
+public sealed class CrossEngineReachabilityTests {
+
+ #region Statics members declarations
+
+ // Three consecutive representable values of each floating type: a tight domain where excluding one endpoint
+ // must still generate by nudging along the type's own ladder — the exact shape that stalled #207 on the
+ // quantized types. Computed once via the type-aware bit step so the values are genuinely adjacent.
+ private static readonly double DLo = 1.0d;
+ private static readonly double DMid = Math.BitIncrement(1.0d);
+ private static readonly double DHi = Math.BitIncrement(Math.BitIncrement(1.0d));
+
+ private static readonly float FLo = 1.0f;
+ private static readonly float FMid = MathF.BitIncrement(1.0f);
+ private static readonly float FHi = MathF.BitIncrement(MathF.BitIncrement(1.0f));
+
+ private static readonly Half HLo = (Half)1;
+ private static readonly Half HMid = NextHalf((Half)1);
+ private static readonly Half HHi = NextHalf(NextHalf((Half)1));
+
+ // Temporal anchors — the low end of each narrow (few-ordinal) domain and of each wide range.
+ private static readonly DateTime DtAnchor = new(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+ private static readonly DateTimeOffset DtoAnchor = new(2000, 1, 1, 0, 0, 0, TimeSpan.Zero);
+ private static readonly DateOnly DoAnchor = new(2000, 1, 1);
+ private static readonly TimeOnly ToAnchor = new(9, 0, 0);
+
+ private static readonly Int128 WideInt128 = (Int128)ulong.MaxValue * 10;
+ private static readonly UInt128 WideUInt128 = (UInt128)ulong.MaxValue * 10;
+
+ ///
+ /// One adapter per interval-backed builder — every discrete integer, the two 128-bit integers, the three
+ /// binary-floating types, , and the five temporal types. The non-interval scalars
+ /// (bool, guid, string, enum, char) are deliberately absent: they expose no user-bounded ordered range, so
+ /// "both halves / both endpoints" is undefined for them.
+ ///
+ private static readonly ReachabilityCase[] AllCases = {
+ Case("SByte", true,
+ c => c.SByte(),
+ (c, lo, hi) => c.SByte().Between(lo, hi),
+ (c, lo, hi, x) => c.SByte().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.SByte().OneOf(allow).Except(except),
+ v => v, sbyte.MinValue, sbyte.MaxValue, -100, 100, 1, 2, 3),
+ Case("Int16", true,
+ c => c.Int16(),
+ (c, lo, hi) => c.Int16().Between(lo, hi),
+ (c, lo, hi, x) => c.Int16().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.Int16().OneOf(allow).Except(except),
+ v => v, short.MinValue, short.MaxValue, -30000, 30000, 1, 2, 3),
+ Case("Int32", true,
+ c => c.Int32(),
+ (c, lo, hi) => c.Int32().Between(lo, hi),
+ (c, lo, hi, x) => c.Int32().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.Int32().OneOf(allow).Except(except),
+ v => v, int.MinValue, int.MaxValue, -1_000_000, 1_000_000, 1, 2, 3),
+ Case("Int64", true,
+ c => c.Int64(),
+ (c, lo, hi) => c.Int64().Between(lo, hi),
+ (c, lo, hi, x) => c.Int64().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.Int64().OneOf(allow).Except(except),
+ v => v, long.MinValue, long.MaxValue, -1_000_000_000_000L, 1_000_000_000_000L, 1L, 2L, 3L),
+ Case("Byte", true,
+ c => c.Byte(),
+ (c, lo, hi) => c.Byte().Between(lo, hi),
+ (c, lo, hi, x) => c.Byte().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.Byte().OneOf(allow).Except(except),
+ v => v, byte.MinValue, byte.MaxValue, 10, 240, 100, 101, 102),
+ Case("UInt16", true,
+ c => c.UInt16(),
+ (c, lo, hi) => c.UInt16().Between(lo, hi),
+ (c, lo, hi, x) => c.UInt16().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.UInt16().OneOf(allow).Except(except),
+ v => v, ushort.MinValue, ushort.MaxValue, 100, 60000, 1000, 1001, 1002),
+ Case("UInt32", true,
+ c => c.UInt32(),
+ (c, lo, hi) => c.UInt32().Between(lo, hi),
+ (c, lo, hi, x) => c.UInt32().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.UInt32().OneOf(allow).Except(except),
+ v => v, uint.MinValue, uint.MaxValue, 1000u, 4_000_000_000u, 100_000u, 100_001u, 100_002u),
+ Case("UInt64", true,
+ c => c.UInt64(),
+ (c, lo, hi) => c.UInt64().Between(lo, hi),
+ (c, lo, hi, x) => c.UInt64().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.UInt64().OneOf(allow).Except(except),
+ v => v, ulong.MinValue, ulong.MaxValue, 1000ul, 10_000_000_000_000_000_000ul, 1_000_000ul, 1_000_001ul, 1_000_002ul),
+ Case("Int128", true,
+ c => c.Int128(),
+ (c, lo, hi) => c.Int128().Between(lo, hi),
+ (c, lo, hi, x) => c.Int128().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.Int128().OneOf(allow).Except(except),
+ v => (double)v, Int128.MinValue, Int128.MaxValue, -WideInt128, WideInt128, Int128.Zero, Int128.One, (Int128)2),
+ Case("UInt128", true,
+ c => c.UInt128(),
+ (c, lo, hi) => c.UInt128().Between(lo, hi),
+ (c, lo, hi, x) => c.UInt128().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.UInt128().OneOf(allow).Except(except),
+ v => (double)v, UInt128.MinValue, UInt128.MaxValue, UInt128.Zero, WideUInt128, UInt128.Zero, UInt128.One, (UInt128)2),
+ Case("Double", false,
+ c => c.Double(),
+ (c, lo, hi) => c.Double().Between(lo, hi),
+ (c, lo, hi, x) => c.Double().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.Double().OneOf(allow).Except(except),
+ v => v, double.MinValue, double.MaxValue, -1_000_000d, 1_000_000d, DLo, DMid, DHi),
+ Case("Single", false,
+ c => c.Single(),
+ (c, lo, hi) => c.Single().Between(lo, hi),
+ (c, lo, hi, x) => c.Single().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.Single().OneOf(allow).Except(except),
+ v => v, float.MinValue, float.MaxValue, -100_000f, 100_000f, FLo, FMid, FHi),
+ Case("Half", false,
+ c => c.Half(),
+ (c, lo, hi) => c.Half().Between(lo, hi),
+ (c, lo, hi, x) => c.Half().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.Half().OneOf(allow).Except(except),
+ v => (double)v, Half.MinValue, Half.MaxValue, (Half)(-1000), (Half)1000, HLo, HMid, HHi),
+ Case("Decimal", false,
+ c => c.Decimal(),
+ (c, lo, hi) => c.Decimal().Between(lo, hi),
+ (c, lo, hi, x) => c.Decimal().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.Decimal().OneOf(allow).Except(except),
+ v => (double)v, decimal.MinValue, decimal.MaxValue, 0m, 1_000_000m, 1m, 2m, 3m),
+ Case("DateTime", true,
+ c => c.DateTime(),
+ (c, lo, hi) => c.DateTime().Between(lo, hi),
+ (c, lo, hi, x) => c.DateTime().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.DateTime().OneOf(allow).Except(except),
+ v => v.Ticks, DateTime.MinValue, DateTime.MaxValue,
+ DtAnchor, new DateTime(2100, 1, 1, 0, 0, 0, DateTimeKind.Utc), DtAnchor, DtAnchor.AddTicks(1), DtAnchor.AddTicks(2)),
+ Case("DateTimeOffset", true,
+ c => c.DateTimeOffset(),
+ (c, lo, hi) => c.DateTimeOffset().Between(lo, hi),
+ (c, lo, hi, x) => c.DateTimeOffset().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.DateTimeOffset().OneOf(allow).Except(except),
+ v => v.UtcTicks, DateTimeOffset.MinValue, DateTimeOffset.MaxValue,
+ DtoAnchor, new DateTimeOffset(2100, 1, 1, 0, 0, 0, TimeSpan.Zero), DtoAnchor, DtoAnchor.AddTicks(1), DtoAnchor.AddTicks(2)),
+ Case("DateOnly", true,
+ c => c.DateOnly(),
+ (c, lo, hi) => c.DateOnly().Between(lo, hi),
+ (c, lo, hi, x) => c.DateOnly().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.DateOnly().OneOf(allow).Except(except),
+ v => v.DayNumber, DateOnly.MinValue, DateOnly.MaxValue,
+ DoAnchor, new DateOnly(2100, 1, 1), DoAnchor, DoAnchor.AddDays(1), DoAnchor.AddDays(2)),
+ Case("TimeOnly", true,
+ c => c.TimeOnly(),
+ (c, lo, hi) => c.TimeOnly().Between(lo, hi),
+ (c, lo, hi, x) => c.TimeOnly().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.TimeOnly().OneOf(allow).Except(except),
+ v => v.Ticks, TimeOnly.MinValue, TimeOnly.MaxValue,
+ new TimeOnly(0, 0, 0), new TimeOnly(23, 59, 59), ToAnchor, ToAnchor.Add(TimeSpan.FromTicks(1)), ToAnchor.Add(TimeSpan.FromTicks(2))),
+ Case("TimeSpan", true,
+ c => c.TimeSpan(),
+ (c, lo, hi) => c.TimeSpan().Between(lo, hi),
+ (c, lo, hi, x) => c.TimeSpan().Between(lo, hi).DifferentFrom(x),
+ (c, allow, except) => c.TimeSpan().OneOf(allow).Except(except),
+ v => v.Ticks, TimeSpan.MinValue, TimeSpan.MaxValue,
+ TimeSpan.FromHours(-1), TimeSpan.FromHours(1), TimeSpan.Zero, TimeSpan.FromTicks(1), TimeSpan.FromTicks(2)),
+ };
+
+ private static readonly IReadOnlyDictionary ByName = AllCases.ToDictionary(one => one.Name);
+
+ /// The builder names, one theory row each — a serializable key so every builder is an isolated test.
+ public static TheoryData CaseNames() {
+ TheoryData data = new();
+ foreach (ReachabilityCase one in AllCases) { data.Add(one.Name); }
+
+ return data;
+ }
+
+ private static IntervalCase Case(string name, bool exact,
+ Func> full,
+ Func> between,
+ Func> betweenDifferentFrom,
+ Func> oneOfExcept,
+ Func scale,
+ T domainMin, T domainMax, T wideLo, T wideHi, T na, T nb, T nc) {
+ return new IntervalCase(name, exact, full, between, betweenDifferentFrom, oneOfExcept, scale,
+ domainMin, domainMax, wideLo, wideHi, na, nb, nc);
+ }
+
+ private static Half NextHalf(Half value) {
+ return BitConverter.Int16BitsToHalf((short)(BitConverter.HalfToInt16Bits(value) + 1));
+ }
+
+ #endregion
+
+ [Theory(DisplayName = "Full range: an unconstrained generator reaches both halves of its domain.")]
+ [MemberData(nameof(CaseNames))]
+ public void FullRangeReachesBothHalvesOfTheDomain(string builder) {
+ ByName[builder].FullRangeReachesBothHalvesOfTheDomain();
+ }
+
+ [Theory(DisplayName = "Between: a wide range reaches both halves — neither half is silently unreachable.")]
+ [MemberData(nameof(CaseNames))]
+ public void WideBetweenReachesBothHalves(string builder) {
+ ByName[builder].WideBetweenReachesBothHalves();
+ }
+
+ [Theory(DisplayName = "Between: both inclusive bounds are reachable (exactly for discrete types, to within 1% for continuous ones).")]
+ [MemberData(nameof(CaseNames))]
+ public void BothInclusiveBoundsAreReachable(string builder) {
+ ByName[builder].BothInclusiveBoundsAreReachable();
+ }
+
+ [Theory(DisplayName = "Exclusion: a point excluded from a narrow range still generates and is never returned.")]
+ [MemberData(nameof(CaseNames))]
+ public void NarrowExclusionStillGenerates(string builder) {
+ ByName[builder].NarrowExclusionStillGenerates();
+ }
+
+ [Theory(DisplayName = "OneOf then Except: only the surviving allowed values are generated.")]
+ [MemberData(nameof(CaseNames))]
+ public void OneOfThenExceptYieldsOnlyTheSurvivors(string builder) {
+ ByName[builder].OneOfThenExceptYieldsOnlyTheSurvivors();
+ }
+
+ [Theory(DisplayName = "Conflict: contradictory constraints throw naming both sides.")]
+ [MemberData(nameof(CaseNames))]
+ public void ContradictoryConstraintsNameBothSides(string builder) {
+ ByName[builder].ContradictoryConstraintsNameBothSides();
+ }
+
+}
+
+/// One interval-backed builder's participation in the shared scenario battery — the per-type adapter.
+internal abstract class ReachabilityCase {
+
+ protected ReachabilityCase(string name) {
+ Name = name;
+ }
+
+ public string Name { get; }
+
+ public sealed override string ToString() {
+ return Name;
+ }
+
+ public abstract void FullRangeReachesBothHalvesOfTheDomain();
+
+ public abstract void WideBetweenReachesBothHalves();
+
+ public abstract void BothInclusiveBoundsAreReachable();
+
+ public abstract void NarrowExclusionStillGenerates();
+
+ public abstract void OneOfThenExceptYieldsOnlyTheSurvivors();
+
+ public abstract void ContradictoryConstraintsNameBothSides();
+
+}
+
+///
+/// The generic adapter carrying a builder's value type , its wide and narrow test
+/// domains, whether its endpoints are exactly reachable, a monotone projection onto for
+/// half/bound detection, and the four uniformly-named chains the scenarios drive (Between,
+/// Between + DifferentFrom, OneOf + Except, and the unconstrained builder).
+///
+internal sealed class IntervalCase : ReachabilityCase {
+
+ private const int Seed = 20260721;
+ private const int DistributionSamples = 4000;
+ private const int SetSamples = 200;
+
+ #region Fields declarations
+
+ private readonly Func> _betweenDifferentFrom;
+ private readonly Func> _between;
+ private readonly T _domainMax;
+ private readonly T _domainMin;
+ private readonly bool _endpointsExact;
+ private readonly Func> _full;
+ private readonly T _na;
+ private readonly T _nb;
+ private readonly T _nc;
+ private readonly Func> _oneOfExcept;
+ private readonly Func _scale;
+ private readonly T _wideHi;
+ private readonly T _wideLo;
+
+ #endregion
+
+ public IntervalCase(string name, bool endpointsExact,
+ Func> full,
+ Func> between,
+ Func> betweenDifferentFrom,
+ Func> oneOfExcept,
+ Func scale,
+ T domainMin, T domainMax, T wideLo, T wideHi, T na, T nb, T nc)
+ : base(name) {
+ _endpointsExact = endpointsExact;
+ _full = full;
+ _between = between;
+ _betweenDifferentFrom = betweenDifferentFrom;
+ _oneOfExcept = oneOfExcept;
+ _scale = scale;
+ _domainMin = domainMin;
+ _domainMax = domainMax;
+ _wideLo = wideLo;
+ _wideHi = wideHi;
+ _na = na;
+ _nb = nb;
+ _nc = nc;
+ }
+
+ public override void FullRangeReachesBothHalvesOfTheDomain() {
+ (double min, double max, _) = Sample(_full(Any.WithSeed(Seed)), DistributionSamples);
+ double mid = _scale(_domainMin) / 2 + _scale(_domainMax) / 2;
+
+ Check.That(min).IsStrictlyLessThan(mid); // the lower half of the domain is reached
+ Check.That(max).IsStrictlyGreaterThan(mid); // and so is the upper half
+ }
+
+ public override void WideBetweenReachesBothHalves() {
+ (double min, double max, _) = Sample(_between(Any.WithSeed(Seed), _wideLo, _wideHi), DistributionSamples);
+ double lo = _scale(_wideLo);
+ double hi = _scale(_wideHi);
+ double mid = lo / 2 + hi / 2;
+
+ Check.That(min).IsGreaterOrEqualThan(lo); // draws stay in range
+ Check.That(max).IsLessOrEqualThan(hi);
+ Check.That(min).IsStrictlyLessThan(mid); // the lower half is reached
+ Check.That(max).IsStrictlyGreaterThan(mid); // the upper half is reached — the #206 class of defect
+ }
+
+ public override void BothInclusiveBoundsAreReachable() {
+ if (_endpointsExact) {
+ // Discrete types: a narrow three-value range must hand back both of its exact endpoints.
+ (_, _, HashSet seen) = Sample(_between(Any.WithSeed(Seed), _na, _nc), DistributionSamples);
+ Check.That(seen.Contains(_na)).IsTrue();
+ Check.That(seen.Contains(_nc)).IsTrue();
+
+ return;
+ }
+
+ // Continuous types: exact endpoints are a measure-zero target, so a draw must instead come within 1% of
+ // each inclusive bound of a wide range. A generator stuck below the midpoint (#206) never gets near the top.
+ (double min, double max, _) = Sample(_between(Any.WithSeed(Seed), _wideLo, _wideHi), DistributionSamples);
+ double lo = _scale(_wideLo);
+ double hi = _scale(_wideHi);
+ double range = hi - lo;
+
+ Check.That(max).IsStrictlyGreaterThan(hi - range * 0.01d);
+ Check.That(min).IsStrictlyLessThan(lo + range * 0.01d);
+ }
+
+ public override void NarrowExclusionStillGenerates() {
+ // Between(na, nc).DifferentFrom(na): on the quantized floating types this drives the type-aware nudge that
+ // stalled in #207; on the discrete types it drives the ordinal exclusion. Either way it must generate.
+ IAny generator = _betweenDifferentFrom(Any.WithSeed(Seed), _na, _nc, _na);
+ EqualityComparer equals = EqualityComparer.Default;
+ double lo = _scale(_na);
+ double hi = _scale(_nc);
+
+ for (int i = 0; i < SetSamples; i++) {
+ T value = generator.Generate();
+ Check.That(equals.Equals(value, _na)).IsFalse();
+ Check.That(_scale(value)).IsGreaterOrEqualThan(lo);
+ Check.That(_scale(value)).IsLessOrEqualThan(hi);
+ }
+ }
+
+ public override void OneOfThenExceptYieldsOnlyTheSurvivors() {
+ IAny generator = _oneOfExcept(Any.WithSeed(Seed), [_na, _nb, _nc], [_nb]);
+ EqualityComparer equals = EqualityComparer.Default;
+
+ for (int i = 0; i < SetSamples; i++) {
+ T value = generator.Generate();
+ Check.That(equals.Equals(value, _nb)).IsFalse();
+ Check.That(equals.Equals(value, _na) || equals.Equals(value, _nc)).IsTrue();
+ }
+ }
+
+ public override void ContradictoryConstraintsNameBothSides() {
+ // OneOf(na).Except(na) empties the allow-list; the message must name both the allow-list and the exclusion.
+ // Asserting the method-name tokens keeps this independent of how each type renders its values.
+ ConflictingAnyConstraintException conflict = Assert.Throws(
+ () => _oneOfExcept(Any.WithSeed(Seed), [_na], [_na]));
+
+ Check.That(conflict.Message).Contains("OneOf(");
+ Check.That(conflict.Message).Contains("Except(");
+ }
+
+ private (double Min, double Max, HashSet Seen) Sample(IAny generator, int count) {
+ double min = double.PositiveInfinity;
+ double max = double.NegativeInfinity;
+ HashSet seen = new();
+
+ for (int i = 0; i < count; i++) {
+ T value = generator.Generate();
+ double scaled = _scale(value);
+ seen.Add(value);
+ if (scaled < min) { min = scaled; }
+ if (scaled > max) { max = scaled; }
+ }
+
+ return (min, max, seen);
+ }
+
+}
diff --git a/Dummies/DecimalIntervalSpec.cs b/Dummies/DecimalIntervalSpec.cs
index 5cef143..894973a 100644
--- a/Dummies/DecimalIntervalSpec.cs
+++ b/Dummies/DecimalIntervalSpec.cs
@@ -151,10 +151,11 @@ internal decimal Generate(Random random, int seed) {
BitConverter.ToInt32(mantissa, 4),
BitConverter.ToInt32(mantissa, 8),
false, 28) / MaxFraction;
- // Sample around the midpoint so the span (max - min) never overflows on wide ranges.
- decimal mid = _min / 2 + _max / 2;
- decimal half = _max / 2 - _min / 2;
- decimal candidate = Clamped(mid + (fraction * 2 - 1) * half);
+ // Interpolate as a convex combination: min*(1 - fraction) + max*fraction stays within [min, max] for
+ // fraction in [0, 1], and no intermediate ever leaves the decimal range. The earlier midpoint form
+ // (mid ± half) overflowed on the full domain — it is symmetric, so max/2 rounds up and half = max/2 - min/2
+ // doubles to just past decimal.MaxValue, throwing on an unconstrained Any.Decimal().Generate().
+ decimal candidate = Clamped(_min * (1m - fraction) + _max * fraction);
// A draw colliding with an excluded point is walked by the smallest decimal step — deterministic and
// bounded, not a retry loop. (At extreme magnitudes the step can vanish in rounding; the budget then