1use crate::primitives::cross2d;
31use crate::types::ProtocolConfig;
32
33pub fn is_convex(ring: &[[i64; 2]]) -> bool {
45 let n = ring.len();
46 if n < 3 {
47 return false;
48 }
49
50 let mut direction: i32 = 0; for i in 0..n {
53 let prev = (i + n - 1) % n;
54 let next = (i + 1) % n;
55
56 let cross = cross2d(
57 ring[prev][0],
58 ring[prev][1],
59 ring[i][0],
60 ring[i][1],
61 ring[next][0],
62 ring[next][1],
63 );
64
65 if cross == 0 {
66 continue;
68 }
69
70 let cross_dir = if cross > 0 { 1i32 } else { -1i32 };
71
72 if direction == 0 {
73 direction = cross_dir;
74 } else if direction != cross_dir {
75 return false; }
77 }
78
79 true }
81
82pub fn validate_edge_lengths(ring: &[[i64; 2]], config: &ProtocolConfig) -> Option<String> {
86 let n = ring.len();
87 for i in 0..n {
88 let j = (i + 1) % n;
89 let dx = (ring[j][0] as i128) - (ring[i][0] as i128);
90 let dy = (ring[j][1] as i128) - (ring[i][1] as i128);
91 let sq_len = (dx * dx + dy * dy) as u128;
92 if sq_len < config.min_edge_length_squared {
93 return Some(format!(
94 "edge {i}→{j} too short: {sq_len} < {}",
95 config.min_edge_length_squared
96 ));
97 }
98 }
99 None
100}
101
102pub fn perimeter_l1(ring: &[[i64; 2]]) -> u128 {
106 let n = ring.len();
107 let mut perimeter: u128 = 0;
108 for i in 0..n {
109 let j = (i + 1) % n;
110 let dx = ring[j][0].abs_diff(ring[i][0]) as u128;
111 let dy = ring[j][1].abs_diff(ring[i][1]) as u128;
112 perimeter += dx + dy;
113 }
114 perimeter
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub struct CompactnessOutcome {
126 pub passes: bool,
127 pub ratio_ppm: u128,
128 pub min_ppm: u128,
129}
130
131pub fn check_compactness(
144 twice_area: u128,
145 perimeter: u128,
146 config: &ProtocolConfig,
147) -> CompactnessOutcome {
148 let min_ppm = config.min_compactness_ppm;
149
150 let perimeter_sq = perimeter.checked_mul(perimeter);
151 let rhs = perimeter_sq.and_then(|p| min_ppm.checked_mul(p));
152 let lhs = 8_000_000u128.checked_mul(twice_area);
153
154 let passes = match (lhs, rhs) {
155 (None, _) => true, (Some(_), None) => false, (Some(l), Some(r)) => l >= r,
158 };
159
160 let ratio_ppm = match (lhs, perimeter_sq) {
162 (None, _) => u128::MAX,
163 (_, None) | (_, Some(0)) => 0,
164 (Some(l), Some(p_sq)) => l / p_sq,
165 };
166
167 CompactnessOutcome {
168 passes,
169 ratio_ppm,
170 min_ppm,
171 }
172}
173
174pub fn validate_compactness(
181 twice_area: u128,
182 perimeter: u128,
183 config: &ProtocolConfig,
184) -> Option<String> {
185 let outcome = check_compactness(twice_area, perimeter, config);
186 if outcome.passes {
187 None
188 } else {
189 Some(format!(
190 "not compact: {} ppm < min {} ppm",
191 outcome.ratio_ppm, outcome.min_ppm
192 ))
193 }
194}
195
196pub fn validate_part(ring: &[[i64; 2]], config: &ProtocolConfig) -> Option<String> {
210 let n = ring.len();
211
212 if n < 3 {
213 return Some(format!("part has {n} vertices, need >= 3"));
214 }
215
216 if n > config.max_vertices_per_part {
217 return Some(format!(
218 "part has {n} vertices, max is {}",
219 config.max_vertices_per_part
220 ));
221 }
222
223 if !is_convex(ring) {
224 return Some("part is not convex".into());
225 }
226
227 if let Some(err) = validate_edge_lengths(ring, config) {
228 return Some(err);
229 }
230
231 None
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use crate::area::twice_area_fp2;
238 use crate::types::ProtocolConfig;
239
240 const M: i64 = 1_000_000;
241
242 fn merca_config() -> ProtocolConfig {
243 ProtocolConfig::merca()
244 }
245
246 #[test]
247 fn is_convex_accepts_square() {
248 let ring = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
249 assert!(is_convex(&ring));
250 }
251
252 #[test]
253 fn is_convex_rejects_l_shape() {
254 let ring = vec![
256 [0, 0],
257 [20 * M, 0],
258 [20 * M, 10 * M],
259 [10 * M, 10 * M],
260 [10 * M, 20 * M],
261 [0, 20 * M],
262 ];
263 assert!(!is_convex(&ring));
264 }
265
266 #[test]
267 fn is_convex_accepts_weakly_convex_with_collinear() {
268 let ring = vec![
270 [0, 0],
271 [10 * M, 0],
272 [10 * M, 10 * M],
273 [5 * M, 10 * M],
274 [0, 10 * M],
275 ];
276 assert!(is_convex(&ring));
277 }
278
279 #[test]
280 fn is_convex_accepts_triangle() {
281 let ring = vec![[0, 0], [10 * M, 0], [5 * M, 10 * M]];
282 assert!(is_convex(&ring));
283 }
284
285 #[test]
286 fn is_convex_rejects_two_points() {
287 let ring = vec![[0, 0], [1, 0]];
288 assert!(!is_convex(&ring));
289 }
290
291 #[test]
292 fn is_convex_accepts_convex_pentagon() {
293 let ring = vec![[0, 0], [2 * M, 0], [3 * M, M], [2 * M, 2 * M], [0, 2 * M]];
294 assert!(is_convex(&ring));
295 }
296
297 #[test]
298 fn validate_edge_lengths_valid() {
299 let ring = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
301 assert!(validate_edge_lengths(&ring, &merca_config()).is_none());
302 }
303
304 #[test]
305 fn validate_edge_lengths_rejects_short_edge() {
306 let ring = vec![[0, 0], [M / 2, 0], [M / 2, M], [0, M]]; assert!(validate_edge_lengths(&ring, &merca_config()).is_some());
310 }
311
312 #[test]
313 fn validate_edge_lengths_exact_threshold() {
314 let ring = vec![[0, 0], [M, 0], [M, M], [0, M]];
316 assert!(validate_edge_lengths(&ring, &merca_config()).is_none());
317 }
318
319 #[test]
320 fn validate_edge_lengths_accepts_large_negative_coordinates() {
321 let ring = vec![[-1_000_000, 0], [1_000_000, 0]];
322 assert!(validate_edge_lengths(&ring, &merca_config()).is_none());
323 }
324
325 #[test]
326 fn validate_edge_lengths_rejects_unit_edge() {
327 let ring = vec![[0, 0], [1, 1], [1, 0]];
328 assert!(validate_edge_lengths(&ring, &merca_config()).is_some());
329 }
330
331 #[test]
332 fn perimeter_l1_uses_manhattan() {
333 let ring = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
335 let p = perimeter_l1(&ring);
336 assert_eq!(p, 4 * 10 * M as u128);
337 }
338
339 #[test]
340 fn perimeter_l1_not_euclidean() {
341 let ring = vec![[0, 0], [3 * M, 0], [0, 4 * M]]; let p = perimeter_l1(&ring);
346 assert_eq!(p, (3 + 7 + 4) * M as u128);
347 }
348
349 #[test]
350 fn perimeter_l1_handles_negative_coordinates() {
351 let ring = vec![[-1, -1], [-1, 1], [1, 1], [1, -1]];
352 assert_eq!(perimeter_l1(&ring), 8);
353 }
354
355 #[test]
356 fn perimeter_l1_large_square_does_not_overflow() {
357 let ring = vec![
358 [10_000_000 * M, 10_000_000 * M],
359 [11_000_000 * M, 10_000_000 * M],
360 [11_000_000 * M, 11_000_000 * M],
361 [10_000_000 * M, 11_000_000 * M],
362 ];
363 assert_eq!(perimeter_l1(&ring), 4 * 1_000_000_000_000u128);
364 }
365
366 #[test]
367 fn validate_compactness_compact_square_passes() {
368 let ring = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
370 let twice_area = twice_area_fp2(&ring);
371 let perimeter = perimeter_l1(&ring);
372 assert!(validate_compactness(twice_area, perimeter, &merca_config()).is_none());
373 }
374
375 #[test]
376 fn validate_compactness_uses_checked_mul() {
377 let ring = vec![
379 [0, 0],
380 [10_000_000 * M, 0],
381 [10_000_000 * M, 10_000_000 * M],
382 [0, 10_000_000 * M],
383 ];
384 let twice_area = twice_area_fp2(&ring);
385 let perimeter = perimeter_l1(&ring);
386 let _ = validate_compactness(twice_area, perimeter, &merca_config());
388 }
389
390 #[test]
391 fn validate_part_valid_square() {
392 let ring = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
393 assert!(validate_part(&ring, &merca_config()).is_none());
394 }
395
396 #[test]
397 fn validate_part_square_passes_both_configs() {
398 let ring = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
399 let permissive = ProtocolConfig::permissive();
400 assert!(validate_part(&ring, &merca_config()).is_none());
401 assert!(validate_part(&ring, &permissive).is_none());
402 }
403
404 #[test]
405 fn validate_part_short_square_fails_merca_but_passes_permissive() {
406 let ring = vec![[0, 0], [M / 2, 0], [M / 2, M / 2], [0, M / 2]];
407 let permissive = ProtocolConfig::permissive();
408 assert!(validate_part(&ring, &merca_config()).is_some());
409 assert!(validate_part(&ring, &permissive).is_none());
410 }
411
412 #[test]
413 fn validate_part_rejects_l_shape() {
414 let ring = vec![
415 [0, 0],
416 [20 * M, 0],
417 [20 * M, 10 * M],
418 [10 * M, 10 * M],
419 [10 * M, 20 * M],
420 [0, 20 * M],
421 ];
422 assert!(validate_part(&ring, &merca_config()).is_some());
423 }
424
425 #[test]
426 fn validate_part_rejects_too_few_vertices() {
427 assert!(validate_part(&[[0, 0], [M, 0]], &merca_config()).is_some());
428 }
429}