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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
//! Shared fixed-point constants for the engine.
//!
//! All constants are constructed as exact rational values using
//! FixedPoint integer division. No floats are used.
use FixedPoint;
/// Below this many nodes, `nearest_semantic` stays on the brute-force scan:
/// a VP-tree build costs O(n log n) distance evaluations, which small stores
/// never amortize (see `docs/SEMANTIC_INDEX.md`).
pub const SEMANTIC_INDEX_MIN_NODES: usize = 256;
/// Maximum number of dimension slices the semantic index caches at once.
/// Eviction is deterministic (lowest key first), not wall-clock LRU.
pub const SEMANTIC_INDEX_MAX_SLICES: usize = 16;
/// Minimum population for `find_outliers`: z-scores over fewer nodes
/// are statistically meaningless, so smaller populations return no outliers.
pub const OUTLIER_MIN_POPULATION: usize = 5;
/// Neighborhood size for `find_outliers`: each node's outlier score is
/// its average distance to this many nearest peers (capped at population−1).
pub const OUTLIER_KNN: usize = 10;
/// 0.5 = 1/2
/// Largest norm/ratio the model represents: `1 − 10⁻¹²`.
///
/// This bounds the greatest expressible hyperbolic distance at
/// `2·atanh(1 − 10⁻¹²) ≈ 28.3`, which with the default τ = 1 is roughly 27
/// levels of nesting. The limit is a deliberate margin, not an arithmetic
/// one: `atanh`/`tanh` round-trip at 0 ULP out to `1 − 10⁻¹⁸`, and `1 − r²`
/// stays representable until about `1 − 10⁻¹⁹`, so this sits six orders of
/// magnitude clear of the floor.
///
/// It was 0.99 until 2026-07-30, which capped distance at 5.29 — about seven
/// levels — and, worse, made `ensure_in_disk` collapse anything deeper back
/// onto this value, so norms cycled instead of growing. See
/// `docs/HYPERBOLIC_INDEX.md`.
/// 0.99999 = 99999/100000 — tanh saturation bound
/// 0.0001 = 1/10000 — standard epsilon for near-zero checks
/// 0.00001 = 1/100000 — small epsilon for origin detection
/// How close to the boundary a point may sit before it is projected back in:
/// `ensure_in_disk` rescales when `1 − ‖p‖² < 10⁻¹²`.
///
/// Matched to [`near_boundary`] so a rescaled point lands *inside* the
/// margin and does not immediately re-trigger. Was 1/1000, which fired at
/// ‖p‖ ≈ 0.9995 — reachable by depth 8 — and rescaled all the way back to
/// 0.99, producing an observable 4-cycle in the norms
/// (0.99 → 0.9963 → 0.9986 → 0.9995 → 0.99) rather than a depth limit.
/// Below this, the Möbius denominator `|1 − p̄q|²` is treated as degenerate.
///
/// Purely a division-safety bound, not a geometric one. `dist_sq ≤ 4` for any
/// two points in the disk, so the quotient stays inside Q64.64 as long as the
/// denominator exceeds `4 / (2⁶³−1) ≈ 8` ULP; 16 ULP doubles that margin.
///
/// This was `epsilon()² = 10⁻⁸` until 2026-07-30 — ten orders of magnitude
/// above the real floor. Since `|1 − p̄q|² = (1 − ‖p‖²)² + ‖p − q‖²` for
/// points at equal radius, ordinary sibling geometry at depth 11 evaluates to
/// ~3.5e-9 and tripped the guard, making the kernel return the saturation
/// value for *every* pair from that depth on. Every node became equidistant,
/// so nearest-neighbour ranking became arbitrary — the real cause of what
/// looked like a spatial-index limit.
/// Largest hyperbolic radius at which a position is still meaningful.
///
/// **This is a measurement of the arithmetic, not a policy choice.** A node
/// sits at radius `depth × tau`, and `1 − ‖p‖² ≈ 4·e^(−r)`, so coordinate
/// *differences* shrink exponentially with radius. In Q64.64, `‖p − q‖²`
/// underflows once `‖p − q‖ < 2^-32`, and the distance kernel then returns its
/// saturation value for every pair — every node equidistant, ranking
/// arbitrary.
///
/// Measured step error along a geodesic, walking outward in steps of 1:
///
/// | radius | error in a unit step |
/// |--------|----------------------|
/// | 16 | 3.4e-8 |
/// | 18 | 8.9e-6 |
/// | 20 | 2.0e-4 |
/// | 22 | 4.5e-3 |
/// | 24 | saturated (28.324) |
///
/// 21 is the last radius comfortably before saturation. Placement beyond it is
/// **refused** rather than warned about: past this point queries do not get
/// slower, they get wrong, and a wrong answer that looks like an answer is the
/// failure mode this engine has already paid for once.
/// Radius slack added to [`max_safe_radius`] before it becomes a gap test.
///
/// Purely a rounding-safety bound, not a geometric one. The guard in
/// `add_node_inner` compares two quantities that are *meant to be equal* at
/// the cap: `1 − ‖p‖²` of a node placed at radius 21 (twenty chained Möbius
/// reflections) against `1 − tanh²(21/2)` (one `tanh`). Both are correct to
/// a handful of ULP, but not the *same* handful, and a strict compare with no
/// slack lets the rounding direction decide whether the documented depth is
/// accepted or refused.
///
/// That happened on the g_math 0.4 → 0.5 upgrade: multiply and divide went
/// from truncation to round-to-nearest, the reflected node at radius 21 moved
/// from 4 ULP inside the line to just past it, and `max_depth()` kept
/// promising a level the store refused. In radius terms the drift is ~1e-10;
/// 2⁻²⁰ is four orders of magnitude above it and far below any level spacing.
/// `1 − ‖p‖²` at [`max_safe_radius`] plus [`safe_radius_slack`], as a
/// squared-norm test.
///
/// Checking the radius directly would need `artanh` (16 µs) on every insert;
/// this is one subtraction and a compare. `1 − tanh²(21/2) ≈ 3.03e-9`.
/// The largest node degree for which `PROOF.md`'s Delaunay hypothesis holds
/// at the given tau.
///
/// The theorem requires `tau >= -log(tan(pi / (2 * d_max)))`. Inverted:
///
/// ```text
/// d_max = pi / (2 * arctan(e^-tau))
/// ```
///
/// Checks against the published table: tau = 1.0 gives 4 (the documented
/// `d_max ~= 4.46`), and 256 children need tau >= 5.094, which gives 256.
///
/// Computed once per network, never on an insert path — it costs an `exp` and
/// an `atan`.
/// Pi, parsed from string for maximum precision
/// Two * pi
/// Golden angle = pi * (3 - sqrt(5))
/// Safe atanh: clamps input to (-0.99, 0.99) then calls gMath's .atanh()
/// Default Sarkar embedding scale factor τ = 1.0.
/// Controls parent-child hyperbolic distance. Q64.64 supports depth ~44/τ.
/// Quantization helper: converts a FixedPoint to an i32 by multiplying
/// by 1000 and rounding to the nearest integer.
/// Replaces the pattern `(x.to_f32() * 1000.0).round() as i32`
/// High-resolution quantization for node-identity position signatures:
/// 2^20 steps per unit (coordinates live inside the unit disk, so the
/// result fits an i32 with room to spare).
///
/// Node identity must NOT use `quantize_1000`: at 1/1000 resolution two
/// depth-2 cousins in *different branches* can quantize to identical
/// signatures at a few hundred nodes (silent data
/// crossover). 2^-20 resolution pushes the same birthday bound past
/// millions of nodes, and rainbow fan-out banding guarantees the true
/// positions are distinct.