Files
ConformalLabpp/CLAUDE.md
Tarik Moussa 569a03dc08
All checks were successful
C++ Tests / test-fast (pull_request) Successful in 2m2s
API Docs / doc-build (pull_request) Successful in 1m8s
Markdown link check / check (pull_request) Successful in 52s
C++ Tests / test-cgal (pull_request) Has been skipped
C++ Tests / quality-gates (pull_request) Successful in 1m59s
docs+tooling: doc-freshness gate, documentation-pass policy, token-hygiene
Refresh CLAUDE.md for v0.10.0 (3 CI jobs incl. disabled test-cgal,
compile-time option matrix, reviewer/pages docs, agentic + token-hygiene
workflow patterns) and condense the historical Phase-8/audit logs to
pointers.

Add the documentation-pass process so the single-source-of-truth rules
stay enforced:
  * scripts/quality/check-doc-freshness.sh — string-only drift gate
    (version/date across CITATION/CHANGELOG/CLAUDE, doc-map count), <1 s,
    registered in run-all.sh + quality README.
  * doc/release-policy.md — "Documentation passes" subsection (triggers +
    docs:sync rule); fix stale Phase-milestone mapping (v0.10.0 was
    reviewer-ready, 9c→v0.11.0) and the test-cgal CI mention.

Add shared Claude config (un-ignore the two files only):
  * .claude/settings.json — permission allowlist for safe repo commands.
  * .claude/token-hygiene.md — Tier-3 cache-discipline user guide that
    CLAUDE.md instructs Claude to remind the user about.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 06:15:14 +02:00

427 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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](https://github.com/varylab/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](https://depositonce.tu-berlin.de/items/8e2988b2-d991-45b5-aad5-9fb7988f3b2f) · 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:
```bash
# 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_USE_CCACHE=ON` | OFF | Route compiles through ccache. |
| `-DCONFORMALLAB_HEADERS_CHECK=ON` | OFF | Standalone header self-containment check target. |
### Running a single test
```bash
# 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
```bash
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:
```cpp
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 | S² | 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 eq. (4.6) 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](https://github.com/varylab/conformallab) and use it as the reference implementation.
## Test design patterns
### "Natural theta" — constructing a known equilibrium at x* = 0
```cpp
// 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
```cpp
// 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
```cpp
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
```cpp
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 | **active** |
| `test-cgal` | `-DWITH_CGAL_TESTS=ON` | + Boost | pull requests only | **DISABLED 2026-05-26** (`if: false`) |
| `quality-gates` | *(none)* | + codespell, shellcheck | all branches (`needs: test-fast`) | **active** |
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 gated off** (`if: false`, see the DISABLED-2026-05-26 comment block in the workflow): the `-j1` CGAL build is too memory-heavy for the 1.6 GB runner and OOMs intermittently without exposing real regressions. Consequence: the two structural sub-gates that lived inside it — `scripts/check-test-counts.sh` and `scripts/try_it.sh` — are currently **un-gated**; run them locally before tagging a release. Re-enable by restoring `if: github.event_name == 'pull_request'`.
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`](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 at release: **259/259 tests pass, 0 skipped** (23 non-CGAL + 236 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`](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`):
```bash
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`.
## 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 110 with status and sub-tasks | `doc/roadmap/phases.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` |
### 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` |
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_tests``ctest -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`).
- **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 259 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`](.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.html``index.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.