Files
ConformalLabpp/CLAUDE.md
Tarik Moussa b57528d92f docs(roadmap): add phase orchestration system mirroring the reviewer audit workflow
Brings doc/roadmap/ to the same operational level as doc/reviewer/ by adding
the two missing structural files and updating existing docs to close the gap
identified in the system review.

New files:
- doc/roadmap/phase-orchestration.md  — master phase table (model assignments,
  session IDs, status, review-gate checklist); mirrors finding-orchestration.md
- doc/roadmap/session-prompts.md      — copy-paste-ready prompts for P1–P4
  (Haiku/Sonnet/Opus) + universal Opus review gate; mirrors reviewer/session-prompts.md

Updated files:
- CLAUDE.md: roadmap section now lists porting-status.md, phase-orchestration.md,
  session-prompts.md; reviewer section adds finding-orchestration.md and
  session-prompts.md; agentic-workflow section has direct "S3 is next / P1 is next"
  entry points so agents don't need to derive the next action from scratch
- doc/roadmap/phases.md: Current-focus table at the top (7 tracks, next session
  per track, gating); cross-links to geometry-central-comparison.md,
  software-landscape.md, complexity.md added in GC and 9b-analytic sections
- doc/roadmap/porting-status.md: snapshot date updated to 2026-05-31 (post S1+S2);
  test count replaced by reference to doc/api/tests.md; §4 gains four new solver
  rows (newton_core refactor, NewtonStatus enum, diagnostics, selectable clamp mode);
  §7 gains four new research-extension rows from S1/S2
- doc/roadmap/research-track.md: header companion-docs section links to
  novelty-statement.md, software-landscape.md, and phase-orchestration.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 08:58:46 +02:00

36 KiB
Raw Permalink Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project purpose and long-term goal

conformallab++ is a C++17 reimplementation of ConformalLab — Stefan Sechelmann's Java research library for discrete conformal geometry (TU Berlin, ~850 commits, v1.0.0 2018). The algorithmic foundation is his dissertation:

Stefan Sechelmann — Variational Methods for Discrete Surface Parameterization: Applications and Implementation, TU Berlin 2016. DOI: 10.14279/depositonce-5415 · CC BY-SA 4.0

The long-term goal is a CGAL package — a submission to the CGAL library that brings discrete conformal maps (hyper-ideal, spherical, Euclidean) to the CGAL ecosystem using CGAL::Surface_mesh as the underlying halfedge data structure, with a traits-class design compatible with arbitrary CGAL-conforming mesh types.

The project has four distinct phase blocks (updated 2026-05-22):

  • Phase 17 (done, v0.7.0): Direct port of the Java library algorithms to C++.
  • Phase 8a MVP + 8b-Lite (done, v0.9.0): CGAL public-API surface for all five DCE models via <CGAL/Discrete_*.h>. Phase 8a.2 (generic FaceGraph), 8c (manuals), 8d (CGAL-test-format), 8e (YAML pipeline) deferred on-demand.
  • Phase 9a + 9b (done, v0.9.0): Two new functionals (CP-Euclidean port, Inversive-Distance research), two new Newton solvers, block-FD HyperIdeal Hessian.
  • Phase 9b-analytic + 9c (planned): Full analytic HyperIdeal Hessian via Schläfli identity (research, see doc/roadmap/research-track.md); 4g-polygon fundamental domain for genus g > 1 (mixed port + research).
  • Phase 10+ (research): Holomorphic differentials, Siegel period matrix Ω ∈ H_g, full uniformization for genus g ≥ 2.

Language

All code, comments, documentation, commit messages, and test descriptions must be in English. The project is intended for international collaboration and CGAL submission. Existing German-language comments in older files should be replaced with English when editing those files.

Build commands

All source lives under code/. Three build modes:

# Mode 1 — fast tests, no CGAL, no Boost, no display (CI default)
cmake -S code -B build
cmake --build build --target conformallab_tests -j$(nproc)
ctest --test-dir build --output-on-failure

# Mode 2 — CGAL tests, headless (CI full, requires Boost headers only)
#   macOS: brew install boost    Linux: apt install libboost-dev
cmake -S code -B build -DWITH_CGAL_TESTS=ON
cmake --build build --target conformallab_cgal_tests -j$(nproc)
ctest --test-dir build -R "^cgal\." --output-on-failure

# Mode 3 — full local build: CLI app + viewer + examples (requires Wayland/X11)
cmake -S code -B build -DWITH_CGAL=ON
cmake --build build -j$(nproc)

-DWITH_CGAL=ON automatically enables -DWITH_VIEWER=ON, which pulls in GLFW and requires wayland-scanner. Never use this in headless CI.

Compile-time options (v0.10.0 matrix)

The CGAL test build defaults to PCH + Unity Build ON (CGAL test wall-time 78 s → 55 s, CPU time 676 s → 167 s on Apple M1). Opt-in/opt-out flags — full measurements + macOS-vs-Linux notes in doc/architecture/compile-time.md:

Flag Default Effect
-DBUILD_TESTING=OFF ON Skip the entire test subtree (headers-only consumers).
-DCONFORMALLAB_USE_PCH=OFF ON Disable precompiled headers for conformallab_cgal_tests.
-DCMAKE_UNITY_BUILD=OFF ON Disable Unity (jumbo) build.
-DCONFORMALLAB_DEV_BUILD=ON OFF Dev iteration: PCH on, Unity forced off (cheaper incremental rebuilds).
-DCONFORMALLAB_FAST_TEST_BUILD=ON OFF -O0 -g for tests (faster compile, slower run).
-DCONFORMALLAB_LOW_MEMORY_BUILD=ON OFF RAM-constrained CI (Raspberry Pi): -O0 (no -g), PCH off, unity batch 1, --no-keep-memory linker. Drops cc1plus peak from ~700 MB to ~150-200 MB per TU so the CGAL build fits in a 2 GB container. Tests run ~15× slower but all pass. Use with -j1.
-DCONFORMALLAB_USE_CCACHE=ON OFF Route compiles through ccache.
-DCONFORMALLAB_HEADERS_CHECK=ON OFF Standalone header self-containment check target.

Running a single test

# By GTest suite/test name
./build/conformallab_cgal_tests --gtest_filter="NewtonSolver*"
./build/conformallab_tests       --gtest_filter="Clausen*"

# By CTest regex (prefix "cgal." for all CGAL tests)
ctest --test-dir build -R "cgal.NewtonSolver" --output-on-failure

Rebuilding the CI Docker image

docker buildx build \
  --platform linux/arm64 \
  -f .gitea/docker/Dockerfile.ci-cpp \
  -t git.eulernest.eu/conformallab/ci-cpp:latest \
  --push \
  .gitea/docker/

Architecture

Everything is header-only

All algorithms live in code/include/*.hpp. There is no compiled library. The three CMake targets (conformallab_tests, conformallab_cgal_tests, conformallab_core) compile headers directly from their .cpp entry points. To add a new algorithm: create a .hpp in code/include/, add a test in code/tests/cgal/, and register the test file in code/tests/cgal/CMakeLists.txt.

Central type: ConformalMesh

conformal_mesh.hpp defines the core type:

using ConformalMesh = CGAL::Surface_mesh<Point3>;  // CGAL::Simple_cartesian<double>

This replaces the Java CoHDS (half-edge data structure) and its intrusive CoVertex/CoEdge/CoFace types. Data is attached via named CGAL property maps instead of intrusive fields:

Property map name Type Meaning
"v:lambda" double per vertex log scale factor (conformal variable uᵢ)
"v:theta" double per vertex target cone angle Θᵥ
"v:idx" int per vertex solver DOF index; -1 = pinned/boundary
"e:alpha" double per edge intersection angle αᵢⱼ (hyperbolic only)
"f:type" int per face geometry type (0=Euclidean, 1=Hyperbolic, 2=Spherical)

CGAL_DISABLE_GMP and CGAL_DISABLE_MPFR are defined for all CGAL targets — the library deliberately uses Simple_cartesian<double> (floating-point, no exact arithmetic) because conformal geometry does not require exact predicates.

The five DCE models

Each model has its own Maps struct that bundles all property maps, plus a functional, optional Hessian, Newton solver, and (since v0.9.0) a CGAL public-API entry function:

Model Space DOFs Maps struct Key headers Newton function CGAL entry
Euclidean ℝ² vertex EuclideanMaps euclidean_functional.hpp, euclidean_hessian.hpp newton_euclidean() discrete_conformal_map_euclidean()
Spherical vertex SphericalMaps spherical_functional.hpp, spherical_hessian.hpp newton_spherical() discrete_conformal_map_spherical()
Hyper-ideal H² (Poincaré disk) vertex + edge HyperIdealMaps hyper_ideal_functional.hpp, hyper_ideal_hessian.hpp (block-FD, Phase 9b) newton_hyper_ideal() discrete_conformal_map_hyper_ideal()
CP-Euclidean (BPS 2010) face-based circle packing face CPEuclideanMaps cp_euclidean_functional.hpp newton_cp_euclidean() discrete_circle_packing_euclidean()
Inversive-Distance (Luo 2004) vertex-based circle packing vertex InversiveDistanceMaps inversive_distance_functional.hpp newton_inversive_distance() discrete_inversive_distance_map()

DOF-assignment patterns:

  • Vertex-only models (Euclidean, Spherical, Inversive-Distance): pin one vertex manually (maps.v_idx[first_vertex] = -1) then assign sequential indices. The CGAL public entries do this automatically with the "natural-theta" trick (so calling them with no arguments returns x = 0 as the equilibrium).
  • HyperIdeal: assign_all_dof_indices(mesh, maps) assigns vertex + edge DOFs automatically.
  • CP-Euclidean: face-based — assign_cp_euclidean_face_dof_indices(mesh, maps, pinned_face) pins one face and indexes the rest.

The full pipeline

load_mesh()                          → ConformalMesh  (OFF/OBJ/PLY)
setup_*_maps(mesh)                   → *Maps  (property maps created, all zero)
compute_*_lambda0_from_mesh(mesh, m) → λ° initialised from 3-D edge lengths
DOF assignment                       → v_idx[v] set; -1 = pinned
check_gauss_bonnet(mesh, maps)       → throws if Σ(2πΘᵥ) ≠ 2π·χ(M)
enforce_gauss_bonnet(mesh, maps)     → redistributes angle defect uniformly
newton_*(mesh, x0, maps)             → NewtonResult{x*, iterations, converged}
compute_cut_graph(mesh)              → CutGraph  (2g seam edges, tree-cotree)
*_layout(mesh, x*, maps, &cg, &hol) → Layout2D/3D  + HolonomyData
normalise_*(layout)                  → canonical position (PCA / Möbius / Rodrigues)
compute_period_matrix(hol)           → PeriodData{τ∈ℍ}  (genus 1 flat torus)
compute_fundamental_domain(hol)      → FundamentalDomain{vertices, generators}
tiling_neighbourhood(layout, hol)    → vector of translated layout copies
save_result_json/xml()               → serialised result

After compute_*_lambda0_from_mesh() the original vertex positions are no longer used — all subsequent computation is in log-length/scale-factor space.

Newton solver (newton_solver.hpp)

Gradient sign convention differs across the five models:

  • Euclidean / Spherical / Inversive-Distance: G_v = Θ_v actual_angle_sum (target minus actual).
  • HyperIdeal: G_v = actual_angle_sum Θ_v (actual minus target).
  • CP-Euclidean: G_f = φ_f Σ_{h:face(h)=f} (p(θ*,Δρ) + θ*) (face-based; see cp_euclidean_functional.hpp header for the full formula).

Hessian sign and solver per model:

  • Euclidean: H is PSD (cotangent Laplacian) → SimplicialLDLT(H).
  • Spherical: H is NSD (concave energy) → SimplicialLDLT(H) (sign flip inside newton_spherical).
  • HyperIdeal: H is PSD (strictly convex) → SimplicialLDLT(H). Phase 9b uses a block-FD Hessian (per-face 6×6 local block, ~96× speed-up vs full FD on V=200). Full analytic Hessian via the chain (bᵢ, aₑ) → lᵢⱼ → ζ₁₃/ζ₁₄/ζ₁₅ → αᵢⱼ/βᵢ is planned research — see doc/roadmap/research-track.md Phase 9b-analytic.
  • CP-Euclidean: analytic 2×2-per-edge h_jk = sin θ / (cosh Δρ cos θ) (BPS 2010), strictly convex → SimplicialLDLT(H).
  • Inversive-Distance: FD Hessian (inline in newton_inversive_distance). Analytic via Glickenstein 2011 §5.2 is planned research (Phase 9a.2-analytic).

When SimplicialLDLT fails (rank-deficient H — gauge mode on a closed mesh without pinned vertex/face), the solver automatically retries with Eigen::SparseQR to find the minimum-norm step orthogonal to the null space. Public API: solve_linear_system(H, rhs, &used_fallback).

Layout and holonomy (layout.hpp)

BFS-trilateration with a priority min-heap on BFS depth (depth = max(depth[src], depth[tgt]) + 1). Root face = largest 3-D area face. This minimises trilateration error accumulation compared to simple BFS.

Key output fields:

  • layout.uv[v.idx()] — primary UV (first/shallowest BFS visit per vertex)
  • layout.halfedge_uv[h.idx()] — UV of source(h) as seen from face(h); at seam halfedges the two opposite halfedges carry different UV values, enabling proper GPU texture atlasing without vertex duplication
  • hol.translations[i] — lattice generator ωᵢ ∈ (Euclidean/spherical)
  • hol.mobius_maps[i] — Möbius isometry Tᵢ ∈ SU(1,1) (hyperbolic, Poincaré disk)

MobiusMap is defined in layout.hpp: T(z) = (az+b)/(cz+d). Key methods: from_three() (fit to 3 point correspondences via 3×3 complex linear system), compose(), inverse(), apply(Vector2d).

Key mathematical reference for each header

Header Java original Key reference
hyper_ideal_geometry.hpp HyperIdealGeometry.java Springborn (2020) — ζ₁₃/ζ₁₄/ζ₁₅ functions
euclidean_hessian.hpp EuclideanHessian.java Pinkall & Polthier (1993) — cotangent Laplacian
spherical_hessian.hpp SphericalHessian.java ∂α/∂u from spherical law of cosines
cut_graph.hpp CuttingUtility.java Erickson & Whittlesey (SODA 2005) — tree-cotree
period_matrix.hpp PeriodMatrixUtility.java Sechelmann (2016) §4 — SL(2,) reduction
gauss_bonnet.hpp (distributed across Java) GaussBonnet: Σ(2πΘᵥ) = 2π·χ(M)

Java features not yet ported (Phase 9)

The Java library under de.varylab.discreteconformal contains these items not yet in C++:

Java class Planned C++ header Phase
InversiveDistanceFunctional inversive_distance_functional.hpp 9a
Analytic HyperIdeal Hessian hyper_ideal_hessian.hpp (replace FD) 9b
4g-polygon boundary walk in FundamentalDomainUtility fundamental_domain.hpp (extend) 9c †
DiscreteHarmonicFormUtility Phase 10a prerequisite 10
DiscreteHolomorphicFormUtility Phase 10a 10
HomologyUtility, CanonicalBasisUtility Phase 10 † 10

When porting a Java class, locate the original in de.varylab.discreteconformal.* at github.com/varylab/conformallab and use it as the reference implementation.

† High-precision requirement (Phase 9c / 10): The Java uniformization classes (FundamentalPolygon, CanonicalFormUtility) use RnBig/PnBig/P2Big with MathContext(50) — 50 significant decimal digits. Reason: products of hyperbolic isometry generators grow exponentially, so double fails when verifying the group relation ∏gᵢ = Id. When porting, replicate this locally with boost::multiprecision::cpp_dec_float_50 (or MPFR mpreal) — only inside the uniformization module, NOT globally and NOT in the Eigen solver. The core flattening (Newton/energy) stays double (see conformal_mesh.hpp:45).

Test design patterns

"Natural theta" — constructing a known equilibrium at x* = 0

// Evaluate gradient at x=0; set target angles = actual angle sums → x*=0 by definition
std::vector<double> x0(n_dofs, 0.0);
auto G0 = euclidean_gradient(mesh, x0, maps);
for (auto v : mesh.vertices())
    if (maps.v_idx[v] >= 0)
        maps.theta_v[v] -= G0[maps.v_idx[v]];  // shift so G(x=0) = 0

This is used in virtually every Newton convergence test — it avoids hardcoding specific angle values.

Gradient check pattern

// Copy from any test_*_functional.cpp — GradientCheck_* test suite
double eps = 1e-5;
for (int i = 0; i < n; ++i) {
    xp[i] += eps;  auto Gp = euclidean_gradient(mesh, xp, maps);
    xm[i] -= eps;  auto Gm = euclidean_gradient(mesh, xm, maps);
    double fd = (energy(xp) - energy(xm)) / (2*eps);
    EXPECT_NEAR(G[i], fd, 1e-7);
    xp[i] = xm[i] = x0[i];
}

All new functionals must have a gradient-check test before being considered complete.

Halfedge traversal

for (auto f : mesh.faces()) {
    auto h0 = mesh.halfedge(f);          // canonical halfedge of face
    auto h1 = mesh.next(h0);
    auto h2 = mesh.next(h1);

    Vertex_index v1 = mesh.source(h0);  // = mesh.target(h2)
    Vertex_index v2 = mesh.source(h1);
    Vertex_index v3 = mesh.source(h2);

    // Angle at v3 is opposite to h0 (edge v1v2)
    // h_alpha[h0] = α₃,  h_alpha[h1] = α₁,  h_alpha[h2] = α₂

    bool is_boundary = mesh.is_border(mesh.opposite(h0));
}

Attaching custom data to the mesh

auto [my_map, created] = mesh.add_property_map<Vertex_index, double>("v:my_data", 0.0);
my_map[v] = 3.14;

CI pipeline

Three jobs in .gitea/workflows/cpp-tests.yml:

Job CMake flags Deps Triggers on Status
test-fast (none) Eigen + GTest only all branches (auto) active
test-cgal -DWITH_CGAL_TESTS=ON -DCONFORMALLAB_LOW_MEMORY_BUILD=ON + Boost /test-cgal in commit message active
quality-gates (none) + codespell, shellcheck /quality-gates in commit message active
doc-build (none) Doxygen /docs in commit message or workflow_dispatch active
markdown-links (none) python3 /links in commit message, weekly cron, workflow_dispatch active

⚠ Pi runner limit: use one keyword per commit. The Pi (3-4 GB RAM) cannot run multiple Docker containers simultaneously. /ci-all was removed for this reason. Typical workflow: one commit with /test-cgal, then if green a separate commit with /quality-gates.

Runner: eulernest — self-hosted Raspberry Pi, ARM64, Ubuntu 22.04. Docker image: git.eulernest.eu/conformallab/ci-cpp:latest. test-cgal and quality-gates both need test-fast to pass first (needs: test-fast).

quality-gates runs four required structural gates: license-headers.sh, cgal-conventions.py, codespell.sh, shellcheck.sh --strict. Seven more gates (clang-format, cmake-format, cppcheck, sanitizers, clang-tidy, multi-compiler, reproducible-build) are local-only — see scripts/quality/README.md.

test-cgal is comment-triggered (2026-05-31): write /test-cgal as a comment on any PR to start the CGAL suite manually. Not triggered on every push — the Pi runner (3-4 GB RAM, swap heavily loaded) cannot sustain a build on every WIP commit. LOW_MEMORY_BUILD=ON (-O0, no PCH, unity batch 1) keeps peak cc1plus RAM at ~150-200 MB, fitting in a 2000 MB container. All 277 tests pass in ~31 s run time. The two structural sub-gates (scripts/check-test-counts.sh, scripts/try_it.sh) still run after the test step.

Two other workflows are also restricted to workflow_dispatch: only (auto-trigger disabled 2026-05-26 while the codeberg pages-branch push is being stabilised):

  • .gitea/workflows/doxygen-pages.yml — publishes Doxygen HTML + reviewer hub to the codeberg pages branch.
  • .gitea/workflows/perf-compile-time.yml — Linux ARM64 compile-time benchmark.

Expected results: test-fast + quality-gates green on every push, 0 skipped, 0 failed. The canonical counts live in doc/api/tests.md — do not hardcode them anywhere else (see doc/release-policy.md).

Release state

Current release: v0.10.0 (tag on main, released 2026-05-26 — the "reviewer-ready" release). Phases 19a complete, Phase 8b-Lite CGAL API surface complete (all 5 DCE models reachable via <CGAL/Discrete_*.h>), Phase 9b block-FD HyperIdeal Hessian shipped (~96× speed-up). v0.10.0 added: 100 % Doxygen public-API coverage (396/396 symbols, 0 warnings), a 14-gate structural quality suite (4 required in CI), the reviewer materials package (doc/reviewer/), output_uv_map for 4 of 5 DCE entries, six new roadmap phases + three RESEARCH phases, and a six-mode compile-time workflow matrix. Numbers (single source of truth = doc/api/tests.md): 272/272 tests pass, 0 skipped (26 non-CGAL + 246 CGAL). Next planned milestones: Phase 9c (4g-polygon, genus g > 1) and Phase 9b-analytic (Schläfli identity). See doc/release-policy.md for the version-tag policy, CHANGELOG.md for full release notes, and doc/roadmap/phases.md for the phase plan.

Phase 8 CGAL-package decisions (frozen 2026-05-19)

Locked architecture choices — full design in doc/api/cgal-package.md, locked-vs-flexible split in doc/architecture/locked-vs-flexible.md: MIT license (no LGPL switch) · Named Parameters (CGAL::parameters::...) · default kernel Simple_cartesian<double> · dual-layer wrapper (code/include/*.hpp = implementation, include/CGAL/*.h = thin wrapper, no duplication) · target design is generic FaceGraph + HalfedgeGraph but ships Surface_mesh-only · upstream CGAL submission is pre-submission-ready, not bound (12+ month horizon). Phase 8 extensions (8a.2 generic FaceGraph, 8c manuals, 8d CGAL-format tests, 8e YAML pipeline) are deferred to on-demand.

Port-vs-research maintenance rule

Before claiming something "ports X from Java", verify empirically — if zero matches, it is new research (→ doc/roadmap/research-track.md with citations, not doc/roadmap/java-parity.md):

find /Users/tarikmoussa/Desktop/conformallab -iname "*X*"
grep -r "ClassName" /Users/tarikmoussa/Desktop/conformallab/src

The 2026-05-21 audit corrected four mis-labels (now fixed): InversiveDistanceFunctional, both HyperIdeal Hessians (FD + analytic), and the inversive-distance tutorial were all wrongly tagged "Java port" — they are research (no Java parent; Java declares hasHessian()==false). Details in research-track.md.

Java↔C++ math-correctness audit (2026-05-29)

Full line-by-line audit of all math-critical headers against de.varylab.discreteconformal.* lives in doc/reviewer/java-port-audit.md (read it before re-investigating any of these). All 11 findings are resolved or noted; the four that needed code changes are now FIXED and all CGAL tests pass (246 after the Euclidean holonomy/τ end-to-end tests, the spherical edge-DOF closed-form oracle, and the Java golden-value oracle suites — incl. three full-mesh oracles driving the real EuclideanCyclicFunctional/SphericalFunctional on a shared tetrahedron, one of them an edge-DOF gradient oracle — landed on top; see doc/api/tests.md):

  • Finding 3 (spherical_functional.hpp) — spherical edge-DOF now uses Java's replacement parameterization (Λ = λ_e when the edge is a DOF, via helper spher_eff_lambda); edge gradient is α_opp⁺ + α_opp⁻ θ_e (dropped the extra (S_f⁺+S_f⁻)/2 term). Vertex-only path is bit-for-bit unchanged.
  • Finding 4 (spherical_hessian.hpp) — added an always-compiled throw std::logic_error edge-DOF guard (mirrors Finding 2 for Euclidean).
  • Finding 6 (period_matrix.hpp) — compute_period_matrix now calls the faithful normalizeModulus (matches Java oracle: 0 ≤ Re ≤ ½, Im ≥ 0, |τ| ≥ 1); reduce_to_fundamental_domain retained for the canonical SL(2,) domain.
  • Finding 9 (inversive_distance_functional.hpp) — degenerate-face gradient now uses limiting angles instead of skipping (mirrors Finding 1); the genuinely-non-real l*sq <= 0 skip is kept.

Java golden-value oracles (2026-05-29, P0): five suites now pin the C++ pure-math core bit-for-bit (1e-12) against the compiled Java library (openjdk 17, real Clausen.Л / HyperIdealUtility / DiscreteEllipticUtility.normalizeModulus): HyperIdealGoldenJava (Clausen/Л/ImLi₂, ζ₁₃/₁₄/₁₅/ζ, both tetrahedron-volume formulas), EuclideanGoldenJava (angle formula + 2·Л energy), SphericalGoldenJava (law-of-cosines angles + β relations + Л energy), and PeriodMatrix.NormalizeModulus_GoldenJava (audit missing-test item 7, done). Oracle harnesses live in /tmp/oracle/*.java (recipe in the audit doc).

Full-mesh oracles (2026-05-29): EuclideanGoldenJava.FullMeshGradientAndEnergy_Tetrahedron and SphericalGoldenJava.FullMeshGradientAndEnergy_Tetrahedron drive the real EuclideanCyclicFunctional / SphericalFunctional on a shared tetrahedron (/tmp/oracle/{tet.obj,EucMeshOracle.java,SphereMeshOracle.java}) and pin both the per-vertex gradient (Θ−Σα) and ΔE = E(x)E(0) to 1e-12 — the energy cross-checks C++'s Gauss-Legendre path integral against Java's closed-form functional (audit missing-test item 5, done). Finding surfaced: the spherical oracle must call Java's raw conformalEnergyAndGradient, NOT evaluate() — the latter pre-runs a 1-D Brent maximization over the global-scale gauge (maximizeInNegativeDirection), which C++ deliberately factors into the Newton solver's spherical_gauge_shift instead (both correct; different factoring).

Open follow-ups (tests only, not bugs): (a) the spherical edge-DOF gradient is now Java-oracle'd at the solution level (SphericalGoldenJava.FullMeshEdgeDofGradient_Tetrahedron — vertex + edge components vs raw conformalEnergyAndGradient, locking Finding 3); the only remaining edge-DOF gap is Newton-to-convergence with edge DOFs, blocked by the Finding-4 spherical-Hessian guard (needs an FD-Hessian or guard relaxation first) — a solver feature, not a correctness gap; (b) inversive-distance degenerate-face robustness (item 10) has no Java oracle because the functional is research with no Java parent — only a NaN-free limiting-angle regression test applies. Build/test reminder: the CGAL build dir used for this audit is build-cgal at the repo root (not under code/), target conformallab_cgal_tests, filter ctest -R '^cgal\.'.

Documentation map

38 documents across 7 categories. Read the relevant one before reasoning from scratch — do not hallucinate content that is already written down.

Mathematics & theory

Question Document
What problem does this library solve mathematically? doc/math/discrete-conformal-theory.md
How do the three geometry modes differ (Euclidean/Spherical/HyperIdeal)? doc/math/geometry-modes.md
What analytic invariants can be used to validate correctness? doc/math/validation.md
What are the exact ctest commands with expected terminal output? doc/math/validation-protocol.md
What is the O() complexity and how does it scale with mesh size? doc/math/complexity.md
Which papers are referenced by which header? doc/math/references.md
How does conformallab++ compare to libigl, CGAL, geometry-central, pmp-library? doc/math/software-landscape.md
What is unique about conformallab++ (novelty, target audience)? doc/math/novelty-statement.md

Architecture & design

Question Document
Full pipeline diagram and data-flow overview doc/architecture/overall_pipeline.md
Directory tree, build targets, file organisation doc/architecture/project-structure.md
Key architectural decisions and their rationale doc/architecture/design-decisions.md
Detailed comparison with geometry-central (CMU): overlap, adoption, scientific value doc/architecture/geometry-central-comparison.md
Phase 9a validation report (CP-Euclidean port + Luo-inversive-distance literature check) doc/architecture/phase-9a-validation.md
Compile-time measurements + six-mode workflow matrix (macOS-vs-Linux honesty notes) doc/architecture/compile-time.md
Required vs optional dependencies + standalone-verification recipe doc/architecture/dependencies.md
Which 12 architecture decisions are locked vs still flexible doc/architecture/locked-vs-flexible.md

API & extension

Question Document
All 24 public headers with descriptions doc/api/headers.md
Full pipeline API for all three geometries doc/api/pipeline.md
What does each processing unit require/provide (contracts)? doc/api/contracts.md
How to add a new functional / geometry mode / port from Java doc/api/extending.md
Per-suite breakdown and counts (single source of truth) doc/api/tests.md
Phase 8 CGAL package design + Declarative YAML pipeline spec doc/api/cgal-package.md

Concepts & specs

Question Document
Declarative YAML pipeline: token vocabulary, 5 examples, validation algorithm doc/concepts/declarative-pipeline.md

Roadmap & porting

Question Document
Phases 113 with status, effort, and sub-tasks doc/roadmap/phases.md
Operational truth of which Java maths is in C++ today doc/roadmap/porting-status.md
Which Java classes are ported, which are planned, which are skipped? doc/roadmap/java-parity.md
New research items (beyond Java) — citations, acceptance criteria doc/roadmap/research-track.md
Phase orchestration — model assignments, priority, phase-session mapping doc/roadmap/phase-orchestration.md
Ready-to-paste session prompts for upcoming phases (P1P4) doc/roadmap/session-prompts.md

Tutorials & onboarding

Question Document
Build modes, single-test invocation, CLI, Docker image rebuild doc/getting-started.md
Step-by-step: port the Inversive Distance functional (Phase 9a template) doc/tutorials/add-inversive-distance.md
Language policy, test standards, release flow doc/contributing.md
Versioning rules + release process + single-source-of-truth list doc/release-policy.md

Reviewer materials (v0.10.0)

Question Document
One-page reviewer briefing doc/reviewer/briefing.md
Seven scoped reviewer questions (Q1Q7) doc/reviewer/questions.md
Internal meeting agenda doc/reviewer/agenda.md
Reviewer landing index doc/reviewer/README.md
Hand-curated reviewer landing page (HTML, source-of-truth for codeberg pages) doc/reviewer/hub.html
Audit orchestration — model assignments, session sequence, status tracker doc/reviewer/finding-orchestration.md
Ready-to-paste session prompts for pending audit sessions (S3S6) doc/reviewer/session-prompts.md

The published hub lives at https://tmoussa.codeberg.page/ConformalLabpp/ (Doxygen index at /doxygen.html). See the "Codeberg pages" quirk below for how it is republished.

geometry-central context

geometry-central (Keenan Crane, CMU) implements the same discrete conformal equivalence problem (Gillespie, Springborn & Crane, SIGGRAPH 2021) but uses Ptolemaic flips on intrinsic triangulations instead of Newton on the original mesh. It has no period matrix, holonomy, or spherical geometry mode. The shared mathematical core (Springborn 2020) means cross-validation is meaningful. Full analysis: doc/architecture/geometry-central-comparison.md. Optional adoption roadmap (GC-1/2/3): doc/roadmap/phases.md (Optional section).

Agentic workflow patterns

Recommended loops when working in this repo. Prefer the cheapest gate that catches the class of error you just touched.

  • Add/port an algorithm: new .hpp in code/include/ → test in code/tests/cgal/ (must include a gradient-check, see Test design patterns) → register in code/tests/cgal/CMakeLists.txt → build conformallab_cgal_testsctest -R "^cgal\.". Before claiming "ports X", run the empirical port-vs-research check above.
  • Inner dev loop (fast iteration): -DCONFORMALLAB_DEV_BUILD=ON (PCH on, Unity off) for cheap incremental rebuilds; run a single suite via --gtest_filter. Switch back to the default (Unity on) for a final full build.
  • Before any commit: run the four required gates locally — they mirror CI exactly and are seconds-cheap: bash scripts/quality/license-headers.sh, python3 scripts/quality/cgal-conventions.py, bash scripts/quality/codespell.sh, bash scripts/quality/shellcheck.sh --strict.
  • Before tagging a release: also run the two now-un-gated structural gates (test-cgal is disabled in CI): BUILD_DIR=build bash scripts/check-test-counts.sh and bash scripts/try_it.sh. Update CHANGELOG.md, CITATION.cff, and the doc/api/tests.md counts (single source of truth).
  • Touching public-API headers: rebuild Doxygen (cmake --build build --target doc) and re-check coverage (bash scripts/doxygen-coverage.sh --threshold 100); regenerate doc/api/headers.md via python3 scripts/gen-headers-md.py (or bash scripts/regen-docs.sh).
  • Starting work on an audit finding: open doc/reviewer/finding-orchestration.md, pick the next pending session, copy its prompt from doc/reviewer/session-prompts.md, set the named model, and go. S3 is next (H3/H4/H5/V5/V6, Sonnet → Opus review).
  • Starting work on a roadmap phase: open doc/roadmap/phase-orchestration.md, pick the next pending phase-session, copy its prompt from doc/roadmap/session-prompts.md, set the named model, and go. P1 is next (9g.1 + 9h.1 + 9h.2 + 9d.3, Haiku → Opus review).
  • Landing to main (origin is protected): branch → push to origin → open PR via gh/Gitea API → merge via API → also push codeberg/main directly → keep both remotes in sync.
  • Republishing the reviewer hub: see the Codeberg pages quirk below — manual force-push of an orphan branch; verify the live URL with a cache-bust query.
  • Delegation: this repo's heavy builds are slow on the ARM64 runner — when a task is genuinely parallelisable and independent, consider a background agent; otherwise handle inline. Always verify an agent's actual diff, not just its summary.
  • settings.json: .claude/settings.json (committed) pre-allows the safe read/build/test/quality commands so they don't prompt, and denies destructive git on main. Extend the allowlist as new safe commands recur rather than re-approving each time.

Token hygiene — session-cut (Tier 1, highest impact)

Every turn re-sends the whole conversation, so context accumulation is the largest avoidable cost.

  • One session per task. After a task is done, start a fresh session (/clear) instead of pivoting to an unrelated task in the same thread — the old task's context is dead weight in every later turn.
  • Compact proactively at clean breakpoints. Run /compact <what matters> when a task finishes and before the next starts, rather than waiting for auto-compaction at the limit (which you don't control).
  • Delegate read-heavy sweeps to a subagent. An Explore/Plan subagent reads large amounts in its context and returns a short summary — the bulk never enters the main context. Use it for "where is X used / what depends on Y"; for a known path, Read directly (a subagent starts cold and only pays off on a large search space).

Token hygiene — command discipline (Tier 2)

  • Scope + filter together. Path-scope searches (grep -rn "newton_" code/include/, not repo-wide). Filter test output (ctest -R "cgal.NewtonSolver" --output-on-failure, or --gtest_filter for one suite) instead of dumping all 272 results.
  • Never read raw logs into context. Redirect to a file, then grep/tail it. With run_in_background, read the output file selectively rather than pulling it whole.
  • Don't re-read a file you just edited. Edit errors on stale state — the harness tracks it; a Read-back after a successful edit is wasted context.
  • Reference by line number (newton_solver.hpp:147) instead of re-pasting code blocks.

Cache discipline (Tier 3) is a user-facing guide — see .claude/token-hygiene.md. Remind the user of the relevant Tier-3 rule when you observe the matching anti-pattern (mid-session CLAUDE.md edits, chained sub-5-minute waits, long multi-topic sessions).

Known quirks

  • No GTEST_SKIP stubs remain (since v0.9.0): the three stale HDS-port stub files were removed because the CGAL test suite covers the same functionality with real tests. The pure-math conformallab_tests target now only contains active tests.
  • Boost is header-only: CGAL 6.x uses only Boost headers (Boost.Config, Boost.Graph). No compiled Boost libraries are needed. find_package(Boost REQUIRED) only locates the include path.
  • main branch is protected on origin (Gitea). Push to dev, then merge via pull request. Codeberg main can be pushed to directly.
  • Both remotes must stay in sync: origin = git.eulernest.eu (CI runs here), codeberg = codeberg.org/TMoussa/ConformalLabpp (public mirror, SSH). Push to both after every significant change.
  • Codeberg pages branch is an orphan publish target that serves the reviewer hub + Doxygen HTML at https://tmoussa.codeberg.page/ConformalLabpp/. Its source-of-truth is doc/reviewer/hub.html (installed as index.html) + a local Doxygen build (cmake --build build --target doc, demoted to /doxygen.html). Because the auto-publish workflow (doxygen-pages.yml) is on workflow_dispatch: only, the branch is not refreshed on normal pushes and has been accidentally lost during force-push/merge cleanups. To republish manually: build Doxygen, copy doc/doxygen/html/. into a scratch dir, mv index.html doxygen.html, copy hub.htmlindex.html, then git init -b pages && git commit && git push -f codeberg pages:pages. Codeberg pages caches for ~10 min (Cache-Control: max-age=600) — verify with a cache-busting ?cb=$(date +%s) query. Protect the branch in codeberg settings to prevent deletion (leave force-push allowed so CI/manual republish still works).
  • Both main branches are independent for the pages cycle: origin/main is protected (PR-only); codeberg/main can be pushed directly.