aboutsummaryrefslogtreecommitdiff
path: root/ink_stroke_modeler/stroke_modeler_test.cc
blob: 531b12bf259d237c3340d380a32519116537961e (plain)
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
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "ink_stroke_modeler/stroke_modeler.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "ink_stroke_modeler/internal/type_matchers.h"
#include "ink_stroke_modeler/params.h"

namespace ink {
namespace stroke_model {
namespace {

using ::testing::DoubleNear;
using ::testing::ElementsAre;
using ::testing::FloatNear;
using ::testing::IsEmpty;
using ::testing::Matches;
using ::testing::Not;

constexpr float kTol = 1e-4;

// These parameters use cm for distance and seconds for time.
const StrokeModelParams kDefaultParams{
    .wobble_smoother_params{
        .timeout{.04}, .speed_floor = 1.31, .speed_ceiling = 1.44},
    .position_modeler_params{.spring_mass_constant = 11.f / 32400,
                             .drag_constant = 72.f},
    .sampling_params{.min_output_rate = 180,
                     .end_of_stroke_stopping_distance = .001,
                     .end_of_stroke_max_iterations = 20},
    .stylus_state_modeler_params{.max_input_samples = 20},
    .prediction_params = StrokeEndPredictorParams()};

MATCHER_P2(ResultNearMatcher, expected, tolerance, "") {
  if (Matches(Vec2Near(expected.position, tolerance))(arg.position) &&
      Matches(Vec2Near(expected.velocity, tolerance))(arg.velocity) &&
      Matches(DoubleNear(expected.time.Value(), tolerance))(arg.time.Value()) &&
      Matches(FloatNear(expected.pressure, tolerance))(arg.pressure) &&
      Matches(FloatNear(expected.tilt, tolerance))(arg.tilt) &&
      Matches(FloatNear(expected.orientation, tolerance))(arg.orientation)) {
    return true;
  }

  return false;
}

::testing::Matcher<Result> ResultNear(const Result &expected, float tolerance) {
  return ResultNearMatcher(expected, tolerance);
}

TEST(StrokeModelerTest, NoPredictionUponInit) {
  StrokeModeler modeler;
  ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());
  EXPECT_EQ(modeler.Predict().status().code(),
            absl::StatusCode::kFailedPrecondition);
}

TEST(StrokeModelerTest, InputRateSlowerThanMinOutputRate) {
  const Duration kDeltaTime{1. / 30};

  StrokeModeler modeler;
  ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());

  Time time{0};
  absl::StatusOr<std::vector<Result>> results =
      modeler.Update({.event_type = Input::EventType::kDown,
                      .position = {3, 4},
                      .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results,
              ElementsAre(ResultNear(
                  {.position = {3, 4}, .velocity = {0, 0}, .time{0}}, kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, IsEmpty());

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {3.2, 4.2},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {3.0019, 4.0019},
                                                .velocity = {0.4007, 0.4007},
                                                .time{0.0048}},
                                               kTol),
                                    ResultNear({.position = {3.0069, 4.0069},
                                                .velocity = {1.0381, 1.0381},
                                                .time{0.0095}},
                                               kTol),
                                    ResultNear({.position = {3.0154, 4.0154},
                                                .velocity = {1.7883, 1.7883},
                                                .time{0.0143}},
                                               kTol),
                                    ResultNear({.position = {3.0276, 4.0276},
                                                .velocity = {2.5626, 2.5626},
                                                .time{0.0190}},
                                               kTol),
                                    ResultNear({.position = {3.0433, 4.0433},
                                                .velocity = {3.3010, 3.3010},
                                                .time{0.0238}},
                                               kTol),
                                    ResultNear({.position = {3.0622, 4.0622},
                                                .velocity = {3.9665, 3.9665},
                                                .time{0.0286}},
                                               kTol),
                                    ResultNear({.position = {3.0838, 4.0838},
                                                .velocity = {4.5397, 4.5397},
                                                .time{0.0333}},
                                               kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {3.1095, 4.1095},
                                                .velocity = {4.6253, 4.6253},
                                                .time{0.0389}},
                                               kTol),
                                    ResultNear({.position = {3.1331, 4.1331},
                                                .velocity = {4.2563, 4.2563},
                                                .time{0.0444}},
                                               kTol),
                                    ResultNear({.position = {3.1534, 4.1534},
                                                .velocity = {3.6479, 3.6479},
                                                .time{0.0500}},
                                               kTol),
                                    ResultNear({.position = {3.1698, 4.1698},
                                                .velocity = {2.9512, 2.9512},
                                                .time{0.0556}},
                                               kTol),
                                    ResultNear({.position = {3.1824, 4.1824},
                                                .velocity = {2.2649, 2.2649},
                                                .time{0.0611}},
                                               kTol),
                                    ResultNear({.position = {3.1915, 4.1915},
                                                .velocity = {1.6473, 1.6473},
                                                .time{0.0667}},
                                               kTol),
                                    ResultNear({.position = {3.1978, 4.1978},
                                                .velocity = {1.1269, 1.1269},
                                                .time{0.0722}},
                                               kTol),
                                    ResultNear({.position = {3.1992, 4.1992},
                                                .velocity = {1.0232, 1.0232},
                                                .time{0.0736}},
                                               kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {3.5, 4.2},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {3.1086, 4.1058},
                                                .velocity = {5.2142, 4.6131},
                                                .time{0.0381}},
                                               kTol),
                                    ResultNear({.position = {3.1368, 4.1265},
                                                .velocity = {5.9103, 4.3532},
                                                .time{0.0429}},
                                               kTol),
                                    ResultNear({.position = {3.1681, 4.1450},
                                                .velocity = {6.5742, 3.8917},
                                                .time{0.0476}},
                                               kTol),
                                    ResultNear({.position = {3.2022, 4.1609},
                                                .velocity = {7.1724, 3.3285},
                                                .time{0.0524}},
                                               kTol),
                                    ResultNear({.position = {3.2388, 4.1739},
                                                .velocity = {7.6876, 2.7361},
                                                .time{0.0571}},
                                               kTol),
                                    ResultNear({.position = {3.2775, 4.1842},
                                                .velocity = {8.1138, 2.1640},
                                                .time{0.0619}},
                                               kTol),
                                    ResultNear({.position = {3.3177, 4.1920},
                                                .velocity = {8.4531, 1.6436},
                                                .time{0.0667}},
                                               kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {3.3625, 4.1982},
                                                .velocity = {8.0545, 1.1165},
                                                .time{0.0722}},
                                               kTol),
                                    ResultNear({.position = {3.4018, 4.2021},
                                                .velocity = {7.0831, 0.6987},
                                                .time{0.0778}},
                                               kTol),
                                    ResultNear({.position = {3.4344, 4.2043},
                                                .velocity = {5.8564, 0.3846},
                                                .time{0.0833}},
                                               kTol),
                                    ResultNear({.position = {3.4598, 4.2052},
                                                .velocity = {4.5880, 0.1611},
                                                .time{0.0889}},
                                               kTol),
                                    ResultNear({.position = {3.4788, 4.2052},
                                                .velocity = {3.4098, 0.0124},
                                                .time{0.0944}},
                                               kTol),
                                    ResultNear({.position = {3.4921, 4.2048},
                                                .velocity = {2.3929, -0.0780},
                                                .time{0.1000}},
                                               kTol),
                                    ResultNear({.position = {3.4976, 4.2045},
                                                .velocity = {1.9791, -0.1015},
                                                .time{0.1028}},
                                               kTol),
                                    ResultNear({.position = {3.5001, 4.2044},
                                                .velocity = {1.7911, -0.1098},
                                                .time{0.1042}},
                                               kTol)));

  time += kDeltaTime;
  // We get more results at the end of the stroke as it tries to "catch up" to
  // the raw input.
  results = modeler.Update({.event_type = Input::EventType::kUp,
                            .position = {3.7, 4.4},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {3.3583, 4.1996},
                                                .velocity = {8.5122, 1.5925},
                                                .time{0.0714}},
                                               kTol),
                                    ResultNear({.position = {3.3982, 4.2084},
                                                .velocity = {8.3832, 1.8534},
                                                .time{0.0762}},
                                               kTol),
                                    ResultNear({.position = {3.4369, 4.2194},
                                                .velocity = {8.1393, 2.3017},
                                                .time{0.0810}},
                                               kTol),
                                    ResultNear({.position = {3.4743, 4.2329},
                                                .velocity = {7.8362, 2.8434},
                                                .time{0.0857}},
                                               kTol),
                                    ResultNear({.position = {3.5100, 4.2492},
                                                .velocity = {7.5143, 3.4101},
                                                .time{0.0905}},
                                               kTol),
                                    ResultNear({.position = {3.5443, 4.2680},
                                                .velocity = {7.2016, 3.9556},
                                                .time{0.0952}},
                                               kTol),
                                    ResultNear({.position = {3.5773, 4.2892},
                                                .velocity = {6.9159, 4.4505},
                                                .time{0.1000}},
                                               kTol),
                                    ResultNear({.position = {3.6115, 4.3141},
                                                .velocity = {6.1580, 4.4832},
                                                .time{0.1056}},
                                               kTol),
                                    ResultNear({.position = {3.6400, 4.3369},
                                                .velocity = {5.1434, 4.0953},
                                                .time{0.1111}},
                                               kTol),
                                    ResultNear({.position = {3.6626, 4.3563},
                                                .velocity = {4.0671, 3.4902},
                                                .time{0.1167}},
                                               kTol),
                                    ResultNear({.position = {3.6796, 4.3719},
                                                .velocity = {3.0515, 2.8099},
                                                .time{0.1222}},
                                               kTol),
                                    ResultNear({.position = {3.6916, 4.3838},
                                                .velocity = {2.1648, 2.1462},
                                                .time{0.1278}},
                                               kTol),
                                    ResultNear({.position = {3.6996, 4.3924},
                                                .velocity = {1.4360, 1.5529},
                                                .time{0.1333}},
                                               kTol),
                                    ResultNear({.position = {3.7028, 4.3960},
                                                .velocity = {1.1520, 1.3044},
                                                .time{0.1361}},
                                               kTol)));

  // The stroke is finished, so there's nothing to predict anymore.
  EXPECT_EQ(modeler.Predict().status().code(),
            absl::StatusCode::kFailedPrecondition);
}

TEST(StrokeModelerTest, InputRateFasterThanMinOutputRate) {
  const Duration kDeltaTime{1. / 300};

  StrokeModeler modeler;
  ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());

  Time time{2};
  absl::StatusOr<std::vector<Result>> results =
      modeler.Update({.event_type = Input::EventType::kDown,
                      .position = {5, -3},
                      .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results,
              ElementsAre(ResultNear(
                  {.position = {5, -3}, .velocity = {0, 0}, .time{2}}, kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, IsEmpty());

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {5, -3.1},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(
      *results,
      ElementsAre(ResultNear(
          {.position = {5, -3.0033}, .velocity = {0, -0.9818}, .time{2.0033}},
          kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {5, -3.0153},
                                                .velocity = {0, -2.1719},
                                                .time{2.0089}},
                                               kTol),
                                    ResultNear({.position = {5, -3.0303},
                                                .velocity = {0, -2.6885},
                                                .time{2.0144}},
                                               kTol),
                                    ResultNear({.position = {5, -3.0456},
                                                .velocity = {0, -2.7541},
                                                .time{2.0200}},
                                               kTol),
                                    ResultNear({.position = {5, -3.0597},
                                                .velocity = {0, -2.5430},
                                                .time{2.0256}},
                                               kTol),
                                    ResultNear({.position = {5, -3.0718},
                                                .velocity = {0, -2.1852},
                                                .time{2.0311}},
                                               kTol),
                                    ResultNear({.position = {5, -3.0817},
                                                .velocity = {0, -1.7719},
                                                .time{2.0367}},
                                               kTol),
                                    ResultNear({.position = {5, -3.0893},
                                                .velocity = {0, -1.3628},
                                                .time{2.0422}},
                                               kTol),
                                    ResultNear({.position = {5, -3.0948},
                                                .velocity = {0, -0.9934},
                                                .time{2.0478}},
                                               kTol),
                                    ResultNear({.position = {5, -3.0986},
                                                .velocity = {0, -0.6815},
                                                .time{2.0533}},
                                               kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {4.975, -3.175},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9992, -3.0114},
                                                .velocity = {-0.2455, -2.4322},
                                                .time{2.0067}},
                                               kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9962, -3.0344},
                                                .velocity = {-0.5430, -4.1368},
                                                .time{2.0122}},
                                               kTol),
                                    ResultNear({.position = {4.9924, -3.0609},
                                                .velocity = {-0.6721, -4.7834},
                                                .time{2.0178}},
                                               kTol),
                                    ResultNear({.position = {4.9886, -3.0873},
                                                .velocity = {-0.6885, -4.7365},
                                                .time{2.0233}},
                                               kTol),
                                    ResultNear({.position = {4.9851, -3.1110},
                                                .velocity = {-0.6358, -4.2778},
                                                .time{2.0289}},
                                               kTol),
                                    ResultNear({.position = {4.9820, -3.1311},
                                                .velocity = {-0.5463, -3.6137},
                                                .time{2.0344}},
                                               kTol),
                                    ResultNear({.position = {4.9796, -3.1471},
                                                .velocity = {-0.4430, -2.8867},
                                                .time{2.0400}},
                                               kTol),
                                    ResultNear({.position = {4.9777, -3.1593},
                                                .velocity = {-0.3407, -2.1881},
                                                .time{2.0456}},
                                               kTol),
                                    ResultNear({.position = {4.9763, -3.1680},
                                                .velocity = {-0.2484, -1.5700},
                                                .time{2.0511}},
                                               kTol),
                                    ResultNear({.position = {4.9754, -3.1739},
                                                .velocity = {-0.1704, -1.0564},
                                                .time{2.0567}},
                                               kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {4.9, -3.2},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9953, -3.0237},
                                                .velocity = {-1.1603, -3.7004},
                                                .time{2.0100}},
                                               kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9828, -3.0521},
                                                .velocity = {-2.2559, -5.1049},
                                                .time{2.0156}},
                                               kTol),
                                    ResultNear({.position = {4.9677, -3.0825},
                                                .velocity = {-2.7081, -5.4835},
                                                .time{2.0211}},
                                               kTol),
                                    ResultNear({.position = {4.9526, -3.1115},
                                                .velocity = {-2.7333, -5.2122},
                                                .time{2.0267}},
                                               kTol),
                                    ResultNear({.position = {4.9387, -3.1369},
                                                .velocity = {-2.4999, -4.5756},
                                                .time{2.0322}},
                                               kTol),
                                    ResultNear({.position = {4.9268, -3.1579},
                                                .velocity = {-2.1326, -3.7776},
                                                .time{2.0378}},
                                               kTol),
                                    ResultNear({.position = {4.9173, -3.1743},
                                                .velocity = {-1.7184, -2.9554},
                                                .time{2.0433}},
                                               kTol),
                                    ResultNear({.position = {4.9100, -3.1865},
                                                .velocity = {-1.3136, -2.1935},
                                                .time{2.0489}},
                                               kTol),
                                    ResultNear({.position = {4.9047, -3.1950},
                                                .velocity = {-0.9513, -1.5369},
                                                .time{2.0544}},
                                               kTol),
                                    ResultNear({.position = {4.9011, -3.2006},
                                                .velocity = {-0.6475, -1.0032},
                                                .time{2.0600}},
                                               kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {4.825, -3.2},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9868, -3.0389},
                                                .velocity = {-2.5540, -4.5431},
                                                .time{2.0133}},
                                               kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9636, -3.0687},
                                                .velocity = {-4.1801, -5.3627},
                                                .time{2.0189}},
                                               kTol),
                                    ResultNear({.position = {4.9370, -3.0985},
                                                .velocity = {-4.7757, -5.3670},
                                                .time{2.0244}},
                                               kTol),
                                    ResultNear({.position = {4.9109, -3.1256},
                                                .velocity = {-4.6989, -4.8816},
                                                .time{2.0300}},
                                               kTol),
                                    ResultNear({.position = {4.8875, -3.1486},
                                                .velocity = {-4.2257, -4.1466},
                                                .time{2.0356}},
                                               kTol),
                                    ResultNear({.position = {4.8677, -3.1671},
                                                .velocity = {-3.5576, -3.3287},
                                                .time{2.0411}},
                                               kTol),
                                    ResultNear({.position = {4.8520, -3.1812},
                                                .velocity = {-2.8333, -2.5353},
                                                .time{2.0467}},
                                               kTol),
                                    ResultNear({.position = {4.8401, -3.1914},
                                                .velocity = {-2.1411, -1.8288},
                                                .time{2.0522}},
                                               kTol),
                                    ResultNear({.position = {4.8316, -3.1982},
                                                .velocity = {-1.5312, -1.2386},
                                                .time{2.0578}},
                                               kTol),
                                    ResultNear({.position = {4.8280, -3.2010},
                                                .velocity = {-1.2786, -1.0053},
                                                .time{2.0606}},
                                               kTol),
                                    ResultNear({.position = {4.8272, -3.2017},
                                                .velocity = {-1.2209, -0.9529},
                                                .time{2.0613}},
                                               kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {4.75, -3.225},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9726, -3.0565},
                                                .velocity = {-4.2660, -5.2803},
                                                .time{2.0167}},
                                               kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9381, -3.0894},
                                                .velocity = {-6.2018, -5.9261},
                                                .time{2.0222}},
                                               kTol),
                                    ResultNear({.position = {4.9004, -3.1215},
                                                .velocity = {-6.7995, -5.7749},
                                                .time{2.0278}},
                                               kTol),
                                    ResultNear({.position = {4.8640, -3.1501},
                                                .velocity = {-6.5400, -5.1591},
                                                .time{2.0333}},
                                               kTol),
                                    ResultNear({.position = {4.8319, -3.1741},
                                                .velocity = {-5.7897, -4.3207},
                                                .time{2.0389}},
                                               kTol),
                                    ResultNear({.position = {4.8051, -3.1932},
                                                .velocity = {-4.8133, -3.4248},
                                                .time{2.0444}},
                                               kTol),
                                    ResultNear({.position = {4.7841, -3.2075},
                                                .velocity = {-3.7898, -2.5759},
                                                .time{2.0500}},
                                               kTol),
                                    ResultNear({.position = {4.7683, -3.2176},
                                                .velocity = {-2.8312, -1.8324},
                                                .time{2.0556}},
                                               kTol),
                                    ResultNear({.position = {4.7572, -3.2244},
                                                .velocity = {-1.9986, -1.2198},
                                                .time{2.0611}},
                                               kTol),
                                    ResultNear({.position = {4.7526, -3.2271},
                                                .velocity = {-1.6580, -0.9805},
                                                .time{2.0639}},
                                               kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {4.7, -3.3},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9529, -3.0778},
                                                .velocity = {-5.9184, -6.4042},
                                                .time{2.0200}},
                                               kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9101, -3.1194},
                                                .velocity = {-7.6886, -7.4784},
                                                .time{2.0256}},
                                               kTol),
                                    ResultNear({.position = {4.8654, -3.1607},
                                                .velocity = {-8.0518, -7.4431},
                                                .time{2.0311}},
                                               kTol),
                                    ResultNear({.position = {4.8235, -3.1982},
                                                .velocity = {-7.5377, -6.7452},
                                                .time{2.0367}},
                                               kTol),
                                    ResultNear({.position = {4.7872, -3.2299},
                                                .velocity = {-6.5440, -5.7133},
                                                .time{2.0422}},
                                               kTol),
                                    ResultNear({.position = {4.7574, -3.2553},
                                                .velocity = {-5.3529, -4.5748},
                                                .time{2.0478}},
                                               kTol),
                                    ResultNear({.position = {4.7344, -3.2746},
                                                .velocity = {-4.1516, -3.4758},
                                                .time{2.0533}},
                                               kTol),
                                    ResultNear({.position = {4.7174, -3.2885},
                                                .velocity = {-3.0534, -2.5004},
                                                .time{2.0589}},
                                               kTol),
                                    ResultNear({.position = {4.7056, -3.2979},
                                                .velocity = {-2.1169, -1.6879},
                                                .time{2.0644}},
                                               kTol),
                                    ResultNear({.position = {4.7030, -3.3000},
                                                .velocity = {-1.9283, -1.5276},
                                                .time{2.0658}},
                                               kTol),
                                    ResultNear({.position = {4.7017, -3.3010},
                                                .velocity = {-1.8380, -1.4512},
                                                .time{2.0665}},
                                               kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {4.675, -3.4},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9288, -3.1046},
                                                .velocity = {-7.2260, -8.0305},
                                                .time{2.0233}},
                                               kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.8816, -3.1582},
                                                .velocity = {-8.4881, -9.6525},
                                                .time{2.0289}},
                                               kTol),
                                    ResultNear({.position = {4.8345, -3.2124},
                                                .velocity = {-8.4738, -9.7482},
                                                .time{2.0344}},
                                               kTol),
                                    ResultNear({.position = {4.7918, -3.2619},
                                                .velocity = {-7.6948, -8.9195},
                                                .time{2.0400}},
                                               kTol),
                                    ResultNear({.position = {4.7555, -3.3042},
                                                .velocity = {-6.5279, -7.6113},
                                                .time{2.0456}},
                                               kTol),
                                    ResultNear({.position = {4.7264, -3.3383},
                                                .velocity = {-5.2343, -6.1345},
                                                .time{2.0511}},
                                               kTol),
                                    ResultNear({.position = {4.7043, -3.3643},
                                                .velocity = {-3.9823, -4.6907},
                                                .time{2.0567}},
                                               kTol),
                                    ResultNear({.position = {4.6884, -3.3832},
                                                .velocity = {-2.8691, -3.3980},
                                                .time{2.0622}},
                                               kTol),
                                    ResultNear({.position = {4.6776, -3.3961},
                                                .velocity = {-1.9403, -2.3135},
                                                .time{2.0678}},
                                               kTol),
                                    ResultNear({.position = {4.6752, -3.3990},
                                                .velocity = {-1.7569, -2.0983},
                                                .time{2.0692}},
                                               kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {4.675, -3.525},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9022, -3.1387},
                                                .velocity = {-7.9833, -10.2310},
                                                .time{2.0267}},
                                               kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.8549, -3.2079},
                                                .velocity = {-8.5070, -12.4602},
                                                .time{2.0322}},
                                               kTol),
                                    ResultNear({.position = {4.8102, -3.2783},
                                                .velocity = {-8.0479, -12.6650},
                                                .time{2.0378}},
                                               kTol),
                                    ResultNear({.position = {4.7711, -3.3429},
                                                .velocity = {-7.0408, -11.6365},
                                                .time{2.0433}},
                                               kTol),
                                    ResultNear({.position = {4.7389, -3.3983},
                                                .velocity = {-5.7965, -9.9616},
                                                .time{2.0489}},
                                               kTol),
                                    ResultNear({.position = {4.7137, -3.4430},
                                                .velocity = {-4.5230, -8.0510},
                                                .time{2.0544}},
                                               kTol),
                                    ResultNear({.position = {4.6951, -3.4773},
                                                .velocity = {-3.3477, -6.1727},
                                                .time{2.0600}},
                                               kTol),
                                    ResultNear({.position = {4.6821, -3.5022},
                                                .velocity = {-2.3381, -4.4846},
                                                .time{2.0656}},
                                               kTol),
                                    ResultNear({.position = {4.6737, -3.5192},
                                                .velocity = {-1.5199, -3.0641},
                                                .time{2.0711}},
                                               kTol),
                                    ResultNear({.position = {4.6718, -3.5231},
                                                .velocity = {-1.3626, -2.7813},
                                                .time{2.0725}},
                                               kTol)));

  time += kDeltaTime;
  // We get more results at the end of the stroke as it tries to "catch up" to
  // the raw input.
  results = modeler.Update({.event_type = Input::EventType::kUp,
                            .position = {4.7, -3.6},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.8753, -3.1797},
                                                .velocity = {-8.0521, -12.3049},
                                                .time{2.0300}},
                                               kTol),
                                    ResultNear({.position = {4.8325, -3.2589},
                                                .velocity = {-7.7000, -14.2607},
                                                .time{2.0356}},
                                               kTol),
                                    ResultNear({.position = {4.7948, -3.3375},
                                                .velocity = {-6.7888, -14.1377},
                                                .time{2.0411}},
                                               kTol),
                                    ResultNear({.position = {4.7636, -3.4085},
                                                .velocity = {-5.6249, -12.7787},
                                                .time{2.0467}},
                                               kTol),
                                    ResultNear({.position = {4.7390, -3.4685},
                                                .velocity = {-4.4152, -10.8015},
                                                .time{2.0522}},
                                               kTol),
                                    ResultNear({.position = {4.7208, -3.5164},
                                                .velocity = {-3.2880, -8.6333},
                                                .time{2.0578}},
                                               kTol),
                                    ResultNear({.position = {4.7079, -3.5528},
                                                .velocity = {-2.3128, -6.5475},
                                                .time{2.0633}},
                                               kTol),
                                    ResultNear({.position = {4.6995, -3.5789},
                                                .velocity = {-1.5174, -4.7008},
                                                .time{2.0689}},
                                               kTol),
                                    ResultNear({.position = {4.6945, -3.5965},
                                                .velocity = {-0.9022, -3.1655},
                                                .time{2.0744}},
                                               kTol),
                                    ResultNear({.position = {4.6942, -3.5976},
                                                .velocity = {-0.8740, -3.0899},
                                                .time{2.0748}},
                                               kTol)));

  // The stroke is finished, so there's nothing to predict anymore.
  EXPECT_EQ(modeler.Predict().status().code(),
            absl::StatusCode::kFailedPrecondition);
}

TEST(StrokeModelerTest, WobbleSmoothed) {
  const Duration kDeltaTime{.0167};

  StrokeModeler modeler;
  ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());

  Time time{4};
  absl::StatusOr<std::vector<Result>> results =
      modeler.Update({.event_type = Input::EventType::kDown,
                      .position = {-6, -2},
                      .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results,
              ElementsAre(ResultNear(
                  {.position = {-6, -2}, .velocity = {0, 0}, .time{4}}, kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {-6.02, -2},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {-6.0001, -2},
                                                .velocity = {-0.0328, 0},
                                                .time{4.0042}},
                                               kTol),
                                    ResultNear({.position = {-6.0005, -2},
                                                .velocity = {-0.0869, 0},
                                                .time{4.0084}},
                                               kTol),
                                    ResultNear({.position = {-6.0011, -2},
                                                .velocity = {-0.1531, 0},
                                                .time{4.0125}},
                                               kTol),
                                    ResultNear({.position = {-6.0021, -2},
                                                .velocity = {-0.2244, 0},
                                                .time{4.0167}},
                                               kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {-6.02, -2.02},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {-6.0032, -2.0001},
                                                .velocity = {-0.2709, -0.0205},
                                                .time{4.0209}},
                                               kTol),
                                    ResultNear({.position = {-6.0044, -2.0003},
                                                .velocity = {-0.2977, -0.0543},
                                                .time{4.0251}},
                                               kTol),
                                    ResultNear({.position = {-6.0057, -2.0007},
                                                .velocity = {-0.3093, -0.0956},
                                                .time{4.0292}},
                                               kTol),
                                    ResultNear({.position = {-6.0070, -2.0013},
                                                .velocity = {-0.3097, -0.1401},
                                                .time{4.0334}},
                                               kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {-6.04, -2.02},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {-6.0084, -2.0021},
                                                .velocity = {-0.3350, -0.1845},
                                                .time{4.0376}},
                                               kTol),
                                    ResultNear({.position = {-6.0100, -2.0030},
                                                .velocity = {-0.3766, -0.2266},
                                                .time{4.0418}},
                                               kTol),
                                    ResultNear({.position = {-6.0118, -2.0041},
                                                .velocity = {-0.4273, -0.2649},
                                                .time{4.0459}},
                                               kTol),
                                    ResultNear({.position = {-6.0138, -2.0054},
                                                .velocity = {-0.4818, -0.2986},
                                                .time{4.0501}},
                                               kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {-6.04, -2.04},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {-6.0160, -2.0068},
                                                .velocity = {-0.5157, -0.3478},
                                                .time{4.0543}},
                                               kTol),
                                    ResultNear({.position = {-6.0182, -2.0085},
                                                .velocity = {-0.5334, -0.4054},
                                                .time{4.0585}},
                                               kTol),
                                    ResultNear({.position = {-6.0204, -2.0105},
                                                .velocity = {-0.5389, -0.4658},
                                                .time{4.0626}},
                                               kTol),
                                    ResultNear({.position = {-6.0227, -2.0126},
                                                .velocity = {-0.5356, -0.5251},
                                                .time{4.0668}},
                                               kTol)));
}

TEST(StrokeModelerTest, Reset) {
  const Duration kDeltaTime{1. / 50};

  StrokeModeler modeler;
  ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());

  Time time{0};
  absl::StatusOr<std::vector<Result>> results =
      modeler.Update({.event_type = Input::EventType::kDown,
                      .position = {-8, -10},
                      .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, Not(IsEmpty()));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, IsEmpty());

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {-10, -8},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, Not(IsEmpty()));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, Not(IsEmpty()));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {-11, -5},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, Not(IsEmpty()));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, Not(IsEmpty()));

  ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());
  EXPECT_EQ(modeler.Predict().status().code(),
            absl::StatusCode::kFailedPrecondition);
}

TEST(StrokeModelerTest, IgnoreInputsBeforeTDown) {
  StrokeModeler modeler;
  ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());

  EXPECT_EQ(modeler
                .Update({.event_type = Input::EventType::kMove,
                         .position = {0, 0},
                         .time{0}})
                .status()
                .code(),
            absl::StatusCode::kFailedPrecondition);

  EXPECT_EQ(modeler
                .Update({.event_type = Input::EventType::kUp,
                         .position = {0, 0},
                         .time{1}})
                .status()
                .code(),
            absl::StatusCode::kFailedPrecondition);
}

TEST(StrokeModelerTest, IgnoreTDownWhileStrokeIsInProgress) {
  StrokeModeler modeler;
  ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());

  absl::StatusOr<std::vector<Result>> results = modeler.Update(
      {.event_type = Input::EventType::kDown, .position = {0, 0}, .time{0}});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, Not(IsEmpty()));

  EXPECT_EQ(modeler
                .Update({.event_type = Input::EventType::kDown,
                         .position = {1, 1},
                         .time{1}})
                .status()
                .code(),
            absl::StatusCode::kFailedPrecondition);

  results = modeler.Update(
      {.event_type = Input::EventType::kMove, .position = {1, 1}, .time{1}});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, Not(IsEmpty()));

  EXPECT_EQ(modeler
                .Update({.event_type = Input::EventType::kDown,
                         .position = {2, 2},
                         .time{2}})
                .status()
                .code(),
            absl::StatusCode::kFailedPrecondition);
}

TEST(StrokeModelerTest, AlternateParams) {
  const Duration kDeltaTime{1. / 50};

  StrokeModelParams stroke_model_params = kDefaultParams;
  stroke_model_params.sampling_params.min_output_rate = 70;

  StrokeModeler modeler;
  ASSERT_TRUE(modeler.Reset(stroke_model_params).ok());

  Time time{3};
  absl::StatusOr<std::vector<Result>> results =
      modeler.Update({.event_type = Input::EventType::kDown,
                      .position = {0, 0},
                      .time = time,
                      .pressure = .5,
                      .tilt = .2,
                      .orientation = .4});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear(
                            {{0, 0}, {0, 0}, Time{3}, .5, .2, .4}, kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, IsEmpty());

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {0, .5},
                            .time = time,
                            .pressure = .4,
                            .tilt = .3,
                            .orientation = .3});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(
      *results,
      ElementsAre(
          ResultNear(
              {{0, 0.0736}, {0, 7.3636}, Time{3.0100}, 0.4853, 0.2147, 0.3853},
              kTol),
          ResultNear(
              {{0, 0.2198}, {0, 14.6202}, Time{3.0200}, 0.4560, 0.2440, 0.3560},
              kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(
      *results,
      ElementsAre(
          ResultNear(
              {{0, 0.3823}, {0, 11.3709}, Time{3.0343}, 0.4235, 0.2765, 0.3235},
              kTol),
          ResultNear(
              {{0, 0.4484}, {0, 4.6285}, Time{3.0486}, 0.4103, 0.2897, 0.3103},
              kTol),
          ResultNear(
              {{0, 0.4775}, {0, 2.0389}, Time{3.0629}, 0.4045, 0.2955, 0.3045},
              kTol),
          ResultNear(
              {{0, 0.4902}, {0, 0.8873}, Time{3.0771}, 0.4020, 0.2980, 0.3020},
              kTol),
          ResultNear(
              {{0, 0.4957}, {0, 0.3868}, Time{3.0914}, 0.4009, 0.2991, 0.3009},
              kTol),
          ResultNear(
              {{0, 0.4981}, {0, 0.1686}, Time{3.1057}, 0.4004, 0.2996, 0.3004},
              kTol),
          ResultNear(
              {{0, 0.4992}, {0, 0.0735}, Time{3.1200}, 0.4002, 0.2998, 0.3002},
              kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {.2, 1},
                            .time = time,
                            .pressure = .3,
                            .tilt = .4,
                            .orientation = .2});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({{0.0295, 0.4169},
                                                {2.9455, 19.7093},
                                                Time{3.0300},
                                                0.4166,
                                                0.2834,
                                                0.3166},
                                               kTol),
                                    ResultNear({{0.0879, 0.6439},
                                                {5.8481, 22.6926},
                                                Time{3.0400},
                                                0.3691,
                                                0.3309,
                                                0.2691},
                                               kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({{0.1529, 0.8487},
                                                {4.5484, 14.3374},
                                                Time{3.0543},
                                                0.3293,
                                                0.3707,
                                                0.2293},
                                               kTol),
                                    ResultNear({{0.1794, 0.9338},
                                                {1.8514, 5.9577},
                                                Time{3.0686},
                                                0.3128,
                                                0.3872,
                                                0.2128},
                                               kTol),
                                    ResultNear({{0.1910, 0.9712},
                                                {0.8156, 2.6159},
                                                Time{3.0829},
                                                0.3056,
                                                0.3944,
                                                0.2056},
                                               kTol),
                                    ResultNear({{0.1961, 0.9874},
                                                {0.3549, 1.1389},
                                                Time{3.0971},
                                                0.3024,
                                                0.3976,
                                                0.2024},
                                               kTol),
                                    ResultNear({{0.1983, 0.9945},
                                                {0.1547, 0.4965},
                                                Time{3.1114},
                                                0.3011,
                                                0.3989,
                                                0.2011},
                                               kTol),
                                    ResultNear({{0.1993, 0.9976},
                                                {0.0674, 0.2164},
                                                Time{3.1257},
                                                0.3005,
                                                0.3995,
                                                0.2005},
                                               kTol),
                                    ResultNear({{0.1997, 0.9990},
                                                {0.0294, 0.0943},
                                                Time{3.1400},
                                                0.3002,
                                                0.3998,
                                                0.2002},
                                               kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {.4, 1.4},
                            .time = time,
                            .pressure = .2,
                            .tilt = .7,
                            .orientation = 0});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({{0.1668, 0.8712},
                                                {7.8837, 22.7349},
                                                Time{3.0500},
                                                0.3245,
                                                0.3755,
                                                0.2245},
                                               kTol),
                                    ResultNear({{0.2575, 1.0906},
                                                {9.0771, 21.9411},
                                                Time{3.0600},
                                                0.2761,
                                                0.4716,
                                                0.1522},
                                               kTol)));

  results = modeler.Predict();
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({{0.3395, 1.2676},
                                                {5.7349, 12.3913},
                                                Time{3.0743},
                                                0.2325,
                                                0.6024,
                                                0.0651},
                                               kTol),
                                    ResultNear({{0.3735, 1.3421},
                                                {2.3831, 5.2156},
                                                Time{3.0886},
                                                0.2142,
                                                0.6573,
                                                0.0284},
                                               kTol),
                                    ResultNear({{0.3885, 1.3748},
                                                {1.0463, 2.2854},
                                                Time{3.1029},
                                                0.2062,
                                                0.6814,
                                                0.0124},
                                               kTol),
                                    ResultNear({{0.3950, 1.3890},
                                                {0.4556, 0.9954},
                                                Time{3.1171},
                                                0.2027,
                                                0.6919,
                                                0.0054},
                                               kTol),
                                    ResultNear({{0.3978, 1.3952},
                                                {0.1986, 0.4339},
                                                Time{3.1314},
                                                0.2012,
                                                0.6965,
                                                0.0024},
                                               kTol),
                                    ResultNear({{0.3990, 1.3979},
                                                {0.0866, 0.1891},
                                                Time{3.1457},
                                                0.2005,
                                                0.6985,
                                                0.0010},
                                               kTol),
                                    ResultNear({{0.3996, 1.3991},
                                                {0.0377, 0.0824},
                                                Time{3.1600},
                                                0.2002,
                                                0.6993,
                                                0.0004},
                                               kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kUp,
                            .position = {.7, 1.7},
                            .time = time,
                            .pressure = .1,
                            .tilt = 1,
                            .orientation = 0});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, ElementsAre(ResultNear({{0.3691, 1.2874},
                                                {11.1558, 19.6744},
                                                Time{3.0700},
                                                0.2256,
                                                0.6231,
                                                0.0512},
                                               kTol),
                                    ResultNear({{0.4978, 1.4640},
                                                {12.8701, 17.6629},
                                                Time{3.0800},
                                                0.1730,
                                                0.7809,
                                                0},
                                               kTol),
                                    ResultNear({{0.6141, 1.5986},
                                                {8.1404, 9.4261},
                                                Time{3.0943},
                                                0.1312,
                                                0.9064,
                                                0},
                                               kTol),
                                    ResultNear({{0.6624, 1.6557},
                                                {3.3822, 3.9953},
                                                Time{3.1086},
                                                0.1136,
                                                0.9591,
                                                0},
                                               kTol),
                                    ResultNear({{0.6836, 1.6807},
                                                {1.4851, 1.7488},
                                                Time{3.1229},
                                                0.1059,
                                                0.9822,
                                                0},
                                               kTol),
                                    ResultNear({{0.6929, 1.6916},
                                                {0.6466, 0.7618},
                                                Time{3.1371},
                                                0.1026,
                                                0.9922,
                                                0},
                                               kTol),
                                    ResultNear({{0.6969, 1.6963},
                                                {0.2819, 0.3321},
                                                Time{3.1514},
                                                0.1011,
                                                0.9966,
                                                0},
                                               kTol),
                                    ResultNear({{0.6986, 1.6984},
                                                {0.1229, 0.1447},
                                                Time{3.1657},
                                                0.1005,
                                                0.9985,
                                                0},
                                               kTol),
                                    ResultNear({{0.6994, 1.6993},
                                                {0.0535, 0.0631},
                                                Time{3.1800},
                                                0.1002,
                                                0.9994,
                                                0},
                                               kTol)));

  EXPECT_EQ(modeler.Predict().status().code(),
            absl::StatusCode::kFailedPrecondition);
}

TEST(StrokeModelerTest, GenerateOutputOnTUpEvenIfNoTimeDelta) {
  const Duration kDeltaTime{1. / 500};

  StrokeModeler modeler;
  ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());

  Time time{0};
  absl::StatusOr<std::vector<Result>> results =
      modeler.Update({.event_type = Input::EventType::kDown,
                      .position = {5, 5},
                      .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results,
              ElementsAre(ResultNear(
                  {.position = {5, 5}, .velocity = {0, 0}, .time{0}}, kTol)));

  time += kDeltaTime;
  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {5, 5},
                            .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(
      *results,
      ElementsAre(ResultNear(
          {.position = {5, 5}, .velocity = {0, 0}, .time{0.002}}, kTol)));

  results = modeler.Update(
      {.event_type = Input::EventType::kUp, .position = {5, 5}, .time = time});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(
      *results,
      ElementsAre(ResultNear(
          {.position = {5, 5}, .velocity = {0, 0}, .time{0.0076}}, kTol)));
}

TEST(StrokeModelerTest, RejectInputIfNegativeTimeDelta) {
  StrokeModeler modeler;
  ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());

  absl::StatusOr<std::vector<Result>> results = modeler.Update(
      {.event_type = Input::EventType::kDown, .position = {0, 0}, .time{0}});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, Not(IsEmpty()));

  EXPECT_EQ(modeler
                .Update({.event_type = Input::EventType::kMove,
                         .position = {1, 1},
                         .time{-.1}})
                .status()
                .code(),
            absl::StatusCode::kInvalidArgument);

  results = modeler.Update(
      {.event_type = Input::EventType::kMove, .position = {1, 1}, .time{1}});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, Not(IsEmpty()));

  EXPECT_EQ(modeler
                .Update({.event_type = Input::EventType::kUp,
                         .position = {1, 1},
                         .time{.9}})
                .status()
                .code(),
            absl::StatusCode::kInvalidArgument);
}

TEST(StrokeModelerTest, RejectDuplicateInput) {
  StrokeModeler modeler;
  ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());

  absl::StatusOr<std::vector<Result>> results =
      modeler.Update({.event_type = Input::EventType::kDown,
                      .position = {0, 0},
                      .time{0},
                      .pressure = .2,
                      .tilt = .3,
                      .orientation = .4});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, Not(IsEmpty()));

  EXPECT_EQ(modeler
                .Update({.event_type = Input::EventType::kDown,
                         .position = {0, 0},
                         .time{0},
                         .pressure = .2,
                         .tilt = .3,
                         .orientation = .4})
                .status()
                .code(),
            absl::StatusCode::kInvalidArgument);

  results = modeler.Update({.event_type = Input::EventType::kMove,
                            .position = {1, 2},
                            .time{1},
                            .pressure = .1,
                            .tilt = .2,
                            .orientation = .3});
  ASSERT_TRUE(results.ok());
  EXPECT_THAT(*results, Not(IsEmpty()));

  EXPECT_EQ(modeler
                .Update({.event_type = Input::EventType::kMove,
                         .position = {1, 2},
                         .time{1},
                         .pressure = .1,
                         .tilt = .2,
                         .orientation = .3})
                .status()
                .code(),
            absl::StatusCode::kInvalidArgument);
}

}  // namespace
}  // namespace stroke_model
}  // namespace ink