subtr-actor 1.0.0

Rocket League replay transformer
Documentation
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
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
#![cfg_attr(target_arch = "wasm32", allow(dead_code))]

use serde::Serialize;
use ts_rs::TS;

#[cfg(not(target_arch = "wasm32"))]
use linkme::distributed_slice;

use super::{
    BackboardBounceEvent, BallCarryEvent, BallDepthEvent, BallHalfEvent, BallProximityEvent,
    BoostPickupEvent, BumpEvent, CeilingShotEvent, CenterEvent, ControlledPlayEvent,
    CorePlayerScoreboardEvent, DepthRoleEvent, DodgeEvent, DodgeResetEvent, DoubleTapEvent,
    FieldHalfEvent, FieldThirdEvent, FiftyFiftyEvent, FirstManChangeEvent, FlickEvent,
    FlipResetEvent, HalfFlipEvent, HalfVolleyEvent, MovementEvent, MustyFlickEvent, OneTimerEvent,
    PassEvent, PlayerActivityEvent, PlayerPossessionEvent, PossessionEvent, PowerslideEvent,
    RespawnEvent, RotationRoleEvent, RushEvent, SpeedFlipEvent, TerritorialPressureEvent,
    TimelineEvent, TouchClassificationEvent, WallAerialEvent, WallAerialShotEvent, WavedashEvent,
    WhiffEvent,
};
use crate::stats::timeline::Event;

/// Static, English-language metadata for a stat event type.
///
/// Event structs own this definition through [`StatsEvent`]. Analysis nodes
/// then link event definitions to the calculator code that produces them via
/// [`EmittedEvent`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct EventDefinition {
    pub id: &'static str,
    pub label: &'static str,
    pub category: EventCategory,
    pub confidence: DetectionConfidence,
    pub summary: &'static str,
    pub approach: &'static [&'static str],
    pub limitations: &'static [&'static str],
    /// When true this definition is a label-like or expansion-parent row that
    /// should not be offered as a selectable event type in the review UI.
    pub hidden_from_review: bool,
    /// Concrete event-type keys this definition expands into at serialization
    /// time (e.g. `boost_ledger` -> `boost_ledger_collected`). Expansion parents
    /// are typically also `hidden_from_review`; their variants are surfaced
    /// instead. Empty for ordinary events.
    pub variants: &'static [EventVariant],
}

impl EventDefinition {
    /// Set whether this definition is hidden from the review picker. Named to
    /// double as a `define_stats_event!` modifier (`hidden = true`).
    pub const fn hidden(self, hidden: bool) -> Self {
        let mut def = self;
        def.hidden_from_review = hidden;
        def
    }

    /// Attach the concrete variant keys this definition expands into. Named to
    /// double as a `define_stats_event!` modifier (`variants = SLICE`).
    pub const fn variants(self, variants: &'static [EventVariant]) -> Self {
        let mut def = self;
        def.variants = variants;
        def
    }
}

/// A concrete event-type key produced by expanding a parent [`EventDefinition`]
/// (for example a boost-ledger transaction or a rotation role/depth state).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct EventVariant {
    pub key: &'static str,
    pub label: &'static str,
    pub category: EventCategory,
}

impl EventVariant {
    pub const fn new(key: &'static str, label: &'static str, category: EventCategory) -> Self {
        Self {
            key,
            label,
            category,
        }
    }
}

/// Coarse product/domain grouping for an event definition.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, TS)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
pub enum EventCategory {
    Core,
    Mechanic,
    Positioning,
    Movement,
    Annotation,
    Other,
    /// Label-like metadata rows (e.g. goal context). These are hidden from the
    /// review picker by default via [`EventDefinition::hidden_from_review`].
    Context,
}

/// Multi-dimensional confidence metadata for an event detector.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct DetectionConfidence {
    pub approach: ApproachConfidenceLevel,
    pub true_positive_evidence: TruePositiveEvidenceLevel,
    pub false_positive_evidence: DetectionIssueEvidenceLevel,
    pub false_negative_evidence: DetectionIssueEvidenceLevel,
    pub testing: TestingThoroughnessLevel,
    pub known_issues: &'static [KnownIssueRef],
}

/// How plausible and stable the current detector approach is by design.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ApproachConfidenceLevel {
    Unknown,
    High,
    Medium,
    Low,
    Experimental,
}

/// Whether the detector is known to produce correct detections.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum TruePositiveEvidenceLevel {
    NotEvaluated,
    Plausible,
    ManuallyConfirmed,
    AutomatedTestCovered,
    RepeatedlyConfirmed,
}

/// Whether the detector is known to produce incorrect detections or misses.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum DetectionIssueEvidenceLevel {
    NotEvaluated,
    NoneKnown,
    Suspected,
    Observed,
}

/// Rough level of testing behind the detector definition.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum TestingThoroughnessLevel {
    Untested,
    ManualSpotCheck,
    TargetedAutomatedTest,
    MultipleTargetedTests,
    CuratedSuite,
    CorpusSample,
}

/// Lightweight reference to a known detector issue.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct KnownIssueRef {
    pub id: &'static str,
    pub summary: &'static str,
    pub url: Option<&'static str>,
}

pub const UNKNOWN_DETECTION_CONFIDENCE: DetectionConfidence = DetectionConfidence {
    approach: ApproachConfidenceLevel::Unknown,
    true_positive_evidence: TruePositiveEvidenceLevel::NotEvaluated,
    false_positive_evidence: DetectionIssueEvidenceLevel::NotEvaluated,
    false_negative_evidence: DetectionIssueEvidenceLevel::NotEvaluated,
    testing: TestingThoroughnessLevel::Untested,
    known_issues: &[],
};

pub const fn pending_event_definition(
    id: &'static str,
    label: &'static str,
    category: EventCategory,
) -> EventDefinition {
    event_definition(id, label, category, "Definition pending.", &[])
}

pub const fn event_definition(
    id: &'static str,
    label: &'static str,
    category: EventCategory,
    summary: &'static str,
    approach: &'static [&'static str],
) -> EventDefinition {
    EventDefinition {
        id,
        label,
        category,
        confidence: UNKNOWN_DETECTION_CONFIDENCE,
        summary,
        approach,
        limitations: &[],
        hidden_from_review: false,
        variants: &[],
    }
}

pub const fn produced_event(
    event: &'static EventDefinition,
    node_name: &'static str,
    node_type: &'static str,
    calculator_type: &'static str,
) -> EmittedEvent {
    EmittedEvent {
        event,
        producer: ProducerDefinition {
            node_name,
            node_type,
            calculator_type,
            implementation_notes: &[],
        },
    }
}

pub const fn produced_event_for<E: StatsEvent>(
    node_name: &'static str,
    node_type: &'static str,
    calculator_type: &'static str,
) -> EmittedEvent {
    produced_event(&E::DEFINITION, node_name, node_type, calculator_type)
}

/// Trait implemented by typed stat event payloads.
pub trait StatsEvent {
    const DEFINITION: EventDefinition;
}

/// Static metadata for the analysis node and calculator that produce an event.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct ProducerDefinition {
    pub node_name: &'static str,
    pub node_type: &'static str,
    pub calculator_type: &'static str,
    pub implementation_notes: &'static [&'static str],
}

/// Link between an event definition and the graph node that emits it.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct EmittedEvent {
    pub event: &'static EventDefinition,
    pub producer: ProducerDefinition,
}

/// Static registration for the events emitted by one analysis node.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct EventProducerDefinition {
    pub node_name: &'static str,
    pub emitted_events: &'static [EmittedEvent],
}

/// Distributed static catalog of event producers.
///
/// Each analysis node can register its own emitted events next to the node or
/// calculator implementation. Documentation generation should walk this slice
/// instead of maintaining a second central catalog.
#[cfg(not(target_arch = "wasm32"))]
#[distributed_slice]
pub static EVENT_PRODUCERS: [EventProducerDefinition];

#[cfg(not(target_arch = "wasm32"))]
pub fn event_producers() -> &'static [EventProducerDefinition] {
    &EVENT_PRODUCERS
}

/// `linkme` does not support `wasm32-unknown-unknown`, so the static
/// documentation registry is available on host/tooling builds only for now.
#[cfg(target_arch = "wasm32")]
pub fn event_producers() -> &'static [EventProducerDefinition] {
    &[]
}

/// Distributed catalog of every [`EventDefinition`].
///
/// `define_stats_event!` (and `register_event_definition!` for payload-less
/// rows) register into this slice automatically, so defining an event is the
/// only step required for it to appear everywhere definitions are consumed —
/// there is no separate central list to keep in sync. Read it through
/// [`all_event_definitions`], which sorts and de-duplicates by `id`.
#[cfg(not(target_arch = "wasm32"))]
#[distributed_slice]
pub static EVENT_DEFINITIONS: [EventDefinition];

/// All registered event definitions, sorted by `id` and de-duplicated.
///
/// `linkme` does not guarantee registration order, so this sorts for stable
/// output and panics if two registrations share an `id` but disagree on
/// contents (a real double-registration bug rather than something to hide).
#[cfg(not(target_arch = "wasm32"))]
pub fn all_event_definitions() -> &'static [EventDefinition] {
    use std::sync::OnceLock;
    static SORTED: OnceLock<Vec<EventDefinition>> = OnceLock::new();
    SORTED.get_or_init(|| {
        let mut defs: Vec<EventDefinition> = EVENT_DEFINITIONS.iter().copied().collect();
        defs.sort_by(|left, right| left.id.cmp(right.id));
        let mut deduped: Vec<EventDefinition> = Vec::with_capacity(defs.len());
        for def in defs {
            match deduped.last() {
                Some(last) if last.id == def.id => {
                    assert!(
                        *last == def,
                        "conflicting EventDefinition registrations for id {:?}",
                        def.id
                    );
                }
                _ => deduped.push(def),
            }
        }
        deduped
    })
}

// `linkme` is unavailable on wasm32, so the registry and `all_event_definitions()`
// are host-only — there is intentionally no wasm fallback. The catalog is consumed
// only by host/server tooling and by the build-time TypeScript codegen
// (`event_definition_catalog()` + its export test); wasm/browser consumers use the
// generated TS catalog instead. A wasm caller referencing it is a compile error
// rather than a silently-empty list.

/// A variant entry in the TypeScript event catalog (owned, ts-rs-exportable).
#[derive(Debug, Clone, Serialize, TS)]
#[ts(export)]
pub struct EventVariantTs {
    pub key: String,
    pub label: String,
    pub category: EventCategory,
}

/// One entry in the TypeScript event catalog: the slim, viewer-relevant view of
/// an [`EventDefinition`] (id/label/category/hidden + expansion variants). The
/// browser viewer derives its event list from a generated array of these so it
/// can never drift from the Rust registry. Confidence/approach metadata is
/// intentionally omitted — it is host/docs-only.
#[derive(Debug, Clone, Serialize, TS)]
#[ts(export)]
pub struct EventDefinitionCatalogEntry {
    pub key: String,
    pub label: String,
    pub category: EventCategory,
    pub hidden_from_review: bool,
    pub variants: Vec<EventVariantTs>,
}

/// Build the TypeScript-facing catalog from the registry. Sorted/de-duplicated by
/// id (inherited from [`all_event_definitions`]) so codegen output is stable.
#[cfg(not(target_arch = "wasm32"))]
pub fn event_definition_catalog() -> Vec<EventDefinitionCatalogEntry> {
    all_event_definitions()
        .iter()
        .map(|definition| EventDefinitionCatalogEntry {
            key: definition.id.to_owned(),
            label: definition.label.to_owned(),
            category: definition.category,
            hidden_from_review: definition.hidden_from_review,
            variants: definition
                .variants
                .iter()
                .map(|variant| EventVariantTs {
                    key: variant.key.to_owned(),
                    label: variant.label.to_owned(),
                    category: variant.category,
                })
                .collect(),
        })
        .collect()
}

/// Build-time codegen for the TypeScript event catalog data file. ts-rs only
/// generates *types*; this writes the *data* array next to them. Runs as part of
/// the `generate:stats-types` npm script via the `export_bindings` test filter,
/// writing to `$TS_RS_EXPORT_DIR` when set. Without the env var it still validates
/// serialization but writes nothing, so a plain `cargo test` never touches the tree.
#[cfg(test)]
#[test]
fn export_bindings_event_definition_catalog() {
    let catalog = event_definition_catalog();
    let json = serde_json::to_string_pretty(&catalog).expect("serialize event catalog");
    let contents = format!(
        "// This file was generated from the subtr-actor event-definition registry. \
Do not edit this file manually.\n\
import type {{ EventDefinitionCatalogEntry }} from \"./EventDefinitionCatalogEntry.ts\";\n\
\n\
export const EVENT_DEFINITION_CATALOG: EventDefinitionCatalogEntry[] = {json};\n"
    );

    if let Ok(dir) = std::env::var("TS_RS_EXPORT_DIR") {
        let path = std::path::Path::new(&dir).join("eventDefinitionCatalog.generated.ts");
        std::fs::write(&path, contents).expect("write event catalog data file");
    }
}

/// Register an already-declared `EventDefinition` const into the
/// [`EVENT_DEFINITIONS`] catalog. Used for payload-less rows (core scoreboard
/// stats, goal context, expansion fallbacks) that have no [`StatsEvent`] type.
macro_rules! register_stats_event_definition {
    ($definition:ident) => {
        paste::paste! {
            #[cfg(not(target_arch = "wasm32"))]
            #[distributed_slice(EVENT_DEFINITIONS)]
            static [<$definition _REGISTRATION>]: EventDefinition = $definition;
        }
    };
}

macro_rules! define_stats_event {
    (
        $event_type:ty,
        $definition:ident,
        $id:literal,
        $label:literal,
        $category:expr,
        summary = $summary:literal,
        approach = [$($approach:literal),* $(,)?]
        $(, $modifier:ident = $modval:expr)* $(,)?
    ) => {
        pub const $definition: EventDefinition =
            event_definition($id, $label, $category, $summary, &[$($approach),*])
                $(.$modifier($modval))*;

        impl StatsEvent for $event_type {
            const DEFINITION: EventDefinition = $definition;
        }

        register_stats_event_definition!($definition);
    };

    (
        $event_type:ty,
        $definition:ident,
        $id:literal,
        $label:literal,
        $category:expr
        $(, $modifier:ident = $modval:expr)* $(,)?
    ) => {
        pub const $definition: EventDefinition =
            pending_event_definition($id, $label, $category)
                $(.$modifier($modval))*;

        impl StatsEvent for $event_type {
            const DEFINITION: EventDefinition = $definition;
        }

        register_stats_event_definition!($definition);
    };
}

macro_rules! register_event_producer {
    ($static_name:ident, $node_name:literal, $emitted_events:expr) => {
        #[cfg(not(target_arch = "wasm32"))]
        #[distributed_slice(EVENT_PRODUCERS)]
        static $static_name: EventProducerDefinition = EventProducerDefinition {
            node_name: $node_name,
            emitted_events: $emitted_events,
        };
    };
}

// Variant tables for expansion-parent definitions. Each parent is
// `hidden_from_review` and surfaces these concrete keys instead. The keys must
// match the ones serialized at runtime in the server's timeline expansion.
// All pickups surface under one key; the `detection` payload field
// (`both` | `inferred_only` | `reported_only`) records corroboration provenance and is a
// filter facet, not an event-type split.
const BOOST_PICKUP_VARIANTS: &[EventVariant] = &[EventVariant::new(
    "boost_pickup",
    "Boost Pickup",
    EventCategory::Other,
)];

// Payload-less event definitions: native Rocket League scoreboard stats, goal
// context labels, and the air-dribble mechanic kind. These have no `StatsEvent`
// payload type but still belong in the catalog so they surface in the review
// picker (or are explicitly hidden) without a separate hand-maintained list.
pub const ASSIST_EVENT_DEFINITION: EventDefinition =
    pending_event_definition("assist", "Assist", EventCategory::Core);
register_stats_event_definition!(ASSIST_EVENT_DEFINITION);

pub const DEATH_EVENT_DEFINITION: EventDefinition =
    pending_event_definition("death", "Death", EventCategory::Core);
register_stats_event_definition!(DEATH_EVENT_DEFINITION);

pub const GOAL_EVENT_DEFINITION: EventDefinition =
    pending_event_definition("goal", "Goal", EventCategory::Core);
register_stats_event_definition!(GOAL_EVENT_DEFINITION);

pub const KILL_EVENT_DEFINITION: EventDefinition =
    pending_event_definition("kill", "Demolition", EventCategory::Core);
register_stats_event_definition!(KILL_EVENT_DEFINITION);

pub const SAVE_EVENT_DEFINITION: EventDefinition =
    pending_event_definition("save", "Save", EventCategory::Core);
register_stats_event_definition!(SAVE_EVENT_DEFINITION);

pub const SHOT_EVENT_DEFINITION: EventDefinition =
    pending_event_definition("shot", "Shot", EventCategory::Core);
register_stats_event_definition!(SHOT_EVENT_DEFINITION);

pub const KICKOFF_EVENT_DEFINITION: EventDefinition =
    pending_event_definition("kickoff", "Kickoff", EventCategory::Core);
register_stats_event_definition!(KICKOFF_EVENT_DEFINITION);

pub const GOAL_CONTEXT_EVENT_DEFINITION: EventDefinition =
    pending_event_definition("goal_context", "Goal Context", EventCategory::Context).hidden(true);
register_stats_event_definition!(GOAL_CONTEXT_EVENT_DEFINITION);

pub const AIR_DRIBBLE_EVENT_DEFINITION: EventDefinition = event_definition(
    "air_dribble",
    "Air Dribble",
    EventCategory::Mechanic,
    "An airborne ball-control sequence where a player keeps the ball under control off the ground.",
    &[
        "Reuse the ball-carry sequence sampler's air-dribble carry kind, which tracks player-owned ball control while airborne.",
        "Surface the span when a completed ball-carry sequence is classified as an air dribble rather than a grounded carry.",
    ],
);
register_stats_event_definition!(AIR_DRIBBLE_EVENT_DEFINITION);

define_stats_event!(
    TimelineEvent,
    TIMELINE_EVENT_DEFINITION,
    "timeline",
    "Replay Timeline Event",
    EventCategory::Core,
    hidden = true
);
define_stats_event!(
    CorePlayerScoreboardEvent,
    CORE_PLAYER_SCOREBOARD_EVENT_DEFINITION,
    "core_player_scoreboard",
    "Core Player Scoreboard",
    EventCategory::Core,
    hidden = true
);
define_stats_event!(
    BackboardBounceEvent,
    BACKBOARD_BOUNCE_EVENT_DEFINITION,
    "backboard_bounce",
    "Backboard Bounce",
    EventCategory::Mechanic,
    summary = "A ball rebound off the opponent backboard attributed to the player who sent the ball there.",
    approach = [
        "Track the last touch during live play and attribute a later backboard rebound to that touch when it occurs within the configured attribution window.",
        "Require the ball to be high, near the backboard face, moving toward the backboard before the rebound, and moving away after the rebound.",
        "Ignore frames with a simultaneous touch so the rebound is not confused with a player-ball contact.",
    ]
);
define_stats_event!(
    CeilingShotEvent,
    CEILING_SHOT_EVENT_DEFINITION,
    "ceiling_shot",
    "Ceiling Shot",
    EventCategory::Mechanic,
    summary = "A shot touch shortly after the player contacts the ceiling and drops back toward the ball.",
    approach = [
        "Record recent ceiling contacts when the car is near the ceiling and oriented roof-first against it.",
        "Match a later touch by the same player within the ceiling-contact window after the player has separated from the ceiling.",
        "Score the candidate from contact timing, height, separation, forward alignment, approach speed, ball impulse, and ceiling-contact alignment.",
    ]
);
define_stats_event!(
    WallAerialEvent,
    WALL_AERIAL_EVENT_DEFINITION,
    "wall_aerial",
    "Wall Aerial",
    EventCategory::Mechanic,
    summary = "An aerial play that starts from controlled ball movement on a side or back wall.",
    approach = [
        "Track wall-control sequences where the last toucher keeps the ball close while positioned on a side or back wall.",
        "Arm a wall-aerial candidate when the player leaves the wall soon after a qualifying wall-control setup.",
        "Emit on a later aerial touch by the same player when the player and ball are high enough, the setup/takeoff windows hold, and the confidence score clears the threshold.",
    ]
);
define_stats_event!(
    WallAerialShotEvent,
    WALL_AERIAL_SHOT_EVENT_DEFINITION,
    "wall_aerial_shot",
    "Wall Aerial Shot",
    EventCategory::Mechanic,
    summary = "A shot credited to a player shortly after taking off from a wall.",
    approach = [
        "Track recent wall contact for each player and arm a candidate when the player leaves the wall while still above the ground threshold.",
        "Match a subsequent shot stat event by that player within the takeoff-to-shot window.",
        "Require the shot touch to occur off the wall with sufficient player and ball height, then score confidence from timing, height, goal alignment, and ball speed.",
    ]
);
define_stats_event!(
    CenterEvent,
    CENTER_EVENT_DEFINITION,
    "center",
    "Center",
    EventCategory::Mechanic,
    summary = "A touch that moves the ball from a wide attacking position toward the central attacking area.",
    approach = [
        "Start a pending center from a live-play touch, unless that player immediately has a shot or goal event.",
        "Watch the ball for a short window after the touch and require meaningful travel from a wide x-position toward a more central x-position in the attacking half.",
        "Clear the candidate when it ages out, loses attribution, or becomes a shot/goal by the same player instead of a center.",
    ]
);
define_stats_event!(
    FlickEvent,
    FLICK_EVENT_DEFINITION,
    "flick",
    "Flick",
    EventCategory::Mechanic,
    summary = "A dodge-powered touch following a short controlled carry setup.",
    approach = [
        "Track controlled setup windows where the current controlling player keeps the ball close above the car within local-position and gap thresholds.",
        "Measure signed horizontal setup rotation so reverse flicks can be labeled as left or right based on the direction the car rotated before the flick.",
        "Record dodge starts that happen immediately after, or during, a qualifying setup.",
        "Emit on a same-player touch shortly after the dodge when the ball impulse is large and directed away from the player, with confidence from setup duration, timing, impulse, and separation.",
    ]
);
define_stats_event!(
    MustyFlickEvent,
    MUSTY_FLICK_EVENT_DEFINITION,
    "musty_flick",
    "Musty Flick",
    EventCategory::Mechanic,
    summary = "A back-flip style flick where the ball is contacted behind/on top of the car during a dominant pitch rotation.",
    approach = [
        "Track dodge starts and keep only recent candidates whose car orientation is compatible with a musty-style setup.",
        "On a same-player touch, require the ball to be behind and above the car in local space, with rear/top alignment and forward approach speed.",
        "Require a meaningful ball speed change and pitch-dominant angular velocity, then score confidence from timing, alignment, approach, pitch, impulse, and setup orientation.",
    ]
);
define_stats_event!(
    DodgeResetEvent,
    DODGE_RESET_EVENT_DEFINITION,
    "dodge_reset",
    "Dodge Reset",
    EventCategory::Mechanic,
    summary = "A frame-level dodge refresh observed from replay state, marked as occurring on the ball (a flip reset) and as used when later converted by a dodge-powered touch.",
    approach = [
        "Consume dodge-refreshed replay events and preserve the player, team, frame, time, and counter value.",
        "Classify the refresh as on-ball (a flip reset) when the player and ball are both airborne enough, close together, and the ball is positioned under the car in local space.",
        "Keep on-ball resets pending in an in-flight ledger; if the player dodges into the ball within the reset-to-touch window, mark the originating reset event `used` with its reset-to-use latency.",
        "Resolve every pending reset into an outcome: used, landed, superseded by a newer reset, expired, or cut off by a goal, live play ending, or the replay ending.",
    ]
);
define_stats_event!(
    DoubleTapEvent,
    DOUBLE_TAP_EVENT_DEFINITION,
    "double_tap",
    "Double Tap",
    EventCategory::Mechanic,
    summary = "A same-player follow-up touch after an attributed backboard bounce that creates a shot-like trajectory.",
    approach = [
        "Arm a pending double tap from a backboard-bounce event attributed to the player who sent the ball to the backboard.",
        "Require the same player and team to touch the ball again during live play within the follow-up window.",
        "Accept the follow-up only when the post-touch straight-line ball trajectory projects into or close to the opponent goal mouth.",
    ]
);
define_stats_event!(
    OneTimerEvent,
    ONE_TIMER_EVENT_DEFINITION,
    "one_timer",
    "One Timer",
    EventCategory::Mechanic,
    summary = "A fast receiver touch from a completed pass that is immediately directed toward goal.",
    approach = [
        "Consume newly completed pass events on the frame they are recorded.",
        "Require the current ball speed after the receiver's touch to exceed the one-timer speed threshold.",
        "Require the post-touch ball velocity to align with the opponent goal center direction.",
    ]
);
define_stats_event!(
    PassEvent,
    PASS_EVENT_DEFINITION,
    "pass",
    "Pass",
    EventCategory::Mechanic,
    summary = "A same-team touch sequence where one player sends the ball to a different teammate.",
    approach = [
        "Track the last attributed touch in live play and compare it to each new touch.",
        "Emit when a different teammate touches the ball within the pass window after the ball has traveled far enough.",
        "Classify the pass as direct, backboard, fifty-fifty, or fifty-fifty backboard using intervening backboard-bounce and fifty-fifty state.",
    ]
);
define_stats_event!(
    BallCarryEvent,
    BALL_CARRY_EVENT_DEFINITION,
    "ball_carry",
    "Ball Carry",
    EventCategory::Mechanic,
    summary = "A sustained player-ball control sequence, covering grounded carries and air dribbles.",
    approach = [
        "Use continuous ball-control tracking to build player-owned sequences while live play is active.",
        "Sample grounded carries from close horizontal/vertical ball gaps over the car, excluding wall contact.",
        "Sample air dribbles with the air-dribble policy, then emit completed sequences that meet the duration and validity rules for their carry kind.",
    ]
);
define_stats_event!(
    ControlledPlayEvent,
    CONTROLLED_PLAY_EVENT_DEFINITION,
    "controlled_play",
    "Controlled Play",
    EventCategory::Mechanic,
    summary = "A same-player possession episode with multiple touches and sustained close-ball time.",
    approach = [
        "Start a player-owned candidate from an attributed touch during live play.",
        "Require at least two distinct touches by the same player with at least one second between the first and last touch.",
        "Require sustained proximity to the ball and finish the candidate when another player touches, live play ends, or the touch chain times out.",
    ]
);
define_stats_event!(
    FiftyFiftyEvent,
    FIFTY_FIFTY_EVENT_DEFINITION,
    "fifty_fifty",
    "50/50",
    EventCategory::Other,
    summary = "A contested ball interaction involving touches or pressure from both teams in a short window.",
    approach = [
        "Start an active 50/50 when a frame contains touches from both teams, including kickoff-specific tracking.",
        "Continue the contest for short follow-up touch windows while either involved team remains in contact.",
        "Resolve after a delay once ball movement, possession state, or max duration gives a winner, possession outcome, or neutral result.",
    ]
);
define_stats_event!(
    RushEvent,
    RUSH_EVENT_DEFINITION,
    "rush",
    "Rush",
    EventCategory::Other,
    summary = "A quick possession transition where the attacking team has numbers moving out of its defensive half.",
    approach = [
        "Start from a possession change when the ball is still in the new attacking team's defensive half.",
        "Count non-demoed attackers near or ahead of the ball and defenders between the ball and their own goal.",
        "Emit once the new attacking team retains possession long enough with at least two attackers and at least one defender in the rush shape.",
    ]
);
define_stats_event!(
    DodgeEvent,
    DODGE_EVENT_DEFINITION,
    "dodge",
    "Dodge",
    EventCategory::Mechanic,
    summary = "A dodge-start event, optionally carrying a rough estimated dodge impulse when the velocity change is measurable.",
    approach = [
        "Start on the replay's dodge-active rising edge for each player.",
        "Sample the player's velocity change over the early dodge window and subtract an approximate forward boost contribution when boost is active.",
        "Store the impulse estimate as dodge_impulse, including car-local direction classification plus raw and compensated world-space vectors for visualization and downstream mechanic detectors.",
    ]
);
define_stats_event!(
    SpeedFlipEvent,
    SPEED_FLIP_EVENT_DEFINITION,
    "speed_flip",
    "Speed Flip",
    EventCategory::Mechanic,
    summary = "A ground-started diagonal dodge/cancel acceleration pattern, primarily intended for kickoff speed flips.",
    approach = [
        "Start candidates on dodge rising edges while the player is grounded, moving in the car's forward direction, and, for kickoff cases, within the kickoff-start window.",
        "Track speed, forward alignment, boost alignment, diagonal angular-velocity balance, and early forward acceleration during a short evaluation window.",
        "Emit when the combined diagonal, cancel, speed, and alignment confidence score clears the speed-flip threshold before the candidate expires.",
    ]
);
define_stats_event!(
    HalfFlipEvent,
    HALF_FLIP_EVENT_DEFINITION,
    "half_flip",
    "Half Flip",
    EventCategory::Mechanic,
    summary = "A dodge sequence that starts while driving backward and reorients the car to move forward.",
    approach = [
        "Start candidates on grounded dodge rising edges when the car is moving backward relative to its facing direction.",
        "Track reorientation during the evaluation window, including forward-vector reversal, alignment with the resulting velocity, and vertical flip evidence.",
        "Emit when the candidate shows enough reversal, reorientation, flip motion, and speed evidence to clear the confidence threshold.",
    ]
);
define_stats_event!(
    HalfVolleyEvent,
    HALF_VOLLEY_EVENT_DEFINITION,
    "half_volley",
    "Half Volley",
    EventCategory::Mechanic,
    summary = "A fast touch shortly after the ball bounces off the floor, paired with a recent player dodge.",
    approach = [
        "Detect floor bounces from ball height and vertical velocity reversal when no touch occurs on the bounce frame.",
        "Track each player's recent ground contact and dodge start.",
        "Emit on a same-player touch shortly after the floor bounce and dodge when the post-touch ball speed clears the configured threshold.",
    ]
);
define_stats_event!(
    WavedashEvent,
    WAVEDASH_EVENT_DEFINITION,
    "wavedash",
    "Wavedash",
    EventCategory::Mechanic,
    summary = "A low airborne dodge that lands quickly and converts the dodge into ground speed.",
    approach = [
        "Start candidates on dodge rising edges from a low but airborne height.",
        "Watch for a landing within the wavedash window while the car is sufficiently upright.",
        "Score confidence from dodge-to-landing timing, starting height, speed gain or landing speed, and landing uprightness.",
    ]
);
define_stats_event!(
    WhiffEvent,
    WHIFF_EVENT_DEFINITION,
    "whiff",
    "Whiff",
    EventCategory::Other,
    summary = "A committed attempt near the ball that does not result in that player touching it.",
    approach = [
        "Start candidates when a player gets within hitbox distance of the ball while moving or dodging toward it with sufficient alignment and closing speed.",
        "Track the closest approach while the candidate remains near the ball.",
        "Resolve as a whiff when the player exits the candidate window without touching, or as beaten-to-ball when an opponent touches first.",
    ]
);
define_stats_event!(
    PowerslideEvent,
    POWERSLIDE_EVENT_DEFINITION,
    "powerslide",
    "Powerslide",
    EventCategory::Mechanic,
    summary = "A state-change event for effective grounded powerslide use.",
    approach = [
        "Read each player's powerslide-active input/state on every frame.",
        "Treat powerslide as effective only while the player is close enough to the ground.",
        "Emit when a player's effective powerslide state changes between active and inactive.",
    ]
);
define_stats_event!(
    TouchClassificationEvent,
    TOUCH_CLASSIFICATION_EVENT_DEFINITION,
    "touch",
    "Touch",
    EventCategory::Other,
    summary = "A classified ball touch with strength kind, surface/height context, and an inferred intention.",
    approach = [
        "Classify each touch's strength kind (control, medium hit, hard hit) from the ball speed change it produces.",
        "Record surface, height band, and dodge context for the touching player at contact time.",
        "Resolve a single mutually-exclusive intention by precedence: replay-confirmed saves and shots first, then contested challenges, then geometric save/shot trajectory projections, then clears out of the defensive third, then passes led toward a teammate, falling back to neutral.",
        "Retroactively upgrade pass/neutral touches to a control intention by outcome: the toucher stayed close to the ball while matching its velocity for most of a short follow window, or earned the follow-up touch themselves.",
        "Mark a touch as a first touch when it starts a new reception: the previous global touch was by a different player or far enough in the past.",
    ]
);
define_stats_event!(
    BoostPickupEvent,
    BOOST_PICKUP_EVENT_DEFINITION,
    "boost_pickups",
    "Boost Pickup",
    EventCategory::Other,
    hidden = true,
    variants = BOOST_PICKUP_VARIANTS
);
define_stats_event!(
    RespawnEvent,
    BOOST_RESPAWN_EVENT_DEFINITION,
    "boost_respawn",
    "Respawn",
    EventCategory::Other
);
define_stats_event!(
    BumpEvent,
    BUMP_EVENT_DEFINITION,
    "bump",
    "Bump",
    EventCategory::Other
);
define_stats_event!(
    PossessionEvent,
    POSSESSION_EVENT_DEFINITION,
    "possession",
    "Possession",
    EventCategory::Other
);
define_stats_event!(
    PlayerPossessionEvent,
    PLAYER_POSSESSION_EVENT_DEFINITION,
    "player_possession",
    "Player Possession",
    EventCategory::Other,
    summary = "A contiguous single-player possession span enriched with touch, ball-progress, and sustained-control activity.",
    approach = [
        "Follow the shared possession tracker's controlling player and open a span when a player establishes control.",
        "Bridge contested or pending-turnover interruptions shorter than the merge gap when the same player re-establishes control, excluding the gap from possessed duration.",
        "Accumulate distinct touches (with aerial/wall classification), signed ball travel toward the opponent goal, and per-frame carry/air-dribble samples while the span is active.",
    ]
);
define_stats_event!(
    BallHalfEvent,
    PRESSURE_EVENT_DEFINITION,
    "ball_half",
    "Ball Half",
    EventCategory::Other
);
define_stats_event!(
    TerritorialPressureEvent,
    TERRITORIAL_PRESSURE_EVENT_DEFINITION,
    "territorial_pressure",
    "Territorial Pressure",
    EventCategory::Other
);
define_stats_event!(
    MovementEvent,
    MOVEMENT_EVENT_DEFINITION,
    "movement",
    "Movement",
    EventCategory::Movement
);
define_stats_event!(
    PlayerActivityEvent,
    PLAYER_ACTIVITY_EVENT_DEFINITION,
    "player_activity",
    "Player Activity",
    EventCategory::Positioning
);
define_stats_event!(
    FieldThirdEvent,
    FIELD_THIRD_EVENT_DEFINITION,
    "field_third",
    "Field Third",
    EventCategory::Positioning
);
define_stats_event!(
    FieldHalfEvent,
    FIELD_HALF_EVENT_DEFINITION,
    "field_half",
    "Field Half",
    EventCategory::Positioning
);
define_stats_event!(
    BallDepthEvent,
    BALL_DEPTH_EVENT_DEFINITION,
    "ball_depth",
    "Ball Depth",
    EventCategory::Positioning
);
define_stats_event!(
    DepthRoleEvent,
    DEPTH_ROLE_EVENT_DEFINITION,
    "depth_role",
    "Depth Role",
    EventCategory::Positioning
);
define_stats_event!(
    BallProximityEvent,
    BALL_PROXIMITY_EVENT_DEFINITION,
    "ball_proximity",
    "Ball Proximity",
    EventCategory::Positioning
);
define_stats_event!(
    RotationRoleEvent,
    ROTATION_ROLE_EVENT_DEFINITION,
    "rotation_role",
    "Rotation Role",
    EventCategory::Positioning
);
define_stats_event!(
    FirstManChangeEvent,
    FIRST_MAN_CHANGE_EVENT_DEFINITION,
    "first_man_change",
    "First-Man Change",
    EventCategory::Positioning
);
define_stats_event!(
    FlipResetEvent,
    FLIP_RESET_EVENT_DEFINITION,
    "flip_reset",
    "Flip Reset",
    EventCategory::Mechanic,
    summary = "A touch candidate where the ball contacts the underside of an airborne car in flip-reset-like geometry.",
    approach = [
        "Evaluate touch events using normalized ball and player rigid bodies plus hitbox contact gap.",
        "Require airborne player/ball height, underside alignment, local ball position under the car, and a bounded hitbox contact gap.",
        "Also emit proximity-based candidates when replay touch attribution is missing but the ball/car geometry strongly matches a reset contact.",
    ]
);
define_stats_event!(
    Event,
    TIMELINE_ENVELOPE_EVENT_DEFINITION,
    "event",
    "Event",
    EventCategory::Mechanic,
    summary = "A shared event envelope with common metadata and a typed event payload.",
    approach = [
        "Collect completed events from the analysis graph at finish time.",
        "Wrap each typed event payload with common timing, participant, team, position, confidence, and stream metadata.",
        "Serialize timeline events as a single heterogeneous event list for playback and analysis consumers.",
    ]
);

// The former hand-maintained `ALL_EVENT_DEFINITIONS` array has been replaced by
// the auto-populated `EVENT_DEFINITIONS` distributed slice; read it through
// `all_event_definitions()`. Defining an event via `define_stats_event!` (or
// `register_stats_event_definition!`) is now the only registration step.

const MATCH_STATS_EMITTED_EVENTS: &[EmittedEvent] = &[
    produced_event(
        &TIMELINE_EVENT_DEFINITION,
        "match_stats",
        "MatchStatsNode",
        "MatchStatsCalculator",
    ),
    produced_event(
        &CORE_PLAYER_SCOREBOARD_EVENT_DEFINITION,
        "match_stats",
        "MatchStatsNode",
        "MatchStatsCalculator",
    ),
];

const DEMO_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &TIMELINE_EVENT_DEFINITION,
    "demo",
    "DemoNode",
    "DemoCalculator",
)];

const BACKBOARD_BOUNCE_STATE_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &BACKBOARD_BOUNCE_EVENT_DEFINITION,
    "backboard_bounce_state",
    "BackboardBounceStateNode",
    "BackboardBounceCalculator",
)];

const CEILING_SHOT_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &CEILING_SHOT_EVENT_DEFINITION,
    "ceiling_shot",
    "CeilingShotNode",
    "CeilingShotCalculator",
)];

const WALL_AERIAL_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &WALL_AERIAL_EVENT_DEFINITION,
    "wall_aerial",
    "WallAerialNode",
    "WallAerialCalculator",
)];

const WALL_AERIAL_SHOT_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &WALL_AERIAL_SHOT_EVENT_DEFINITION,
    "wall_aerial_shot",
    "WallAerialShotNode",
    "WallAerialShotCalculator",
)];

const CENTER_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &CENTER_EVENT_DEFINITION,
    "center",
    "CenterNode",
    "CenterCalculator",
)];

const FLICK_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &FLICK_EVENT_DEFINITION,
    "flick",
    "FlickNode",
    "FlickCalculator",
)];

const MUSTY_FLICK_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &MUSTY_FLICK_EVENT_DEFINITION,
    "musty_flick",
    "MustyFlickNode",
    "MustyFlickCalculator",
)];

const DODGE_RESET_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &DODGE_RESET_EVENT_DEFINITION,
    "dodge_reset",
    "DodgeResetNode",
    "DodgeResetCalculator",
)];

const DOUBLE_TAP_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &DOUBLE_TAP_EVENT_DEFINITION,
    "double_tap",
    "DoubleTapNode",
    "DoubleTapCalculator",
)];

const ONE_TIMER_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &ONE_TIMER_EVENT_DEFINITION,
    "one_timer",
    "OneTimerNode",
    "OneTimerCalculator",
)];

const PASS_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &PASS_EVENT_DEFINITION,
    "pass",
    "PassNode",
    "PassCalculator",
)];

const BALL_CARRY_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &BALL_CARRY_EVENT_DEFINITION,
    "ball_carry",
    "BallCarryNode",
    "BallCarryCalculator",
)];

const CONTROLLED_PLAY_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &CONTROLLED_PLAY_EVENT_DEFINITION,
    "controlled_play",
    "ControlledPlayNode",
    "ControlledPlayCalculator",
)];

const FIFTY_FIFTY_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &FIFTY_FIFTY_EVENT_DEFINITION,
    "fifty_fifty",
    "FiftyFiftyNode",
    "FiftyFiftyCalculator",
)];

const RUSH_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &RUSH_EVENT_DEFINITION,
    "rush",
    "RushNode",
    "RushCalculator",
)];

const DODGE_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &DODGE_EVENT_DEFINITION,
    "dodge",
    "FlipImpulseNode",
    "FlipImpulseCalculator",
)];

const SPEED_FLIP_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &SPEED_FLIP_EVENT_DEFINITION,
    "speed_flip",
    "SpeedFlipNode",
    "SpeedFlipCalculator",
)];

const HALF_FLIP_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &HALF_FLIP_EVENT_DEFINITION,
    "half_flip",
    "HalfFlipNode",
    "HalfFlipCalculator",
)];

const HALF_VOLLEY_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &HALF_VOLLEY_EVENT_DEFINITION,
    "half_volley",
    "HalfVolleyNode",
    "HalfVolleyCalculator",
)];

const WAVEDASH_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &WAVEDASH_EVENT_DEFINITION,
    "wavedash",
    "WavedashNode",
    "WavedashCalculator",
)];

const WHIFF_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &WHIFF_EVENT_DEFINITION,
    "whiff",
    "WhiffNode",
    "WhiffCalculator",
)];

const POWERSLIDE_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &POWERSLIDE_EVENT_DEFINITION,
    "powerslide",
    "PowerslideNode",
    "PowerslideCalculator",
)];

const TOUCH_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &TOUCH_CLASSIFICATION_EVENT_DEFINITION,
    "touch",
    "TouchNode",
    "TouchCalculator",
)];

const BOOST_EMITTED_EVENTS: &[EmittedEvent] = &[
    produced_event(
        &BOOST_PICKUP_EVENT_DEFINITION,
        "boost",
        "BoostNode",
        "BoostCalculator",
    ),
    produced_event(
        &BOOST_RESPAWN_EVENT_DEFINITION,
        "boost",
        "BoostNode",
        "BoostCalculator",
    ),
];

const BUMP_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &BUMP_EVENT_DEFINITION,
    "bump",
    "BumpNode",
    "BumpCalculator",
)];

const POSSESSION_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &POSSESSION_EVENT_DEFINITION,
    "possession",
    "PossessionNode",
    "PossessionCalculator",
)];

const PLAYER_POSSESSION_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &PLAYER_POSSESSION_EVENT_DEFINITION,
    "player_possession",
    "PlayerPossessionNode",
    "PlayerPossessionCalculator",
)];

const BALL_HALF_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &PRESSURE_EVENT_DEFINITION,
    "ball_half",
    "BallHalfNode",
    "BallHalfCalculator",
)];

const TERRITORIAL_BALL_HALF_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &TERRITORIAL_PRESSURE_EVENT_DEFINITION,
    "territorial_pressure",
    "TerritorialPressureNode",
    "TerritorialPressureCalculator",
)];

const MOVEMENT_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &MOVEMENT_EVENT_DEFINITION,
    "movement",
    "MovementNode",
    "MovementCalculator",
)];

const POSITIONING_EMITTED_EVENTS: &[EmittedEvent] = &[
    produced_event(
        &PLAYER_ACTIVITY_EVENT_DEFINITION,
        "positioning",
        "PositioningNode",
        "PositioningCalculator",
    ),
    produced_event(
        &FIELD_THIRD_EVENT_DEFINITION,
        "positioning",
        "PositioningNode",
        "PositioningCalculator",
    ),
    produced_event(
        &FIELD_HALF_EVENT_DEFINITION,
        "positioning",
        "PositioningNode",
        "PositioningCalculator",
    ),
    produced_event(
        &BALL_DEPTH_EVENT_DEFINITION,
        "positioning",
        "PositioningNode",
        "PositioningCalculator",
    ),
    produced_event(
        &DEPTH_ROLE_EVENT_DEFINITION,
        "positioning",
        "PositioningNode",
        "PositioningCalculator",
    ),
    produced_event(
        &BALL_PROXIMITY_EVENT_DEFINITION,
        "positioning",
        "PositioningNode",
        "PositioningCalculator",
    ),
];

const ROTATION_EMITTED_EVENTS: &[EmittedEvent] = &[
    produced_event(
        &ROTATION_ROLE_EVENT_DEFINITION,
        "rotation",
        "RotationNode",
        "RotationCalculator",
    ),
    produced_event(
        &FIRST_MAN_CHANGE_EVENT_DEFINITION,
        "rotation",
        "RotationNode",
        "RotationCalculator",
    ),
];

const STATS_TIMELINE_EVENTS_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
    &TIMELINE_ENVELOPE_EVENT_DEFINITION,
    "stats_timeline_events",
    "StatsTimelineEventsNode",
    "StatsTimelineEventsState",
)];

register_event_producer!(
    MATCH_STATS_EVENT_PRODUCER,
    "match_stats",
    MATCH_STATS_EMITTED_EVENTS
);
register_event_producer!(DEMO_EVENT_PRODUCER, "demo", DEMO_EMITTED_EVENTS);
register_event_producer!(
    BACKBOARD_BOUNCE_STATE_EVENT_PRODUCER,
    "backboard_bounce_state",
    BACKBOARD_BOUNCE_STATE_EMITTED_EVENTS
);
register_event_producer!(
    CEILING_SHOT_EVENT_PRODUCER,
    "ceiling_shot",
    CEILING_SHOT_EMITTED_EVENTS
);
register_event_producer!(
    WALL_AERIAL_EVENT_PRODUCER,
    "wall_aerial",
    WALL_AERIAL_EMITTED_EVENTS
);
register_event_producer!(
    WALL_AERIAL_SHOT_EVENT_PRODUCER,
    "wall_aerial_shot",
    WALL_AERIAL_SHOT_EMITTED_EVENTS
);
register_event_producer!(CENTER_EVENT_PRODUCER, "center", CENTER_EMITTED_EVENTS);
register_event_producer!(FLICK_EVENT_PRODUCER, "flick", FLICK_EMITTED_EVENTS);
register_event_producer!(
    MUSTY_FLICK_EVENT_PRODUCER,
    "musty_flick",
    MUSTY_FLICK_EMITTED_EVENTS
);
register_event_producer!(
    DODGE_RESET_EVENT_PRODUCER,
    "dodge_reset",
    DODGE_RESET_EMITTED_EVENTS
);
register_event_producer!(
    DOUBLE_TAP_EVENT_PRODUCER,
    "double_tap",
    DOUBLE_TAP_EMITTED_EVENTS
);
register_event_producer!(
    ONE_TIMER_EVENT_PRODUCER,
    "one_timer",
    ONE_TIMER_EMITTED_EVENTS
);
register_event_producer!(PASS_EVENT_PRODUCER, "pass", PASS_EMITTED_EVENTS);
register_event_producer!(
    BALL_CARRY_EVENT_PRODUCER,
    "ball_carry",
    BALL_CARRY_EMITTED_EVENTS
);
register_event_producer!(
    CONTROLLED_PLAY_EVENT_PRODUCER,
    "controlled_play",
    CONTROLLED_PLAY_EMITTED_EVENTS
);
register_event_producer!(
    FIFTY_FIFTY_EVENT_PRODUCER,
    "fifty_fifty",
    FIFTY_FIFTY_EMITTED_EVENTS
);
register_event_producer!(RUSH_EVENT_PRODUCER, "rush", RUSH_EMITTED_EVENTS);
register_event_producer!(DODGE_EVENT_PRODUCER, "dodge", DODGE_EMITTED_EVENTS);
register_event_producer!(
    SPEED_FLIP_EVENT_PRODUCER,
    "speed_flip",
    SPEED_FLIP_EMITTED_EVENTS
);
register_event_producer!(
    HALF_FLIP_EVENT_PRODUCER,
    "half_flip",
    HALF_FLIP_EMITTED_EVENTS
);
register_event_producer!(
    HALF_VOLLEY_EVENT_PRODUCER,
    "half_volley",
    HALF_VOLLEY_EMITTED_EVENTS
);
register_event_producer!(WAVEDASH_EVENT_PRODUCER, "wavedash", WAVEDASH_EMITTED_EVENTS);
register_event_producer!(WHIFF_EVENT_PRODUCER, "whiff", WHIFF_EMITTED_EVENTS);
register_event_producer!(
    POWERSLIDE_EVENT_PRODUCER,
    "powerslide",
    POWERSLIDE_EMITTED_EVENTS
);
register_event_producer!(TOUCH_EVENT_PRODUCER, "touch", TOUCH_EMITTED_EVENTS);
register_event_producer!(BOOST_EVENT_PRODUCER, "boost", BOOST_EMITTED_EVENTS);
register_event_producer!(BUMP_EVENT_PRODUCER, "bump", BUMP_EMITTED_EVENTS);
register_event_producer!(
    POSSESSION_EVENT_PRODUCER,
    "possession",
    POSSESSION_EMITTED_EVENTS
);
register_event_producer!(
    PLAYER_POSSESSION_EVENT_PRODUCER,
    "player_possession",
    PLAYER_POSSESSION_EMITTED_EVENTS
);
register_event_producer!(
    BALL_HALF_EVENT_PRODUCER,
    "ball_half",
    BALL_HALF_EMITTED_EVENTS
);
register_event_producer!(
    TERRITORIAL_BALL_HALF_EVENT_PRODUCER,
    "territorial_pressure",
    TERRITORIAL_BALL_HALF_EMITTED_EVENTS
);
register_event_producer!(MOVEMENT_EVENT_PRODUCER, "movement", MOVEMENT_EMITTED_EVENTS);
register_event_producer!(
    POSITIONING_EVENT_PRODUCER,
    "positioning",
    POSITIONING_EMITTED_EVENTS
);
register_event_producer!(ROTATION_EVENT_PRODUCER, "rotation", ROTATION_EMITTED_EVENTS);
register_event_producer!(
    STATS_TIMELINE_EVENTS_EVENT_PRODUCER,
    "stats_timeline_events",
    STATS_TIMELINE_EVENTS_EMITTED_EVENTS
);

#[cfg(test)]
#[path = "event_definition_tests.rs"]
mod tests;