1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
# AGENTS.md — gipfel-rs
Guidance for AI coding agents working in this repository.
## What this is
`gipfel-rs` is a Rust port of **gipfel** (a mountain-photo annotator: you load a
photo, mark known peaks, and it fits the camera viewpoint + lens distortion from
the hill database). The original is C++/FLTK (`https://git.flpsed.org/gipfel`,
cloned at `~/gipfel`). This port targets **Cosmic DE** (libcosmic) on Linux.
The port is organized as milestones (M1 core → M5 polish); see `~/gipfel/plan.md`
for the full plan and `~/gipfel-spikes/MEMO.md` for early UI-spike decisions.
## Workspace layout
```
crates/gipfel-core/ headless computational core (no GUI, no native C deps)
hill.rs hill DB: CSV load (Latin-1), duplicate marking
panorama.rs viewpoint, geodesy, refraction, occlusion, visibility
projection/ rectilinear + cylindrical projections
generated/ maxima-generated symbolic functions (DO NOT hand-edit)
lsq.rs pure-Rust Levenberg-Marquardt fit (was GSL; GSL removed)
metadata.rs `gipfel:` JPEG comment + EXIF read/write
profile.rs distortion profiles (FLTK prefs format, XDG data dir)
stitch.rs cylindrical panorama resampling (ScanImage/Stitch port) + TIFF/JPEG out
track.rs GPX track parser (hardened: no XXE, size cap)
crates/gipfel-cli/ CLI: export-position, export-hills, info, stitch, profiles
crates/gipfel-app/ libcosmic GUI app (binary name: `gipfel`)
main.rs app state + Elm-style update/view + sidebar
viewport.rs custom canvas Widget (image + markers + labels)
compass.rs compass-strip orientation widget
dump.rs off-screen image renderer (screen dump)
codegen/ maxima scripts + regen.sh (regenerates projection/generated/)
tests/ C++ reference harnesses (.cxx) + golden files
```
## Build & test
```sh
cargo build --release # app binary: target/release/gipfel
cargo test --lib --test golden # unit + golden tests (the main gate)
cargo test --workspace # everything incl. app-crate tests
cargo clippy --workspace --all-targets
```
- **Rust edition 2021.** No pinned toolchain file.
- **No native C dependencies** since GSL was replaced by a pure-Rust LM. The only
system libs are what libcosmic/wgpu need (Vulkan/GL, fontconfig).
- The dev environment has **no reachable display server** — the GUI app compiles
but cannot be run here. Runtime UI verification is done by the user on a real
Wayland desktop. Do not assume you can launch `gipfel` to test it.
## Golden tests — the critical invariant
`crates/gipfel-core/tests/golden.rs` compares output against files in
`tests/golden/` produced by the **original C++ build**. This is the contract that
keeps the math faithful.
- Fit tests (`fit_rectilinear_matches_c`, `fit_cylindrical_matches_c`): fitted
params within **1e-6 relative**, final projected coords within **1e-9 relative**.
- Function-grid tests: ~1e-15 agreement on the maxima-generated derivatives.
- Several golden tests **skip silently** if the hill DB is missing. They look for
the data file via `$GIPFEL_DATA`, then `$HOME/gipfel/gipfel.dat`. Set
`GIPFEL_DATA=/path/to/gipfel.dat` so they actually run (a skipped golden test
is a green test that verified nothing).
- **Never edit `tests/golden/*` to make a failing test pass.** They are ground
truth from C++. If a golden test fails, your change altered behavior — fix the
code or understand the deviation.
Regenerating goldens (only when the C++ reference changes):
`tests/gen_golden.sh` — needs the built C++ tree (`$GIPFEL_SRC`, default
`~/gipfel/src`), `$GIPFEL_DATA`, g++, GSL, maxima. The `.cxx` harnesses still use
C++ GSL; that is intentional (they are the reference, not part of the Rust build).
## Code generation (maxima)
`crates/gipfel-core/src/projection/generated/*_funcs.rs` are **generated** from
`codegen/lsq_{rectilinear,cylindrical}.mac` via `codegen/regen.sh` (needs maxima;
optional rustfmt). The mathematical models (refraction, cone-visibility,
distortion) and their symbolic derivatives are **frozen** — do not change the
models. If you regenerate, re-run the golden tests immediately.
## Hard compatibility constraints
- **`gipfel.dat` CSV format** and the **`gipfel:` JPEG comment parameter string**
must stay byte-compatible with the C++ version (keys `nick`/`tilt` are kept in
the file even though the code renamed them to pitch/roll).
- **Hill indices are stable handles.** `Panorama::hills` keeps insertion order;
`mark_duplicates` sorts an index permutation, never the vec. Track lists and
known-hill sets index into `hills`. Do not reorder `hills` in place.
- **Terminology:** UI says "Pitch"/"Roll"; internal fields are `a_pitch`/`a_roll`;
hill elevation is `a_elev` (distinct from camera pitch). JPEG keys stay
`nick`/`tilt`.
- Distortion profiles live in `$XDG_DATA_HOME/gipfel/distortion-profiles.prefs`
(FLTK prefs format, legacy auto-import). Keep the `%g`-style float formatting
(`profile::fmt_g`) matching C.
## Code conventions
- **Elm architecture** in the app: `update(&mut self, Message) -> Task` is one
flat, **exhaustive** `match` (no catch-all) — keep it exhaustive so new
`Message` variants are forced to be handled. Prefer extracting small helper
methods over a catch-all arm.
- Shared helpers to reuse (don't duplicate): `slider_item()`, `pick_open`/
`pick_save` (file dialogs), `compass::newton_inverse` (scalar inverse
projection), `viewport::layout_label_y` (label layout shared by screen + dump).
- Sidebar rows use `settings::item_row` with fixed-width labels when fields must
align to equal widths (see the Distortion section).
- libcosmic is **pinned to git rev `430f3d4`** (see `crates/gipfel-app/Cargo.toml`)
and vendors iced 0.14.2. The correct nav-bar return type is
`cosmic::Action<Message>` (crate root), not `cosmic::app::Action`.
- Rendering gotchas: the wgpu renderer draws in fixed layer order (use
`start_layer`/`end_layer` to force overlays above the image); keep strong
`Plain<P>` paragraph refs alive across the frame in widget `Tree` state.
## Platform notes
- **Linux / Cosmic first.** No Windows/macOS support yet.
- A standalone Windows build is an open experiment. The main blocker (GSL) is
already removed. Remaining work: swap libcosmic's `xdg-portal` feature for
`rfd` (file dialogs), port the hill-DB search path (`/usr/share`, `$HOME` →
`dirs` + exe-relative) and font fallback, then get libcosmic itself to compile
for `x86_64-pc-windows-gnu` (upstream does not test Windows).
## Do / Don't
- ✅ Run `cargo test --lib --test golden` (with `GIPFEL_DATA` set) after any
change to the core math, projection, fit, metadata, or stitch code.
- ✅ Keep the build warning-free (rustc `dead_code`/`unused_*` lints are the
signal; clippy may not be installed in every environment).
- ❌ Don't hand-edit `projection/generated/` or `tests/golden/`.
- ❌ Don't change the mathematical models or file formats.
- ❌ Don't assume the GUI can be launched in this environment.
|