feat(phase6): exact hyperbolic layout, Gauss–Bonnet, cut graph, normalisation — 121 tests
New files:
- gauss_bonnet.hpp: euler_characteristic, genus, Σ(2π-Θ_v) sum/rhs/deficit,
check_gauss_bonnet (throws), enforce_gauss_bonnet (correct sign: Δ=(lhs-rhs)/V)
- cut_graph.hpp: CutGraph struct + compute_cut_graph (tree-cotree, Erickson–Whittlesey
2005); boundary edges correctly excluded from cut set
- test_phase6.cpp: 26 new tests (GaussBonnet ×8, CutGraph ×6, HyperbolicTrilateration
×4, Normalisation ×4 — all pass)
layout.hpp (Phase 6 rewrite):
- detail::trilaterate_hyp: exact Möbius + hyperbolic law of cosines replacing old tanh(d/2)
- detail::center_poincare_disk: Möbius centering for hyperbolic normalisation
- normalise_euclidean: centroid → origin + PCA major-axis rotation
- normalise_hyperbolic: Möbius centering in the Poincaré disk
- normalise_spherical: Rodrigues rotation → north pole
- euclidean_layout / hyper_ideal_layout: optional CutGraph* + HolonomyData* + normalise
Bug fixes caught by new tests:
- gauss_bonnet.hpp: enforce_gauss_bonnet had wrong sign for delta
- cut_graph.hpp: boundary edges were incorrectly marked as cut edges
121 tests pass, 2 skipped (Hessian stubs).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>