wintf 0.0.3

Windows Tategaki Framework - Rust UI library with Japanese vertical text support
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
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
//! ドラッグ状態管理
//!
//! thread_local! + RefCellパターンでwndproc層のドラッグ状態を管理する。
//!
//! 各状態には「どの出来事で入り、どの出来事で出るか」を書く([`DragState`])。
//! 解放・中断のあとは `JustEnded` で次の左押下まで休み、`Idle` へは戻らない。

use crate::ecs::Point;
use crate::ecs::drag::DragConstraint;
use crate::ecs::drag::capture_guard::CaptureGuard;
use crate::ecs::pointer::PhysicalPoint;
use bevy_ecs::entity::Entity;
use std::cell::RefCell;
use std::time::Instant;
use windows::Win32::Foundation::HWND;

/// ドラッグ状態(thread_local!で管理)
///
/// `CaptureGuard` を内包するため Clone 不可。
/// 読み取り専用スナップショットが必要な場合は [`DragStateSnapshot`] / [`snapshot_drag_state`] を使用する。
///
/// 左ボタンを押している間か(`Preparing`・`JustStarted`・`Dragging`)は
/// variant を並べずに [`DragState::is_button_held`] で聞く。
#[derive(Debug)]
pub enum DragState {
    /// 押下の無い状態。
    ///
    /// - 入る: 起動時の初期値。製品コードがここへ戻す遷移は無い。
    /// - 出る: 左押下([`start_preparing`])で `Preparing` へ。
    Idle,

    /// 左ボタンを押したが、まだ閾値に達していない状態。
    ///
    /// - 入る: 左押下([`start_preparing`]・`crates/wintf/src/ecs/window_proc/mouse_click.rs`
    ///   の `handle_button_message`)。このときマウスキャプチャを取得する。
    /// - 出る: 閾値到達([`start_dragging`]・`mouse_move.rs`)で `JustStarted` へ/
    ///   解放([`end_dragging`])・中断([`cancel_dragging`])で `JustEnded` へ。
    Preparing {
        /// ドラッグ対象エンティティ
        entity: Entity,
        /// マウス押下位置(物理ピクセル、スクリーン座標)
        start_pos: PhysicalPoint,
        /// 押下時刻
        start_time: Instant,
        /// マウスキャプチャ RAII ガード
        capture_guard: CaptureGuard,
    },

    /// 閾値に達し、ドラッグ開始をまだ配っていない状態。
    ///
    /// - 入る: 閾値到達([`start_dragging`])。
    /// - 出る: 次の tick の `dispatch_drag_events`(`crates/wintf/src/ecs/drag/dispatch.rs`・
    ///   `DragTransition::Started` の腕)が [`update_dragging`] を呼んで `Dragging` へ/
    ///   解放([`end_dragging`])・中断([`cancel_dragging`])で `JustEnded` へ。
    JustStarted {
        /// ドラッグ対象エンティティ
        entity: Entity,
        /// ドラッグ開始位置
        start_pos: PhysicalPoint,
        /// 現在位置
        current_pos: PhysicalPoint,
        /// 開始時刻
        start_time: Instant,
        /// マウスキャプチャ RAII ガード
        capture_guard: CaptureGuard,
    },

    /// ドラッグ中(閾値到達済み・ドラッグ開始を配り終えた)の状態。
    ///
    /// - 入る: `JustStarted` からの [`update_dragging`]。
    /// - 出る: `WM_MOUSEMOVE` の [`update_dragging`] では位置を更新して `Dragging` のまま/
    ///   解放([`end_dragging`])・中断([`cancel_dragging`])で `JustEnded` へ。
    Dragging {
        /// ドラッグ対象エンティティ
        entity: Entity,
        /// ドラッグ開始位置
        start_pos: PhysicalPoint,
        /// 現在位置
        current_pos: PhysicalPoint,
        /// 前回位置
        prev_pos: PhysicalPoint,
        /// 開始時刻
        start_time: Instant,
        // --- WndProc レベルドラッグ用の新規フィールド ---
        /// Window の Win32 ハンドル
        hwnd: HWND,
        /// ドラッグ開始時のウィンドウ位置(クライアント領域スクリーン座標)
        initial_window_pos: Point,
        /// DragConfig.move_window のキャッシュ
        move_window: bool,
        /// DragConstraint のキャッシュ
        constraint: Option<DragConstraint>,
        /// マウスキャプチャ RAII ガード
        capture_guard: CaptureGuard,
    },

    /// 解放または中断のあと、次の左押下までここで休む状態。
    ///
    /// - 入る: 解放([`end_dragging`])または中断([`cancel_dragging`])。
    ///   マウスキャプチャは解放済み。
    /// - 出る: 次の左押下([`start_preparing`])で `Preparing` へ。それまでここで休む。
    ///   製品コードがこの状態を `Idle` へ戻すことは無い。
    JustEnded {
        /// ドラッグ対象エンティティ
        entity: Entity,
        /// 終了位置
        position: PhysicalPoint,
        /// キャンセルされたか
        cancelled: bool,
    },
}

/// ドラッグ状態の読み取り専用スナップショット。
///
/// `CaptureGuard` を含まないため Clone 可能。
/// WndProc ハンドラが状態を判定するために使用する。
/// 各 variant の意味は [`DragState`] の同名の variant と同じ。
#[derive(Debug, Clone)]
pub enum DragStateSnapshot {
    Idle,
    Preparing {
        entity: Entity,
        start_pos: PhysicalPoint,
        start_time: Instant,
    },
    JustStarted {
        entity: Entity,
        start_pos: PhysicalPoint,
        current_pos: PhysicalPoint,
        start_time: Instant,
    },
    Dragging {
        entity: Entity,
        start_pos: PhysicalPoint,
        current_pos: PhysicalPoint,
        prev_pos: PhysicalPoint,
        start_time: Instant,
        hwnd: HWND,
        initial_window_pos: Point,
        move_window: bool,
        constraint: Option<DragConstraint>,
    },
    JustEnded {
        entity: Entity,
        position: PhysicalPoint,
        cancelled: bool,
    },
}

impl DragStateSnapshot {
    /// [`DragState::is_button_held`] と同じ真偽表(写しから聞く)。
    pub fn is_button_held(&self) -> bool {
        matches!(
            self,
            Self::Preparing { .. } | Self::JustStarted { .. } | Self::Dragging { .. }
        )
    }
}

impl DragState {
    /// 左ボタン(製品で [`start_preparing`] を呼ぶのは左押下のみ)を押している間か。
    /// `Preparing`・`JustStarted`・`Dragging` で真、`Idle`・`JustEnded` で偽。
    /// 「ドラッグ中か」ではない(`Preparing` は閾値未到達だが押している)。
    pub fn is_button_held(&self) -> bool {
        matches!(
            self,
            Self::Preparing { .. } | Self::JustStarted { .. } | Self::Dragging { .. }
        )
    }

    /// 読み取り専用スナップショットを生成する。
    pub fn snapshot(&self) -> DragStateSnapshot {
        match self {
            DragState::Idle => DragStateSnapshot::Idle,
            DragState::Preparing {
                entity,
                start_pos,
                start_time,
                ..
            } => DragStateSnapshot::Preparing {
                entity: *entity,
                start_pos: *start_pos,
                start_time: *start_time,
            },
            DragState::JustStarted {
                entity,
                start_pos,
                current_pos,
                start_time,
                ..
            } => DragStateSnapshot::JustStarted {
                entity: *entity,
                start_pos: *start_pos,
                current_pos: *current_pos,
                start_time: *start_time,
            },
            DragState::Dragging {
                entity,
                start_pos,
                current_pos,
                prev_pos,
                start_time,
                hwnd,
                initial_window_pos,
                move_window,
                constraint,
                ..
            } => DragStateSnapshot::Dragging {
                entity: *entity,
                start_pos: *start_pos,
                current_pos: *current_pos,
                prev_pos: *prev_pos,
                start_time: *start_time,
                hwnd: *hwnd,
                initial_window_pos: *initial_window_pos,
                move_window: *move_window,
                constraint: *constraint,
            },
            DragState::JustEnded {
                entity,
                position,
                cancelled,
            } => DragStateSnapshot::JustEnded {
                entity: *entity,
                position: *position,
                cancelled: *cancelled,
            },
        }
    }
}

thread_local! {
    /// グローバルドラッグ状態(単一ドラッグのみ)
    static DRAG_STATE: RefCell<DragState> = const { RefCell::new(DragState::Idle) };
}

/// ドラッグ状態を更新する(wndprocハンドラから呼ばれる)
pub fn update_drag_state<F, R>(f: F) -> R
where
    F: FnOnce(&mut DragState) -> R,
{
    DRAG_STATE.with(|state| {
        let mut state = state.borrow_mut();
        f(&mut state)
    })
}

/// ドラッグ状態を取得する(読み取り専用)
pub fn read_drag_state<F, R>(f: F) -> R
where
    F: FnOnce(&DragState) -> R,
{
    DRAG_STATE.with(|state| {
        let state = state.borrow();
        f(&state)
    })
}

/// ドラッグ状態のスナップショットを取得する(clone 相当)。
///
/// `CaptureGuard` を含まない `DragStateSnapshot` を返す。
/// WndProc ハンドラ等で状態のパターンマッチに使用する。
#[inline]
pub fn snapshot_drag_state() -> DragStateSnapshot {
    read_drag_state(|state| state.snapshot())
}

/// ドラッグ開始準備(WM_LBUTTONDOWN時)
///
/// `hwnd` を使って `SetCapture` を呼び出し、`CaptureGuard` を生成する。
///
/// 捕捉は `DRAG_STATE` の借用を返してから取る。`SetCapture` は前の捕捉の持ち主へ
/// `WM_CAPTURECHANGED` を同期で送り、その手続きがドラッグの状態を借りに来るため。
#[inline]
pub fn start_preparing(entity: Entity, pos: PhysicalPoint, hwnd: HWND) {
    // 既に押している間は無視(複数ボタン同時ドラッグ禁止)
    // JustEndedは許可(前回のドラッグが終了した後の新しいドラッグ)
    if read_drag_state(DragState::is_button_held) {
        tracing::debug!("[drag] Already dragging, ignoring new button press");
        return;
    }

    let capture_guard = CaptureGuard::acquire(hwnd);

    // 置き換えた前の状態は借用の外で落とす(押していない状態なので守りは持たない)
    let _previous = update_drag_state(|state| {
        std::mem::replace(
            state,
            DragState::Preparing {
                entity,
                start_pos: pos,
                start_time: Instant::now(),
                capture_guard,
            },
        )
    });

    tracing::debug!(
        entity = ?entity,
        x = pos.x,
        y = pos.y,
        hwnd = format!("0x{:X}", hwnd.0 as usize),
        "[start_preparing] DragState -> Preparing (with capture)"
    );
}

/// ドラッグ開始(閾値到達時)
///
/// Preparing → JustStarted 遷移。CaptureGuard を引き継ぐ。
#[inline]
pub fn start_dragging(current_pos: PhysicalPoint) {
    update_drag_state(|state| {
        if !matches!(state, DragState::Preparing { .. }) {
            return;
        }

        let old = std::mem::replace(state, DragState::Idle);
        if let DragState::Preparing {
            entity,
            start_pos,
            start_time,
            capture_guard,
        } = old
        {
            tracing::debug!(
                entity = ?entity,
                start_x = start_pos.x,
                start_y = start_pos.y,
                current_x = current_pos.x,
                current_y = current_pos.y,
                "[drag] Dragging started"
            );

            *state = DragState::JustStarted {
                entity,
                start_pos,
                current_pos,
                start_time,
                capture_guard,
            };
        }
    });
}

/// ドラッグ移動(WM_MOUSEMOVE時)
///
/// JustStarted → Dragging 遷移時に WindowDragContextResource から
/// HWND・初期位置・制約情報を読み取り、DragState::Dragging にセットする。
/// CaptureGuard は `std::mem::replace` で所有権を移動する。
#[inline]
pub fn update_dragging(
    current_pos: PhysicalPoint,
    drag_context: Option<&crate::ecs::drag::WindowDragContextResource>,
) {
    update_drag_state(|state| {
        match state {
            DragState::JustStarted { .. } => {
                let old = std::mem::replace(state, DragState::Idle);
                if let DragState::JustStarted {
                    entity,
                    start_pos,
                    start_time,
                    capture_guard,
                    ..
                } = old
                {
                    // WindowDragContextResource から Window 情報を読み取り
                    let (hwnd, initial_window_pos, move_window, constraint) =
                        if let Some(ctx_res) = drag_context {
                            if let Some(ctx) = ctx_res.get() {
                                (
                                    ctx.hwnd.unwrap_or(HWND::default()),
                                    ctx.initial_window_pos.unwrap_or(Point { x: 0, y: 0 }),
                                    ctx.move_window,
                                    ctx.constraint,
                                )
                            } else {
                                (HWND::default(), Point { x: 0, y: 0 }, false, None)
                            }
                        } else {
                            (HWND::default(), Point { x: 0, y: 0 }, false, None)
                        };

                    tracing::debug!(
                        entity = ?entity,
                        hwnd = format!("0x{:X}", hwnd.0 as usize),
                        initial_x = initial_window_pos.x,
                        initial_y = initial_window_pos.y,
                        move_window = move_window,
                        "[update_dragging] JustStarted -> Dragging with WindowDragContext"
                    );

                    *state = DragState::Dragging {
                        entity,
                        start_pos,
                        current_pos,
                        prev_pos: current_pos,
                        start_time,
                        hwnd,
                        initial_window_pos,
                        move_window,
                        constraint,
                        capture_guard,
                    };
                }
            }
            DragState::Dragging { .. } => {
                let old = std::mem::replace(state, DragState::Idle);
                if let DragState::Dragging {
                    entity,
                    start_pos,
                    current_pos: old_pos,
                    start_time,
                    hwnd,
                    initial_window_pos,
                    move_window,
                    constraint,
                    capture_guard,
                    ..
                } = old
                {
                    *state = DragState::Dragging {
                        entity,
                        start_pos,
                        current_pos,
                        prev_pos: old_pos,
                        start_time,
                        hwnd,
                        initial_window_pos,
                        move_window,
                        constraint,
                        capture_guard,
                    };
                }
            }
            _ => {}
        }
    });
}

/// ドラッグを休ませた結果。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DragClose {
    /// 休ませた。`notified` は終了の種を積んだか(前の状態が `JustStarted`/`Dragging` で、
    /// wndproc 側の控えへ渡せたとき真)。
    Closed { entity: Entity, notified: bool },
    /// 離した窓が、捕捉を取った窓と違う(何もしていない)。
    OtherWindow,
    /// 押している状態ではなかった(何もしていない)。
    NotActive,
}

/// 押している状態(`Preparing`・`JustStarted`・`Dragging`)なら、
/// 捕捉の守り・押した位置・最後の画面の座標を返す。
fn held_parts(state: &mut DragState) -> Option<(&mut CaptureGuard, PhysicalPoint, PhysicalPoint)> {
    match state {
        DragState::Preparing {
            capture_guard,
            start_pos,
            ..
        } => Some((capture_guard, *start_pos, *start_pos)),
        DragState::JustStarted {
            capture_guard,
            start_pos,
            current_pos,
            ..
        }
        | DragState::Dragging {
            capture_guard,
            start_pos,
            current_pos,
            ..
        } => Some((capture_guard, *start_pos, *current_pos)),
        DragState::Idle | DragState::JustEnded { .. } => None,
    }
}

/// ドラッグを休ませる 1 か所。
///
/// `pick` が押している状態を見て (終了位置, 取り消しか) を返したときだけ休ませる。
/// 順は「状態を `JustEnded` へ移す → 前の状態が開始済みなら終了の種を積む →
/// `DRAG_STATE` の借用を返す → 捕捉の守りを落とす」。守りの `ReleaseCapture` は
/// `WM_CAPTURECHANGED` を同期で送るので、借用の中で落とすと入れ子の借用になる。
fn close_drag(
    pick: impl FnOnce(&mut DragState) -> Result<(PhysicalPoint, bool), DragClose>,
) -> DragClose {
    let (result, _guard) = update_drag_state(|state| {
        let (position, cancelled) = match pick(state) {
            Ok(v) => v,
            Err(result) => return (result, None),
        };
        let (entity, started, capture_guard) = match std::mem::replace(state, DragState::Idle) {
            DragState::Preparing {
                entity,
                capture_guard,
                ..
            } => (entity, false, capture_guard),
            DragState::JustStarted {
                entity,
                capture_guard,
                ..
            }
            | DragState::Dragging {
                entity,
                capture_guard,
                ..
            } => (entity, true, capture_guard),
            // pick は押している状態でしか Ok を返さない。届いたら元へ戻して何もしない。
            other => {
                tracing::error!(event = "drag_close_unexpected_state", state = ?other);
                debug_assert!(false, "close_drag: pick が押していない状態で Ok を返した");
                *state = other;
                return (DragClose::NotActive, None);
            }
        };
        *state = DragState::JustEnded {
            entity,
            position,
            cancelled,
        };
        // 開始を配ったドラッグだけが終了の種を積む(閾値前は積まない)
        let notified =
            started && crate::ecs::drag::accumulator::push_ended_seed(entity, position, cancelled);

        tracing::debug!(
            entity = ?entity,
            x = position.x,
            y = position.y,
            cancelled,
            notified,
            "[drag] Dragging ended"
        );
        (DragClose::Closed { entity, notified }, Some(capture_guard))
    });
    // _guard がここでドロップ → ReleaseCapture が RefCell borrow 外で実行される
    result
}

/// ドラッグ終了(位置と取り消しの印を指定して休ませる)。
///
/// 戻り値は [`DragClose`](以前は `()`)。前の状態が `JustStarted`/`Dragging` なら、
/// 呼ぶだけで終了の種(`DragTransition::Ended`)が累積器へ積まれる。呼び出し側でも
/// 積むと二重になる(2 件目は累積器の入口で捨てられる)。
#[inline]
pub fn end_dragging(position: PhysicalPoint, cancelled: bool) -> DragClose {
    close_drag(|state| {
        held_parts(state)
            .map(|_| (position, cancelled))
            .ok_or(DragClose::NotActive)
    })
}

/// ドラッグキャンセル(ESCキー、WM_CANCELMODE、非活性化時)。押した位置・取り消しの印つきで休ませる。
///
/// 戻り値は [`DragClose`](以前は `()`)。種の規則は [`end_dragging`] と同じで、
/// 呼ぶだけで終了の種が積まれる(呼び出し側でも積むと二重になる)。
#[inline]
pub fn cancel_dragging() -> DragClose {
    close_drag(|state| {
        held_parts(state)
            .map(|(_, start_pos, _)| (start_pos, true))
            .ok_or(DragClose::NotActive)
    })
}

/// マウスの捕捉を失ったとき(`WM_CAPTURECHANGED`)。
///
/// 捕捉の守りに解放済みの印を付けてから [`cancel_dragging`] と同じことをする
/// (OS が既に解放しているので `ReleaseCapture` は呼ばない)。呼ぶだけで終了の種が積まれる。
#[inline]
pub fn cancel_dragging_on_capture_lost() -> DragClose {
    close_drag(|state| {
        held_parts(state)
            .map(|(capture_guard, start_pos, _)| {
                capture_guard.mark_released();
                (start_pos, true)
            })
            .ok_or(DragClose::NotActive)
    })
}

/// 左ボタンを離したとき(`WM_LBUTTONUP`)。
///
/// `hwnd` が捕捉を取った窓と同じときだけ、取り消しの印なしで休ませる(違えば
/// [`DragClose::OtherWindow`])。`position` が `None` のときは、状態が持つ最後の画面の座標
/// (`JustStarted`/`Dragging` は current_pos、`Preparing` は start_pos)を使う。
/// 呼ぶだけで終了の種が積まれる(呼び出し側でも積むと二重になる)。
#[inline]
pub fn end_dragging_on_release(hwnd: HWND, position: Option<PhysicalPoint>) -> DragClose {
    close_drag(|state| {
        let (capture_guard, _, last_pos) = held_parts(state).ok_or(DragClose::NotActive)?;
        if capture_guard.hwnd() != hwnd {
            return Err(DragClose::OtherWindow);
        }
        Ok((position.unwrap_or(last_pos), false))
    })
}

/// ドラッグ準備中をDraggingに遷移させるか判定する
///
/// # 整数境界の注意(挙動非破壊で記録)
/// `dx`/`dy` は i32 座標差、`distance_sq = dx*dx + dy*dy` と `threshold_sq = threshold*threshold`
/// はいずれも i32 乗算であり、極値座標差では debug ビルドで桁あふれ panic(`|dx| > 46340` で
/// `dx*dx` が i32::MAX 超過)・release ビルドでラップする理論的経路がある。ただし本番座標は
/// WM lparam 由来の i16 クライアント座標+ウィンドウ位置オフセット(実モニタ幾何で有界)であり、
/// 実用座標では桁あふれしない。なお本関数は **本番呼び出しがゼロ**(ワークスペース全 grep で
/// 呼び出し元はこの in-source テストのみ)で、本番の閾値判定は `window_proc/mouse_move.rs`
/// 内に同一算術がインライン複製されている(W7a 境界)。飽和/checked 化や複製統合は計算結果や
/// 構造を変える挙動変更のため本ループでは適用せず P62 に記録(R2.4/R5.2)。
#[inline]
pub fn check_threshold(current_pos: PhysicalPoint, threshold: i32) -> bool {
    read_drag_state(|state| {
        if let DragState::Preparing { start_pos, .. } = state {
            let dx = current_pos.x - start_pos.x;
            let dy = current_pos.y - start_pos.y;
            let distance_sq = dx * dx + dy * dy;
            let threshold_sq = threshold * threshold;
            let result = distance_sq >= threshold_sq;

            tracing::debug!(
                dx,
                dy,
                distance_sq,
                threshold_sq,
                result,
                "[check_threshold]"
            );

            result
        } else {
            tracing::warn!(state = ?state, "[check_threshold] Not in Preparing state");
            false
        }
    })
}

#[cfg(test)]
mod tests;