1#![cfg_attr(target_arch = "wasm32", allow(dead_code))]
2
3use serde::Serialize;
4use ts_rs::TS;
5
6#[cfg(not(target_arch = "wasm32"))]
7use linkme::distributed_slice;
8
9use super::{
10 BackboardBounceEvent, BallCarryEvent, BallDepthEvent, BallHalfEvent, BallProximityEvent,
11 BallThirdEvent, BoostPickupEvent, BumpEvent, CeilingShotEvent, CenterEvent,
12 ControlledPlayEvent, CorePlayerScoreboardEvent, DemolitionEvent, DepthRoleEvent, DodgeEvent,
13 DodgeResetEvent, DoubleTapEvent, FieldHalfEvent, FieldThirdEvent, FiftyFiftyEvent,
14 FirstManChangeEvent, FlickEvent, FlipResetEvent, HalfFlipEvent, HalfVolleyEvent,
15 LoosePossessionEvent, MovementEvent, OneTimerEvent, PassEvent, PlayerActivityEvent,
16 PlayerPossessionEvent, PossessionEvent, PowerslideEvent, RespawnEvent, RotationRoleEvent,
17 RushEvent, SpeedFlipEvent, TerritorialPressureEvent, TimelineEvent, TouchClassificationEvent,
18 WallAerialEvent, WallAerialShotEvent, WavedashEvent, WhiffEvent,
19};
20use crate::stats::timeline::{Event, EventPayload, EventScope};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
28pub struct EventDefinition {
29 pub id: &'static str,
30 pub label: &'static str,
31 pub category: EventCategory,
32 pub scope: EventScope,
38 pub confidence: DetectionConfidence,
39 pub summary: &'static str,
40 pub approach: &'static [&'static str],
41 pub limitations: &'static [&'static str],
42 pub hidden_from_review: bool,
45 pub variants: &'static [EventVariant],
50}
51
52impl EventDefinition {
53 pub const fn hidden(self, hidden: bool) -> Self {
56 let mut def = self;
57 def.hidden_from_review = hidden;
58 def
59 }
60
61 pub const fn variants(self, variants: &'static [EventVariant]) -> Self {
64 let mut def = self;
65 def.variants = variants;
66 def
67 }
68
69 pub const fn scope(self, scope: EventScope) -> Self {
74 let mut def = self;
75 def.scope = scope;
76 def
77 }
78}
79
80impl EventPayload {
81 pub fn scope(&self) -> EventScope {
88 match self {
89 Self::Timeline(_) => TimelineEvent::DEFINITION.scope,
90 Self::CorePlayer(_) => CorePlayerScoreboardEvent::DEFINITION.scope,
91 Self::Possession(_) => PossessionEvent::DEFINITION.scope,
92 Self::LoosePossession(_) => LoosePossessionEvent::DEFINITION.scope,
93 Self::PlayerPossession(_) => PlayerPossessionEvent::DEFINITION.scope,
94 Self::BallHalf(_) => BallHalfEvent::DEFINITION.scope,
95 Self::BallThird(_) => BallThirdEvent::DEFINITION.scope,
96 Self::TerritorialPressure(_) => TerritorialPressureEvent::DEFINITION.scope,
97 Self::Movement(_) => MovementEvent::DEFINITION.scope,
98 Self::PlayerActivity(_) => PlayerActivityEvent::DEFINITION.scope,
99 Self::FieldThird(_) => FieldThirdEvent::DEFINITION.scope,
100 Self::FieldHalf(_) => FieldHalfEvent::DEFINITION.scope,
101 Self::BallDepth(_) => BallDepthEvent::DEFINITION.scope,
102 Self::DepthRole(_) => DepthRoleEvent::DEFINITION.scope,
103 Self::BallProximity(_) => BallProximityEvent::DEFINITION.scope,
104 Self::ShadowDefense(_) => EventScope::Player,
107 Self::RotationRole(_) => RotationRoleEvent::DEFINITION.scope,
108 Self::FirstManChange(_) => FirstManChangeEvent::DEFINITION.scope,
109 Self::GoalContext(_) => GOAL_CONTEXT_EVENT_DEFINITION.scope,
110 Self::Backboard(_) => BackboardBounceEvent::DEFINITION.scope,
111 Self::CeilingShot(_) => CeilingShotEvent::DEFINITION.scope,
112 Self::WallAerial(_) => WallAerialEvent::DEFINITION.scope,
113 Self::WallAerialShot(_) => WallAerialShotEvent::DEFINITION.scope,
114 Self::Center(_) => CenterEvent::DEFINITION.scope,
115 Self::Flick(_) => FlickEvent::DEFINITION.scope,
116 Self::DodgeReset(_) => DodgeResetEvent::DEFINITION.scope,
117 Self::FlipReset(_) => FlipResetEvent::DEFINITION.scope,
118 Self::DoubleTap(_) => DoubleTapEvent::DEFINITION.scope,
119 Self::FiftyFifty(_) => FiftyFiftyEvent::DEFINITION.scope,
120 Self::Kickoff(_) => KICKOFF_EVENT_DEFINITION.scope,
121 Self::OneTimer(_) => OneTimerEvent::DEFINITION.scope,
122 Self::Pass(_) => PassEvent::DEFINITION.scope,
123 Self::BallCarry(_) => BallCarryEvent::DEFINITION.scope,
124 Self::ControlledPlay(_) => ControlledPlayEvent::DEFINITION.scope,
125 Self::Rush(_) => RushEvent::DEFINITION.scope,
126 Self::Dodge(_) => DodgeEvent::DEFINITION.scope,
127 Self::SpeedFlip(_) => SpeedFlipEvent::DEFINITION.scope,
128 Self::HalfFlip(_) => HalfFlipEvent::DEFINITION.scope,
129 Self::HalfVolley(_) => HalfVolleyEvent::DEFINITION.scope,
130 Self::Wavedash(_) => WavedashEvent::DEFINITION.scope,
131 Self::Whiff(_) => WhiffEvent::DEFINITION.scope,
132 Self::Powerslide(_) => PowerslideEvent::DEFINITION.scope,
133 Self::Touch(_) => TouchClassificationEvent::DEFINITION.scope,
134 Self::BoostPickup(_) => BoostPickupEvent::DEFINITION.scope,
135 Self::Respawn(_) => RespawnEvent::DEFINITION.scope,
136 Self::Bump(_) => BumpEvent::DEFINITION.scope,
137 Self::Demolition(_) => DemolitionEvent::DEFINITION.scope,
138 }
139 }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
145pub struct EventVariant {
146 pub key: &'static str,
147 pub label: &'static str,
148 pub category: EventCategory,
149}
150
151impl EventVariant {
152 pub const fn new(key: &'static str, label: &'static str, category: EventCategory) -> Self {
153 Self {
154 key,
155 label,
156 category,
157 }
158 }
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, TS)]
163#[ts(export)]
164#[serde(rename_all = "snake_case")]
165pub enum EventCategory {
166 Core,
167 Basic,
168 Mechanic,
169 Positioning,
170 Annotation,
171 Other,
172 Context,
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
179pub struct DetectionConfidence {
180 pub approach: ApproachConfidenceLevel,
181 pub true_positive_evidence: TruePositiveEvidenceLevel,
182 pub false_positive_evidence: DetectionIssueEvidenceLevel,
183 pub false_negative_evidence: DetectionIssueEvidenceLevel,
184 pub testing: TestingThoroughnessLevel,
185 pub known_issues: &'static [KnownIssueRef],
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
190#[serde(rename_all = "snake_case")]
191pub enum ApproachConfidenceLevel {
192 Unknown,
193 High,
194 Medium,
195 Low,
196 Experimental,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
201#[serde(rename_all = "snake_case")]
202pub enum TruePositiveEvidenceLevel {
203 NotEvaluated,
204 Plausible,
205 ManuallyConfirmed,
206 AutomatedTestCovered,
207 RepeatedlyConfirmed,
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
212#[serde(rename_all = "snake_case")]
213pub enum DetectionIssueEvidenceLevel {
214 NotEvaluated,
215 NoneKnown,
216 Suspected,
217 Observed,
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
222#[serde(rename_all = "snake_case")]
223pub enum TestingThoroughnessLevel {
224 Untested,
225 ManualSpotCheck,
226 TargetedAutomatedTest,
227 MultipleTargetedTests,
228 CuratedSuite,
229 CorpusSample,
230}
231
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
234pub struct KnownIssueRef {
235 pub id: &'static str,
236 pub summary: &'static str,
237 pub url: Option<&'static str>,
238}
239
240pub const UNKNOWN_DETECTION_CONFIDENCE: DetectionConfidence = DetectionConfidence {
241 approach: ApproachConfidenceLevel::Unknown,
242 true_positive_evidence: TruePositiveEvidenceLevel::NotEvaluated,
243 false_positive_evidence: DetectionIssueEvidenceLevel::NotEvaluated,
244 false_negative_evidence: DetectionIssueEvidenceLevel::NotEvaluated,
245 testing: TestingThoroughnessLevel::Untested,
246 known_issues: &[],
247};
248
249pub const fn pending_event_definition(
250 id: &'static str,
251 label: &'static str,
252 category: EventCategory,
253) -> EventDefinition {
254 event_definition(id, label, category, "Definition pending.", &[])
255}
256
257pub const fn event_definition(
258 id: &'static str,
259 label: &'static str,
260 category: EventCategory,
261 summary: &'static str,
262 approach: &'static [&'static str],
263) -> EventDefinition {
264 EventDefinition {
265 id,
266 label,
267 category,
268 scope: EventScope::Match,
271 confidence: UNKNOWN_DETECTION_CONFIDENCE,
272 summary,
273 approach,
274 limitations: &[],
275 hidden_from_review: false,
276 variants: &[],
277 }
278}
279
280pub const fn produced_event(
281 event: &'static EventDefinition,
282 node_name: &'static str,
283 node_type: &'static str,
284 calculator_type: &'static str,
285) -> EmittedEvent {
286 EmittedEvent {
287 event,
288 producer: ProducerDefinition {
289 node_name,
290 node_type,
291 calculator_type,
292 implementation_notes: &[],
293 },
294 }
295}
296
297pub const fn produced_event_for<E: StatsEvent>(
298 node_name: &'static str,
299 node_type: &'static str,
300 calculator_type: &'static str,
301) -> EmittedEvent {
302 produced_event(&E::DEFINITION, node_name, node_type, calculator_type)
303}
304
305pub trait StatsEvent {
307 const DEFINITION: EventDefinition;
308}
309
310#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
312pub struct ProducerDefinition {
313 pub node_name: &'static str,
314 pub node_type: &'static str,
315 pub calculator_type: &'static str,
316 pub implementation_notes: &'static [&'static str],
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
321pub struct EmittedEvent {
322 pub event: &'static EventDefinition,
323 pub producer: ProducerDefinition,
324}
325
326#[cfg(not(target_arch = "wasm32"))]
334#[distributed_slice]
335pub static EVENT_DEFINITIONS: [EventDefinition];
336
337#[cfg(not(target_arch = "wasm32"))]
343pub fn all_event_definitions() -> &'static [EventDefinition] {
344 use std::sync::OnceLock;
345 static SORTED: OnceLock<Vec<EventDefinition>> = OnceLock::new();
346 SORTED.get_or_init(|| {
347 let mut defs: Vec<EventDefinition> = EVENT_DEFINITIONS.iter().copied().collect();
348 defs.sort_by(|left, right| left.id.cmp(right.id));
349 let mut deduped: Vec<EventDefinition> = Vec::with_capacity(defs.len());
350 for def in defs {
351 match deduped.last() {
352 Some(last) if last.id == def.id => {
353 assert!(
354 *last == def,
355 "conflicting EventDefinition registrations for id {:?}",
356 def.id
357 );
358 }
359 _ => deduped.push(def),
360 }
361 }
362 deduped
363 })
364}
365
366#[derive(Debug, Clone, Serialize, TS)]
375#[ts(export)]
376pub struct EventVariantTs {
377 pub key: String,
378 pub label: String,
379 pub category: EventCategory,
380}
381
382#[derive(Debug, Clone, Serialize, TS)]
388#[ts(export)]
389pub struct EventDefinitionCatalogEntry {
390 pub key: String,
391 pub label: String,
392 pub category: EventCategory,
393 pub hidden_from_review: bool,
394 pub variants: Vec<EventVariantTs>,
395}
396
397#[cfg(not(target_arch = "wasm32"))]
400pub fn event_definition_catalog() -> Vec<EventDefinitionCatalogEntry> {
401 all_event_definitions()
402 .iter()
403 .map(|definition| EventDefinitionCatalogEntry {
404 key: definition.id.to_owned(),
405 label: definition.label.to_owned(),
406 category: definition.category,
407 hidden_from_review: definition.hidden_from_review,
408 variants: definition
409 .variants
410 .iter()
411 .map(|variant| EventVariantTs {
412 key: variant.key.to_owned(),
413 label: variant.label.to_owned(),
414 category: variant.category,
415 })
416 .collect(),
417 })
418 .collect()
419}
420
421#[cfg(test)]
427#[test]
428fn export_bindings_event_definition_catalog() {
429 let catalog = event_definition_catalog();
430 let json = serde_json::to_string_pretty(&catalog).expect("serialize event catalog");
431 let contents = format!(
432 "// This file was generated from the subtr-actor event-definition registry. \
433Do not edit this file manually.\n\
434import type {{ EventDefinitionCatalogEntry }} from \"./EventDefinitionCatalogEntry.ts\";\n\
435\n\
436export const EVENT_DEFINITION_CATALOG: EventDefinitionCatalogEntry[] = {json};\n"
437 );
438
439 if let Ok(dir) = std::env::var("TS_RS_EXPORT_DIR") {
440 let path = std::path::Path::new(&dir).join("eventDefinitionCatalog.generated.ts");
441 std::fs::write(&path, contents).expect("write event catalog data file");
442 }
443}
444
445#[cfg(test)]
446#[path = "event_definition_scope_tests.rs"]
447mod scope_tests;
448
449macro_rules! register_stats_event_definition {
453 ($definition:ident) => {
454 paste::paste! {
455 #[cfg(not(target_arch = "wasm32"))]
456 #[distributed_slice(EVENT_DEFINITIONS)]
457 static [<$definition _REGISTRATION>]: EventDefinition = $definition;
458 }
459 };
460}
461
462macro_rules! define_stats_event {
463 (
464 $event_type:ty,
465 $definition:ident,
466 $id:literal,
467 $label:literal,
468 $category:expr_2021,
469 summary = $summary:literal,
470 approach = [$($approach:literal),* $(,)?]
471 $(, $modifier:ident = $modval:expr_2021)* $(,)?
472 ) => {
473 pub const $definition: EventDefinition =
474 event_definition($id, $label, $category, $summary, &[$($approach),*])
475 $(.$modifier($modval))*;
476
477 impl StatsEvent for $event_type {
478 const DEFINITION: EventDefinition = $definition;
479 }
480
481 register_stats_event_definition!($definition);
482 };
483
484 (
485 $event_type:ty,
486 $definition:ident,
487 $id:literal,
488 $label:literal,
489 $category:expr_2021
490 $(, $modifier:ident = $modval:expr_2021)* $(,)?
491 ) => {
492 pub const $definition: EventDefinition =
493 pending_event_definition($id, $label, $category)
494 $(.$modifier($modval))*;
495
496 impl StatsEvent for $event_type {
497 const DEFINITION: EventDefinition = $definition;
498 }
499
500 register_stats_event_definition!($definition);
501 };
502}
503
504const BOOST_PICKUP_VARIANTS: &[EventVariant] = &[EventVariant::new(
511 "boost_pickup",
512 "Boost Pickup",
513 EventCategory::Other,
514)];
515
516pub const ASSIST_EVENT_DEFINITION: EventDefinition =
521 pending_event_definition("assist", "Assist", EventCategory::Core);
522register_stats_event_definition!(ASSIST_EVENT_DEFINITION);
523
524pub const GOAL_EVENT_DEFINITION: EventDefinition =
525 pending_event_definition("goal", "Goal", EventCategory::Core);
526register_stats_event_definition!(GOAL_EVENT_DEFINITION);
527
528pub const SAVE_EVENT_DEFINITION: EventDefinition =
529 pending_event_definition("save", "Save", EventCategory::Core);
530register_stats_event_definition!(SAVE_EVENT_DEFINITION);
531
532pub const SHOT_EVENT_DEFINITION: EventDefinition =
533 pending_event_definition("shot", "Shot", EventCategory::Core);
534register_stats_event_definition!(SHOT_EVENT_DEFINITION);
535
536pub const KICKOFF_EVENT_DEFINITION: EventDefinition =
537 pending_event_definition("kickoff", "Kickoff", EventCategory::Core).scope(EventScope::Player);
538register_stats_event_definition!(KICKOFF_EVENT_DEFINITION);
539
540pub const GOAL_CONTEXT_EVENT_DEFINITION: EventDefinition =
541 pending_event_definition("goal_context", "Goal Context", EventCategory::Context).hidden(true);
542register_stats_event_definition!(GOAL_CONTEXT_EVENT_DEFINITION);
543
544pub const AIR_DRIBBLE_EVENT_DEFINITION: EventDefinition = event_definition(
545 "air_dribble",
546 "Air Dribble",
547 EventCategory::Mechanic,
548 "An airborne ball-control sequence where a player keeps the ball under control off the ground.",
549 &[
550 "Reuse the ball-carry sequence sampler's air-dribble carry kind, which tracks player-owned ball control while airborne.",
551 "Surface the span when a completed ball-carry sequence is classified as an air dribble rather than a grounded carry.",
552 ],
553);
554register_stats_event_definition!(AIR_DRIBBLE_EVENT_DEFINITION);
555
556define_stats_event!(
557 TimelineEvent,
558 TIMELINE_EVENT_DEFINITION,
559 "timeline",
560 "Replay Timeline Event",
561 EventCategory::Core,
562 hidden = true
563);
564define_stats_event!(
565 CorePlayerScoreboardEvent,
566 CORE_PLAYER_SCOREBOARD_EVENT_DEFINITION,
567 "core_player_scoreboard",
568 "Core Player Scoreboard",
569 EventCategory::Core,
570 hidden = true
571);
572define_stats_event!(
573 BackboardBounceEvent,
574 BACKBOARD_BOUNCE_EVENT_DEFINITION,
575 "backboard_bounce",
576 "Backboard Hit",
577 EventCategory::Basic,
578 summary = "A ball rebound off the opponent backboard attributed to the player who sent the ball there.",
579 approach = [
580 "Track the last touch during live play and attribute a later backboard rebound to that touch when it occurs within the configured attribution window.",
581 "Require the ball to be high, near the backboard face, and moving toward the backboard before contact.",
582 "Confirm the contact either by rebound velocity away from the backboard or by a same-player simultaneous touch at the backboard face.",
583 ],
584 scope = EventScope::Player
585);
586define_stats_event!(
587 CeilingShotEvent,
588 CEILING_SHOT_EVENT_DEFINITION,
589 "ceiling_shot",
590 "Ceiling Shot",
591 EventCategory::Mechanic,
592 summary = "A shot touch shortly after the player contacts the ceiling and drops back toward the ball.",
593 approach = [
594 "Record recent ceiling contacts when the car is near the ceiling and oriented roof-first against it.",
595 "Match a later touch by the same player within the ceiling-contact window after the player has separated from the ceiling.",
596 "Score the candidate from contact timing, height, separation, forward alignment, approach speed, ball impulse, and ceiling-contact alignment.",
597 ],
598 scope = EventScope::Player
599);
600define_stats_event!(
601 WallAerialEvent,
602 WALL_AERIAL_EVENT_DEFINITION,
603 "wall_aerial",
604 "Wall Aerial",
605 EventCategory::Mechanic,
606 summary = "An aerial launched off a side, end, or corner wall, whether or not the player is carrying the ball.",
607 approach = [
608 "Track how long each player rides the wall surface (a side or end wall), regardless of whether they have the ball.",
609 "Arm a wall-aerial candidate when a player who rode the wall long enough leaves it while airborne.",
610 "Classify the takeoff wall relative to the player's attack direction (left/right side, front/back end, or a corner) from the car's surface normal at the last wall contact.",
611 "Emit on a later aerial touch by the same player while the player and ball are high enough and the takeoff-to-touch window holds.",
612 ],
613 scope = EventScope::Player
614);
615define_stats_event!(
616 WallAerialShotEvent,
617 WALL_AERIAL_SHOT_EVENT_DEFINITION,
618 "wall_aerial_shot",
619 "Wall Shot",
620 EventCategory::Mechanic,
621 summary = "A shot credited to a player shortly after taking off from a wall.",
622 approach = [
623 "Track recent wall contact for each player and arm a candidate when the player leaves the wall while still above the ground threshold.",
624 "Classify the takeoff wall relative to the player's attack direction (left/right side, front/back end, or a corner) from the car's surface normal at the last wall contact.",
625 "Match a subsequent shot stat event by that player within the takeoff-to-shot window.",
626 "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.",
627 ],
628 scope = EventScope::Player
629);
630define_stats_event!(
631 CenterEvent,
632 CENTER_EVENT_DEFINITION,
633 "center",
634 "Center",
635 EventCategory::Basic,
636 summary = "A touch that moves the ball from a wide attacking position toward the central attacking area.",
637 approach = [
638 "Start a pending center from a live-play touch, unless that player immediately has a shot or goal event.",
639 "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.",
640 "Clear the candidate when it ages out, loses attribution, or becomes a shot/goal by the same player instead of a center.",
641 ],
642 scope = EventScope::Player
643);
644define_stats_event!(
645 FlickEvent,
646 FLICK_EVENT_DEFINITION,
647 "flick",
648 "Flick",
649 EventCategory::Mechanic,
650 summary = "A dodge-powered touch following a short controlled carry setup.",
651 approach = [
652 "Track controlled setup windows where the current controlling player keeps the ball close above the car within local-position and gap thresholds.",
653 "Record dodge starts that happen immediately after, or during, a qualifying setup, capturing the dodge torque (the flip axis) and the run's travel direction.",
654 "Classify the flick kind from the dodge direction: decompose the dodge torque in the travel frame into forward/back and side components, labeling a backflip that still launches the ball forward as a reverse flick, a sideways-dominant dodge as a side flick, and a forward dodge as a forward flick.",
655 "Tag handedness left/right from the ball's lateral deflection relative to travel.",
656 "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.",
657 ],
658 scope = EventScope::Player
659);
660define_stats_event!(
661 DodgeResetEvent,
662 DODGE_RESET_EVENT_DEFINITION,
663 "dodge_reset",
664 "Dodge Reset",
665 EventCategory::Basic,
666 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.",
667 approach = [
668 "Consume dodge-refreshed replay events and preserve the player, team, frame, time, and counter value.",
669 "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.",
670 "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.",
671 "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.",
672 ],
673 scope = EventScope::Player
674);
675define_stats_event!(
676 DoubleTapEvent,
677 DOUBLE_TAP_EVENT_DEFINITION,
678 "double_tap",
679 "Double Tap",
680 EventCategory::Mechanic,
681 summary = "A same-player follow-up touch after an attributed backboard bounce that creates a shot-like trajectory.",
682 approach = [
683 "Arm a pending double tap from a backboard-bounce event attributed to the player who sent the ball to the backboard.",
684 "Require the same player and team to touch the ball again during live play within the follow-up window.",
685 "Accept the follow-up only when the post-touch straight-line ball trajectory projects into or close to the opponent goal mouth.",
686 ],
687 scope = EventScope::Player
688);
689define_stats_event!(
690 OneTimerEvent,
691 ONE_TIMER_EVENT_DEFINITION,
692 "one_timer",
693 "One Timer",
694 EventCategory::Mechanic,
695 summary =
696 "A fast receiver touch from a completed pass that is immediately directed toward goal.",
697 approach = [
698 "Consume newly completed pass events on the frame they are recorded.",
699 "Require the current ball speed after the receiver's touch to exceed the one-timer speed threshold.",
700 "Require the post-touch ball velocity to align with the opponent goal center direction.",
701 ],
702 scope = EventScope::Player
703);
704define_stats_event!(
705 PassEvent,
706 PASS_EVENT_DEFINITION,
707 "pass",
708 "Pass",
709 EventCategory::Basic,
710 summary = "A same-team touch sequence where one player sends the ball to a different teammate.",
711 approach = [
712 "Track the last attributed touch in live play and compare it to each new touch.",
713 "Emit when a different teammate touches the ball within the pass window after the ball has traveled far enough.",
714 "Classify the pass as direct, backboard, fifty-fifty, or fifty-fifty backboard using intervening backboard-bounce and fifty-fifty state.",
715 ],
716 scope = EventScope::Player
717);
718define_stats_event!(
719 BallCarryEvent,
720 BALL_CARRY_EVENT_DEFINITION,
721 "ball_carry",
722 "Ball Carry",
723 EventCategory::Mechanic,
724 summary =
725 "A sustained player-ball control sequence, covering grounded carries and air dribbles.",
726 approach = [
727 "Use continuous ball-control tracking to build player-owned sequences while live play is active.",
728 "Sample grounded carries from close horizontal/vertical ball gaps over the car, excluding wall contact.",
729 "Sample air dribbles with the air-dribble policy, then emit completed sequences that meet the duration and validity rules for their carry kind.",
730 ],
731 scope = EventScope::Player
732);
733define_stats_event!(
734 ControlledPlayEvent,
735 CONTROLLED_PLAY_EVENT_DEFINITION,
736 "controlled_play",
737 "Controlled Play",
738 EventCategory::Other,
739 summary =
740 "A same-player possession episode with multiple touches and sustained close-ball time.",
741 approach = [
742 "Start a player-owned candidate from an attributed touch during live play.",
743 "Require at least two distinct touches by the same player with at least one second between the first and last touch.",
744 "Require sustained proximity to the ball and finish the candidate when another player touches, live play ends, or the touch chain times out.",
745 ],
746 scope = EventScope::Team
747);
748define_stats_event!(
749 FiftyFiftyEvent,
750 FIFTY_FIFTY_EVENT_DEFINITION,
751 "fifty_fifty",
752 "50/50",
753 EventCategory::Other,
754 summary = "A contested ball interaction involving touches or pressure from both teams in a short window.",
755 approach = [
756 "Start an active 50/50 when a frame contains touches from both teams, including kickoff-specific tracking.",
757 "Continue the contest for short follow-up touch windows while either involved team remains in contact.",
758 "Resolve after a delay once ball movement, possession state, or max duration gives a winner, possession outcome, or neutral result.",
759 ],
760 scope = EventScope::Team
761);
762define_stats_event!(
763 RushEvent,
764 RUSH_EVENT_DEFINITION,
765 "rush",
766 "Rush",
767 EventCategory::Other,
768 summary = "A quick possession transition where the attacking team has numbers moving out of its defensive half.",
769 approach = [
770 "Start from a possession change when the ball is still in the new attacking team's defensive half.",
771 "Count non-demoed attackers near or ahead of the ball and defenders between the ball and their own goal.",
772 "Emit once the new attacking team retains possession long enough with at least two attackers and at least one defender in the rush shape.",
773 ],
774 scope = EventScope::Team
775);
776define_stats_event!(
777 DodgeEvent,
778 DODGE_EVENT_DEFINITION,
779 "dodge",
780 "Dodge",
781 EventCategory::Basic,
782 summary = "A dodge-start event, optionally carrying a rough estimated dodge impulse when the velocity change is measurable.",
783 approach = [
784 "Start on the replay's dodge-active rising edge for each player.",
785 "Sample the player's velocity change over the early dodge window and subtract an approximate forward boost contribution when boost is active.",
786 "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.",
787 ],
788 scope = EventScope::Player
789);
790define_stats_event!(
791 SpeedFlipEvent,
792 SPEED_FLIP_EVENT_DEFINITION,
793 "speed_flip",
794 "Speed Flip",
795 EventCategory::Mechanic,
796 summary = "A ground-started diagonal dodge/cancel acceleration pattern, primarily intended for kickoff speed flips.",
797 approach = [
798 "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.",
799 "Track speed, forward alignment, boost alignment, diagonal angular-velocity balance, and early forward acceleration during a short evaluation window.",
800 "Emit when the combined diagonal, cancel, speed, and alignment confidence score clears the speed-flip threshold before the candidate expires.",
801 ],
802 scope = EventScope::Player
803);
804define_stats_event!(
805 HalfFlipEvent,
806 HALF_FLIP_EVENT_DEFINITION,
807 "half_flip",
808 "Half Flip",
809 EventCategory::Mechanic,
810 summary = "A dodge sequence that cancels a flip into an opposite facing direction.",
811 approach = [
812 "Start candidates on low grounded or low-air dodge rising edges.",
813 "Track the car's forward vector through the evaluation window, including vertical flip evidence and final horizontal facing direction.",
814 "Emit when the candidate has pitched through a flip, reaches and retains roughly opposite facing instead of rotating through a full end-over-end flip, and finishes with a meaningful horizontal facing direction.",
815 ],
816 scope = EventScope::Player
817);
818define_stats_event!(
819 HalfVolleyEvent,
820 HALF_VOLLEY_EVENT_DEFINITION,
821 "half_volley",
822 "Half Volley",
823 EventCategory::Mechanic,
824 summary = "A fast touch shortly after the ball bounces off the floor, paired with a recent player dodge.",
825 approach = [
826 "Detect floor bounces from ball height and vertical velocity reversal when no touch occurs on the bounce frame.",
827 "Track each player's recent ground contact and dodge start.",
828 "Emit on a same-player touch shortly after the floor bounce and dodge when the post-touch ball speed clears the configured threshold.",
829 ],
830 scope = EventScope::Player
831);
832define_stats_event!(
833 WavedashEvent,
834 WAVEDASH_EVENT_DEFINITION,
835 "wavedash",
836 "Wavedash",
837 EventCategory::Mechanic,
838 summary = "A low airborne dodge that lands quickly and converts the dodge into ground speed.",
839 approach = [
840 "Start candidates on dodge rising edges from a low but airborne height.",
841 "Watch for a landing within the wavedash window while the car is sufficiently upright.",
842 "Score confidence from dodge-to-landing timing, starting height, speed gain or landing speed, and landing uprightness.",
843 ],
844 scope = EventScope::Player
845);
846define_stats_event!(
847 WhiffEvent,
848 WHIFF_EVENT_DEFINITION,
849 "whiff",
850 "Whiff",
851 EventCategory::Other,
852 summary = "A committed attempt near the ball that does not result in that player touching it.",
853 approach = [
854 "Start candidates when a player gets within hitbox distance of the ball while moving or dodging toward it with sufficient alignment and closing speed.",
855 "Track the closest approach while the candidate remains near the ball.",
856 "Resolve as a whiff when the player exits the candidate window without touching, or as beaten-to-ball when an opponent touches first.",
857 ],
858 scope = EventScope::Player
859);
860define_stats_event!(
861 PowerslideEvent,
862 POWERSLIDE_EVENT_DEFINITION,
863 "powerslide",
864 "Powerslide",
865 EventCategory::Basic,
866 summary = "A state-change event for effective grounded powerslide use.",
867 approach = [
868 "Read each player's powerslide-active input/state on every frame.",
869 "Treat powerslide as effective only while the player is close enough to the ground.",
870 "Emit when a player's effective powerslide state changes between active and inactive.",
871 ],
872 scope = EventScope::Player
873);
874define_stats_event!(
875 TouchClassificationEvent,
876 TOUCH_CLASSIFICATION_EVENT_DEFINITION,
877 "touch",
878 "Touch",
879 EventCategory::Basic,
880 summary = "A classified ball touch carrying a set of independent tags: strength, surface/height context, action, and an outcome-based possession tag.",
881 approach = [
882 "Carry classification as a set of (group, value) tags rather than rivalrous fields, so independent reads coexist: a boom that the hitter recovers is tagged both action=boom and possession=advance.",
883 "Tag strength kind (control, medium hit, hard hit) from the ball speed change, plus surface, height band, and dodge context for the touching player at contact time.",
884 "Resolve a single mutually-exclusive action by precedence: replay-confirmed saves and shots first, then geometric save/shot trajectory projections, then clears out of the defensive third, then passes led toward a teammate, then booms hit hard downfield into space. A touch matching none of these has no action tag at all, rather than a catch-all value.",
885 "Retroactively raise the action to shot/save by outcome when stronger evidence arrives after the touch: a scored goal (the scorer's touch), a replay shot/save stat event that lands after the touch, or a settled post-touch trajectory that crosses the goal mouth. Upgrades only ever raise the action, never downgrade it.",
886 "Tag a touch contested independently of its action (a contested shot stays a shot and is also flagged contested), rather than collapsing contests into the action.",
887 "Retroactively add a possession tag by outcome on pass/clear/boom and action-less touches: control when the toucher stays close to the ball while matching its velocity for most of a short follow window or wins the follow-up touch with the ball kept near, advance when they win the follow-up touch after playing the ball clear into space.",
888 "Tag a touch's reception 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.",
889 ],
890 scope = EventScope::Player
891);
892define_stats_event!(
893 BoostPickupEvent,
894 BOOST_PICKUP_EVENT_DEFINITION,
895 "boost_pickups",
896 "Boost Pickup",
897 EventCategory::Other,
898 hidden = true,
899 variants = BOOST_PICKUP_VARIANTS,
900 scope = EventScope::Player
901);
902define_stats_event!(
903 RespawnEvent,
904 BOOST_RESPAWN_EVENT_DEFINITION,
905 "boost_respawn",
906 "Respawn",
907 EventCategory::Other,
908 scope = EventScope::Player
909);
910define_stats_event!(
911 BumpEvent,
912 BUMP_EVENT_DEFINITION,
913 "bump",
914 "Bump",
915 EventCategory::Other,
916 scope = EventScope::Player
917);
918define_stats_event!(
919 DemolitionEvent,
920 DEMOLITION_EVENT_DEFINITION,
921 "demolition",
922 "Demolition",
923 EventCategory::Basic,
924 scope = EventScope::Player
925);
926define_stats_event!(
927 PossessionEvent,
928 POSSESSION_EVENT_DEFINITION,
929 "possession",
930 "Possession",
931 EventCategory::Other,
932 scope = EventScope::Team
933);
934define_stats_event!(
935 LoosePossessionEvent,
936 LOOSE_POSSESSION_EVENT_DEFINITION,
937 "loose_possession",
938 "Loose Possession",
939 EventCategory::Other,
940 summary = "A team-possession span under the loose definition: the last team to touch owns the ball until the opponent takes it away.",
941 approach = [
942 "Track the last team to touch the ball, keeping possession through loose balls, teammate passes, and repelled 50-50 challenges.",
943 "Transfer possession only when the opponent demonstrably wins the ball, backdating the boundary to the opponent's takeover touch so there is no neutral gap.",
944 "Credit neutral only before the first touch of a live stretch or during a contested scramble off a neutral ball.",
945 ],
946 scope = EventScope::Team
947);
948define_stats_event!(
949 PlayerPossessionEvent,
950 PLAYER_POSSESSION_EVENT_DEFINITION,
951 "player_possession",
952 "Player Possession",
953 EventCategory::Other,
954 summary = "A contiguous single-player possession span enriched with touch, ball-progress, and sustained-control activity.",
955 approach = [
956 "Follow the shared possession tracker's controlling player and open a span when a player establishes control.",
957 "Bridge contested or pending-turnover interruptions shorter than the merge gap when the same player re-establishes control, excluding the gap from possessed duration.",
958 "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.",
959 ],
960 scope = EventScope::Player
961);
962define_stats_event!(
963 BallHalfEvent,
964 PRESSURE_EVENT_DEFINITION,
965 "ball_half",
966 "Ball Half",
967 EventCategory::Other,
968 scope = EventScope::Team
969);
970define_stats_event!(
971 TerritorialPressureEvent,
972 TERRITORIAL_PRESSURE_EVENT_DEFINITION,
973 "territorial_pressure",
974 "Territorial Pressure",
975 EventCategory::Other,
976 scope = EventScope::Team
977);
978define_stats_event!(
979 MovementEvent,
980 MOVEMENT_EVENT_DEFINITION,
981 "movement",
982 "Movement",
983 EventCategory::Other,
984 scope = EventScope::Player
985);
986define_stats_event!(
987 PlayerActivityEvent,
988 PLAYER_ACTIVITY_EVENT_DEFINITION,
989 "player_activity",
990 "Player Activity",
991 EventCategory::Positioning,
992 scope = EventScope::Player
993);
994define_stats_event!(
995 FieldThirdEvent,
996 FIELD_THIRD_EVENT_DEFINITION,
997 "field_third",
998 "Field Third",
999 EventCategory::Positioning,
1000 scope = EventScope::Player
1001);
1002define_stats_event!(
1003 FieldHalfEvent,
1004 FIELD_HALF_EVENT_DEFINITION,
1005 "field_half",
1006 "Field Half",
1007 EventCategory::Positioning,
1008 scope = EventScope::Player
1009);
1010define_stats_event!(
1011 BallDepthEvent,
1012 BALL_DEPTH_EVENT_DEFINITION,
1013 "ball_depth",
1014 "Ball Depth",
1015 EventCategory::Positioning,
1016 scope = EventScope::Player
1017);
1018define_stats_event!(
1019 BallThirdEvent,
1020 BALL_THIRD_EVENT_DEFINITION,
1021 "ball_third",
1022 "Ball Third",
1023 EventCategory::Positioning,
1024 scope = EventScope::Player
1025);
1026define_stats_event!(
1027 DepthRoleEvent,
1028 DEPTH_ROLE_EVENT_DEFINITION,
1029 "depth_role",
1030 "Depth Role",
1031 EventCategory::Positioning,
1032 scope = EventScope::Player
1033);
1034define_stats_event!(
1035 BallProximityEvent,
1036 BALL_PROXIMITY_EVENT_DEFINITION,
1037 "ball_proximity",
1038 "Ball Proximity",
1039 EventCategory::Positioning,
1040 scope = EventScope::Player
1041);
1042define_stats_event!(
1043 RotationRoleEvent,
1044 ROTATION_ROLE_EVENT_DEFINITION,
1045 "rotation_role",
1046 "Rotation Role",
1047 EventCategory::Positioning,
1048 scope = EventScope::Player
1049);
1050define_stats_event!(
1051 FirstManChangeEvent,
1052 FIRST_MAN_CHANGE_EVENT_DEFINITION,
1053 "first_man_change",
1054 "First-Man Change",
1055 EventCategory::Positioning,
1056 scope = EventScope::Player
1057);
1058define_stats_event!(
1059 FlipResetEvent,
1060 FLIP_RESET_EVENT_DEFINITION,
1061 "flip_reset",
1062 "Flip Reset",
1063 EventCategory::Mechanic,
1064 summary = "An on-ball dodge refresh that is confirmed when the player uses the gained dodge and touches the ball again before landing.",
1065 approach = [
1066 "Consume on-ball dodge refreshes detected from replay state as pending flip-reset candidates.",
1067 "Require a later dodge start by the same player while the reset is still pending.",
1068 "Confirm only when that player touches the ball while dodge-active before landing and within the reset-to-touch window.",
1069 ],
1070 scope = EventScope::Player
1071);
1072define_stats_event!(
1073 Event,
1074 TIMELINE_ENVELOPE_EVENT_DEFINITION,
1075 "event",
1076 "Event",
1077 EventCategory::Basic,
1078 summary = "A shared event envelope with common metadata and a typed event payload.",
1079 approach = [
1080 "Collect completed events from the analysis graph at finish time.",
1081 "Wrap each typed event payload with common timing, participant, team, position, confidence, and stream metadata.",
1082 "Serialize timeline events as a single heterogeneous event list for playback and analysis consumers.",
1083 ]
1084);
1085
1086pub(crate) const MATCH_STATS_EMITTED_EVENTS: &[EmittedEvent] = &[
1092 produced_event(
1093 &TIMELINE_EVENT_DEFINITION,
1094 "match_stats",
1095 "MatchStatsNode",
1096 "MatchStatsCalculator",
1097 ),
1098 produced_event(
1099 &CORE_PLAYER_SCOREBOARD_EVENT_DEFINITION,
1100 "match_stats",
1101 "MatchStatsNode",
1102 "MatchStatsCalculator",
1103 ),
1104];
1105
1106pub(crate) const DEMO_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1107 &DEMOLITION_EVENT_DEFINITION,
1108 "demo",
1109 "DemoNode",
1110 "DemoCalculator",
1111)];
1112
1113pub(crate) const BACKBOARD_BOUNCE_STATE_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1114 &BACKBOARD_BOUNCE_EVENT_DEFINITION,
1115 "backboard_bounce_state",
1116 "BackboardBounceStateNode",
1117 "BackboardBounceCalculator",
1118)];
1119
1120pub(crate) const CEILING_SHOT_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1121 &CEILING_SHOT_EVENT_DEFINITION,
1122 "ceiling_shot",
1123 "CeilingShotNode",
1124 "CeilingShotCalculator",
1125)];
1126
1127pub(crate) const WALL_AERIAL_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1128 &WALL_AERIAL_EVENT_DEFINITION,
1129 "wall_aerial",
1130 "WallAerialNode",
1131 "WallAerialCalculator",
1132)];
1133
1134pub(crate) const WALL_AERIAL_SHOT_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1135 &WALL_AERIAL_SHOT_EVENT_DEFINITION,
1136 "wall_aerial_shot",
1137 "WallAerialShotNode",
1138 "WallAerialShotCalculator",
1139)];
1140
1141pub(crate) const CENTER_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1142 &CENTER_EVENT_DEFINITION,
1143 "center",
1144 "CenterNode",
1145 "CenterCalculator",
1146)];
1147
1148pub(crate) const FLICK_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1149 &FLICK_EVENT_DEFINITION,
1150 "flick",
1151 "FlickNode",
1152 "FlickCalculator",
1153)];
1154
1155pub(crate) const DODGE_RESET_EMITTED_EVENTS: &[EmittedEvent] = &[
1156 produced_event(
1157 &DODGE_RESET_EVENT_DEFINITION,
1158 "dodge_reset",
1159 "DodgeResetNode",
1160 "DodgeResetCalculator",
1161 ),
1162 produced_event(
1163 &FLIP_RESET_EVENT_DEFINITION,
1164 "flip_reset",
1165 "DodgeResetNode",
1166 "DodgeResetCalculator",
1167 ),
1168];
1169
1170pub(crate) const DOUBLE_TAP_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1171 &DOUBLE_TAP_EVENT_DEFINITION,
1172 "double_tap",
1173 "DoubleTapNode",
1174 "DoubleTapCalculator",
1175)];
1176
1177pub(crate) const ONE_TIMER_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1178 &ONE_TIMER_EVENT_DEFINITION,
1179 "one_timer",
1180 "OneTimerNode",
1181 "OneTimerCalculator",
1182)];
1183
1184pub(crate) const PASS_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1185 &PASS_EVENT_DEFINITION,
1186 "pass",
1187 "PassNode",
1188 "PassCalculator",
1189)];
1190
1191pub(crate) const BALL_CARRY_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1192 &BALL_CARRY_EVENT_DEFINITION,
1193 "ball_carry",
1194 "BallCarryNode",
1195 "BallCarryCalculator",
1196)];
1197
1198pub(crate) const AIR_DRIBBLE_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1199 &BALL_CARRY_EVENT_DEFINITION,
1200 "air_dribble",
1201 "AirDribbleNode",
1202 "AirDribbleCalculator",
1203)];
1204
1205pub(crate) const CONTROLLED_PLAY_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1206 &CONTROLLED_PLAY_EVENT_DEFINITION,
1207 "controlled_play",
1208 "ControlledPlayNode",
1209 "ControlledPlayCalculator",
1210)];
1211
1212pub(crate) const FIFTY_FIFTY_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1213 &FIFTY_FIFTY_EVENT_DEFINITION,
1214 "fifty_fifty",
1215 "FiftyFiftyNode",
1216 "FiftyFiftyCalculator",
1217)];
1218
1219pub(crate) const RUSH_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1220 &RUSH_EVENT_DEFINITION,
1221 "rush",
1222 "RushNode",
1223 "RushCalculator",
1224)];
1225
1226pub(crate) const DODGE_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1227 &DODGE_EVENT_DEFINITION,
1228 "dodge",
1229 "FlipImpulseNode",
1230 "FlipImpulseCalculator",
1231)];
1232
1233pub(crate) const SPEED_FLIP_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1234 &SPEED_FLIP_EVENT_DEFINITION,
1235 "speed_flip",
1236 "SpeedFlipNode",
1237 "SpeedFlipCalculator",
1238)];
1239
1240pub(crate) const HALF_FLIP_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1241 &HALF_FLIP_EVENT_DEFINITION,
1242 "half_flip",
1243 "HalfFlipNode",
1244 "HalfFlipCalculator",
1245)];
1246
1247pub(crate) const HALF_VOLLEY_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1248 &HALF_VOLLEY_EVENT_DEFINITION,
1249 "half_volley",
1250 "HalfVolleyNode",
1251 "HalfVolleyCalculator",
1252)];
1253
1254pub(crate) const WAVEDASH_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1255 &WAVEDASH_EVENT_DEFINITION,
1256 "wavedash",
1257 "WavedashNode",
1258 "WavedashCalculator",
1259)];
1260
1261pub(crate) const WHIFF_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1262 &WHIFF_EVENT_DEFINITION,
1263 "whiff",
1264 "WhiffNode",
1265 "WhiffCalculator",
1266)];
1267
1268pub(crate) const POWERSLIDE_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1269 &POWERSLIDE_EVENT_DEFINITION,
1270 "powerslide",
1271 "PowerslideNode",
1272 "PowerslideCalculator",
1273)];
1274
1275pub(crate) const TOUCH_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1276 &TOUCH_CLASSIFICATION_EVENT_DEFINITION,
1277 "touch",
1278 "TouchNode",
1279 "TouchCalculator",
1280)];
1281
1282pub(crate) const BOOST_EMITTED_EVENTS: &[EmittedEvent] = &[
1283 produced_event(
1284 &BOOST_PICKUP_EVENT_DEFINITION,
1285 "boost",
1286 "BoostNode",
1287 "BoostCalculator",
1288 ),
1289 produced_event(
1290 &BOOST_RESPAWN_EVENT_DEFINITION,
1291 "boost",
1292 "BoostNode",
1293 "BoostCalculator",
1294 ),
1295];
1296
1297pub(crate) const BUMP_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1298 &BUMP_EVENT_DEFINITION,
1299 "bump",
1300 "BumpNode",
1301 "BumpCalculator",
1302)];
1303
1304pub(crate) const POSSESSION_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1305 &POSSESSION_EVENT_DEFINITION,
1306 "possession",
1307 "PossessionNode",
1308 "PossessionCalculator",
1309)];
1310
1311pub(crate) const PLAYER_POSSESSION_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1312 &PLAYER_POSSESSION_EVENT_DEFINITION,
1313 "player_possession",
1314 "PlayerPossessionNode",
1315 "PlayerPossessionCalculator",
1316)];
1317
1318pub(crate) const LOOSE_POSSESSION_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1319 &LOOSE_POSSESSION_EVENT_DEFINITION,
1320 "loose_possession",
1321 "LoosePossessionNode",
1322 "LoosePossessionCalculator",
1323)];
1324
1325pub(crate) const BALL_HALF_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1326 &PRESSURE_EVENT_DEFINITION,
1327 "ball_half",
1328 "BallHalfNode",
1329 "BallHalfCalculator",
1330)];
1331
1332pub(crate) const BALL_THIRD_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1333 &BALL_THIRD_EVENT_DEFINITION,
1334 "ball_third",
1335 "BallThirdNode",
1336 "BallThirdCalculator",
1337)];
1338
1339pub(crate) const TERRITORIAL_BALL_HALF_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1340 &TERRITORIAL_PRESSURE_EVENT_DEFINITION,
1341 "territorial_pressure",
1342 "TerritorialPressureNode",
1343 "TerritorialPressureCalculator",
1344)];
1345
1346pub(crate) const MOVEMENT_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1347 &MOVEMENT_EVENT_DEFINITION,
1348 "movement",
1349 "MovementNode",
1350 "MovementCalculator",
1351)];
1352
1353pub(crate) const POSITIONING_EMITTED_EVENTS: &[EmittedEvent] = &[
1354 produced_event(
1355 &PLAYER_ACTIVITY_EVENT_DEFINITION,
1356 "positioning",
1357 "PositioningNode",
1358 "PositioningCalculator",
1359 ),
1360 produced_event(
1361 &FIELD_THIRD_EVENT_DEFINITION,
1362 "positioning",
1363 "PositioningNode",
1364 "PositioningCalculator",
1365 ),
1366 produced_event(
1367 &FIELD_HALF_EVENT_DEFINITION,
1368 "positioning",
1369 "PositioningNode",
1370 "PositioningCalculator",
1371 ),
1372 produced_event(
1373 &BALL_DEPTH_EVENT_DEFINITION,
1374 "positioning",
1375 "PositioningNode",
1376 "PositioningCalculator",
1377 ),
1378 produced_event(
1379 &DEPTH_ROLE_EVENT_DEFINITION,
1380 "positioning",
1381 "PositioningNode",
1382 "PositioningCalculator",
1383 ),
1384 produced_event(
1385 &BALL_PROXIMITY_EVENT_DEFINITION,
1386 "positioning",
1387 "PositioningNode",
1388 "PositioningCalculator",
1389 ),
1390];
1391
1392pub(crate) const ROTATION_EMITTED_EVENTS: &[EmittedEvent] = &[
1393 produced_event(
1394 &ROTATION_ROLE_EVENT_DEFINITION,
1395 "rotation",
1396 "RotationNode",
1397 "RotationCalculator",
1398 ),
1399 produced_event(
1400 &FIRST_MAN_CHANGE_EVENT_DEFINITION,
1401 "rotation",
1402 "RotationNode",
1403 "RotationCalculator",
1404 ),
1405];
1406
1407pub(crate) const STATS_TIMELINE_EVENTS_EMITTED_EVENTS: &[EmittedEvent] = &[produced_event(
1408 &TIMELINE_ENVELOPE_EVENT_DEFINITION,
1409 "stats_timeline_events",
1410 "StatsTimelineEventsNode",
1411 "StatsTimelineEventsState",
1412)];
1413
1414#[cfg(test)]
1415#[path = "event_definition_tests.rs"]
1416mod tests;