From e98c72884058d93b06e383d65e7b14028bd63d77 Mon Sep 17 00:00:00 2001 From: GeneralD Date: Mon, 15 Jun 2026 04:00:34 +0900 Subject: [PATCH 1/3] test: assert geodesic struts form a closed trivalent cage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dual edge list already had a 120-edge invariant, but a malformed subdivision could hold that count while skewing the hub topology. Count each strut endpoint's degree and assert all 80 centroids are trivalent (80 × 3 / 2 = 120), pinning the closed-manifold structure the edge-count test alone can miss. --- Tests/ViewsTests/GeodesicGeometryTests.swift | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Tests/ViewsTests/GeodesicGeometryTests.swift b/Tests/ViewsTests/GeodesicGeometryTests.swift index 5593f17..000b3df 100644 --- a/Tests/ViewsTests/GeodesicGeometryTests.swift +++ b/Tests/ViewsTests/GeodesicGeometryTests.swift @@ -17,6 +17,14 @@ struct GeodesicGeometryTests { .squareRoot() } + /// Quantized coordinate key so the same centroid (reused by value across + /// several struts) collapses to one identity. The 80 centroids are ~0.36 + /// apart on the unit sphere, far above the 1e-6 rounding floor, so distinct + /// vertices never collide. + private func key(_ v: Vertex3D) -> [Int] { + [v.x, v.y, v.z].map { Int(($0 * 1_000_000).rounded()) } + } + @Test("dual of the freq-2 icosphere has exactly 120 edges") func edgeCount() { // 80 triangles → 120 manifold edges → 120 dual struts. Any other count @@ -38,4 +46,19 @@ struct GeodesicGeometryTests { #expect(distance(a, b) > 1e-6) } } + + @Test("struts form a closed trivalent cage — 80 hubs, each of degree 3") + func trivalentCage() { + // Each strut endpoint is the centroid of one of the 80 icosphere + // triangles, i.e. a vertex of the Goldberg dual. A closed manifold + // makes every triangle share all 3 of its edges, so each centroid is + // trivalent. 80 hubs × 3 / 2 = 120 struts — consistent with edgeCount. + // A broken face list or non-manifold subdivision would drop a hub or + // skew a degree, which a raw edge count alone can miss. + let degree = GeodesicGeometry.edges + .flatMap { [key($0.0), key($0.1)] } + .reduce(into: [[Int]: Int]()) { $0[$1, default: 0] += 1 } + #expect(degree.count == 80) + #expect(degree.values.allSatisfy { $0 == 3 }) + } } From 2dc9864d06d419ebfd43ecf9033f7e990f5fb4fe Mon Sep 17 00:00:00 2001 From: GeneralD Date: Mon, 15 Jun 2026 04:00:34 +0900 Subject: [PATCH 2/3] chore: bump version to 2.14.2 --- Sources/VersionHandler/Resources/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/VersionHandler/Resources/version.txt b/Sources/VersionHandler/Resources/version.txt index b70ae75..7243b12 100644 --- a/Sources/VersionHandler/Resources/version.txt +++ b/Sources/VersionHandler/Resources/version.txt @@ -1 +1 @@ -2.14.1 +2.14.2 From ad0f3af49c01f271dff1d354d0915168d16a6aee Mon Sep 17 00:00:00 2001 From: GeneralD Date: Mon, 15 Jun 2026 04:16:01 +0900 Subject: [PATCH 3/3] test: tidy trivalent-cage vertex keying per review Replace the [Int] dictionary key with a dedicated Hashable QuantizedVertex struct (clearer intent, no per-edge array allocation) and fold the degree count directly into one reduce, dropping the intermediate flatMap array. --- Tests/ViewsTests/GeodesicGeometryTests.swift | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Tests/ViewsTests/GeodesicGeometryTests.swift b/Tests/ViewsTests/GeodesicGeometryTests.swift index 000b3df..c42237b 100644 --- a/Tests/ViewsTests/GeodesicGeometryTests.swift +++ b/Tests/ViewsTests/GeodesicGeometryTests.swift @@ -17,12 +17,19 @@ struct GeodesicGeometryTests { .squareRoot() } + private struct QuantizedVertex: Hashable { + let x, y, z: Int + } + /// Quantized coordinate key so the same centroid (reused by value across /// several struts) collapses to one identity. The 80 centroids are ~0.36 /// apart on the unit sphere, far above the 1e-6 rounding floor, so distinct /// vertices never collide. - private func key(_ v: Vertex3D) -> [Int] { - [v.x, v.y, v.z].map { Int(($0 * 1_000_000).rounded()) } + private func key(_ v: Vertex3D) -> QuantizedVertex { + QuantizedVertex( + x: Int((v.x * 1_000_000).rounded()), + y: Int((v.y * 1_000_000).rounded()), + z: Int((v.z * 1_000_000).rounded())) } @Test("dual of the freq-2 icosphere has exactly 120 edges") @@ -56,8 +63,10 @@ struct GeodesicGeometryTests { // A broken face list or non-manifold subdivision would drop a hub or // skew a degree, which a raw edge count alone can miss. let degree = GeodesicGeometry.edges - .flatMap { [key($0.0), key($0.1)] } - .reduce(into: [[Int]: Int]()) { $0[$1, default: 0] += 1 } + .reduce(into: [QuantizedVertex: Int]()) { counts, edge in + counts[key(edge.0), default: 0] += 1 + counts[key(edge.1), default: 0] += 1 + } #expect(degree.count == 80) #expect(degree.values.allSatisfy { $0 == 3 }) }