fix(rectangles): break exact material-area ties the same way on every platform

ZeroClampMixedShapeLayoutIsBitIdenticalToBaseline has failed on Windows since
it was added in de9a087 while passing on Linux CI (bisect pointing at
c374e02 was an artifact: the assumed-good start commit fails too). No
commit changed the layout; the two platforms always chose differently.

In that fixture two candidate sheets (ContactPoint/Global and
BestShortSide/Ordered) both place all 30 parts at the same cost, so Choose
falls to its "more material placed" tie-break. SheetPacker summed material
area in placement order, and the disc's area differs in the last bit
between the Windows CRT and glibc math libraries. On Linux the two orders
rounded to ...253 vs ...257, so the later candidate won on rounding noise;
on Windows both rounded to ...257 and the tie fell to enumeration order as
Choose documents.

Sum a plan's area per type in type order instead (the same form as the
engine's demand area), so plans placing the same parts get bit-identical
areas and exact ties keep enumeration order everywhere. No tolerance is
loosened. The Windows layout is unchanged; Linux now matches it, so the
frozen hash moves to the Windows value, verified identical on both.

Verified: full Engine.Tests pass on Windows and Linux (1105 passed, 2
skipped); Rectangles and Default benchmark results on the six synthetic
nests are identical before and after (validity, placed parts, cost,
utilization, plates).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-16 13:20:20 -04:00
1 parent 9f627c1a9d
commit 4e0699de7a
2 files changed
+9 -3

No files matched your search

@@ -43,7 +43,9 @@ public class RectanglesClampZoneTests
new[] { Stock("sheet", 48, 96, spacing: 0.25) }))));
[Theory]
[InlineData("9B343B6C5CF2CE8753F2E04ACB0A9C4D5921F4ADC15AA2586AF466769C0F019B")]
// Two complete candidates tie here on cost and placed material; the tie must fall to
// enumeration order on every platform (hash identical on Windows and Linux).
[InlineData("BEEEDEC81A7F987720FB5F331873103AD2470C839D568CE587F230BC39D210CC")]
public void ZeroClampMixedShapeLayoutIsBitIdenticalToBaseline(string hash) =>
Assert.Equal(hash, LayoutHash(Engine().Solve(Job(
new[]
@@ -70,14 +70,18 @@ internal static class SheetPacker
.Select(p => new Placed(types[p.Type], types[p.Type].Orientations[p.Size], p.X, p.Y))
.ToList();
var area = 0.0;
var counts = new int[types.Count];
Box? envelope = null;
foreach (var p in placed)
{
area += p.Type.MaterialArea;
counts[p.Type.Index]++;
var box = new Box(work.Left + p.Left, work.Bottom + p.Bottom, p.Orientation.Width, p.Orientation.Height);
envelope = envelope == null ? box : Union(envelope, box);
}
// Summed per type in type order, the same form as the engine's demand area: plans placing
// the same parts get bit-identical areas, so the engine's material-area tie-break cannot
// be decided by placement-order rounding (which differs across platform math libraries).
var area = types.Sum(t => counts[t.Index] * t.MaterialArea);
var world = placed
.Select(p => p with { Left = work.Left + p.Left, Bottom = work.Bottom + p.Bottom })