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
//! The check abstraction, its execution context, and the built-in
//! check sets.
use crate::config::{ClipExpectations, Config};
use crate::evaluation::{Applicability, CheckOutput};
use crate::metrics::MetricGrids;
use crate::model::Document;
use crate::prediction::{PredictionFacetDemandV2, PredictionRuleAllocationV2};
use crate::profile::ResolvedRoles;
use crate::sample::PoseGrid;
use std::rc::Rc;
/// Everything a check may consume: the document, the resolved rig
/// roles, the configuration, and shared metric [`PoseGrid`] samples.
#[derive(Debug)]
pub struct CheckCtx<'a> {
/// Document being checked.
pub doc: &'a Document,
/// Resolved rig roles for semantic checks.
pub roles: &'a ResolvedRoles,
/// Effective configuration for this run.
pub config: &'a Config,
grids: &'a MetricGrids<'a>,
/// Effective per-clip expectations, resolved once and aligned to
/// `doc.clips`. Resolving them means overlaying every matching glob
/// entry (see [`Config::expectations_for`]); caching here keeps that
/// off the per-check hot loop, which otherwise re-resolved the same
/// clip once per check that reads expectations.
expectations: Vec<ClipExpectations>,
}
impl<'a> CheckCtx<'a> {
/// Build a check context that shares metric pose grids with
/// measurement or report generation.
///
/// `roles` must already reflect any [`Config::rig`](crate::Config::rig)
/// profile and inline overrides; constructing a context does not resolve
/// that declarative configuration.
pub fn new(grids: &'a MetricGrids<'a>, roles: &'a ResolvedRoles, config: &'a Config) -> Self {
let doc = grids.document();
let expectations = doc
.clips
.iter()
.map(|c| config.expectations_for(&c.name))
.collect();
Self {
doc,
roles,
config,
grids,
expectations,
}
}
/// The metric pose grid for clip `clip_index`, computed once and
/// shared. `None` for clips too short to carry a cycle.
pub fn grid(&self, clip_index: usize) -> Option<Rc<PoseGrid>> {
self.grids.grid(clip_index)
}
/// Effective expectations for clip `clip_index` (resolved once in
/// [`CheckCtx::new`]). Index into `doc.clips`.
///
/// # Panics
///
/// Panics if `clip_index` is outside the document's clip range.
pub fn expectations(&self, clip_index: usize) -> &ClipExpectations {
&self.expectations[clip_index]
}
/// Per-clip expectations in `doc.clips` order, used by cheap
/// applicability predicates that scan for declared work.
pub fn clip_expectations(&self) -> &[ClipExpectations] {
&self.expectations
}
}
/// A lint check that can inspect a document and emit typed evaluation
/// coverage plus structured content findings.
///
/// Custom embedders may implement this trait and pass their checks to
/// [`crate::evaluate_checks`] alongside, or instead of, [`all_checks`].
/// Implementors should keep both methods panic-free for loader-valid
/// documents. Applicability describes whether declared work exists;
/// unavailable prerequisites or measurements belong in typed coverage gaps
/// returned from [`Check::evaluate`]. Custom checks should use namespaced
/// scope and gap codes. Built-in code values are reserved to the checks named
/// by animsmith's evidence-code authority, and the evaluation boundary rejects
/// a built-in value emitted by any other check id.
pub trait Check {
/// Stable identifier, e.g. `"loop-seam"`. Used in config, JSON
/// output, and `--select`.
fn id(&self) -> &'static str;
/// Whether the check runs when its configuration has no explicit
/// `severity` setting.
///
/// Most checks are enabled by default. Checks for intentionally opt-in
/// policy signals may return `false`; setting their severity to `note`,
/// `warn`, or `error` enables them, while `off` keeps them disabled.
fn enabled_by_default(&self) -> bool {
true
}
/// Whether an explicitly selected and applicable check may be disabled
/// with `severity = "off"`.
///
/// Most checks may be disabled by configuration. Checks whose evidence is
/// required whenever their declared work applies can opt out by returning
/// `false`; the evaluation runners then return a typed error instead of
/// silently dropping the check.
fn allows_severity_off(&self) -> bool {
true
}
/// Whether this document and configuration declare work for the check.
///
/// The runner calls this cheap predicate even for disabled or unselected
/// checks so applicability remains an independent result dimension. It
/// must not perform the check's substantive evaluation.
fn applicability(&self, _ctx: &CheckCtx) -> Applicability {
Applicability::Applicable
}
/// Evaluate every modelled work unit, returning content findings and
/// explicit coverage. Missing prerequisites are gaps, never findings.
fn evaluate(&self, ctx: &CheckCtx) -> CheckOutput;
/// Bounded V2 facet demand, collected before current-output evaluation.
fn prediction_facet_demand_v2(&self, _ctx: &CheckCtx) -> PredictionFacetDemandV2 {
PredictionFacetDemandV2::Exact(0)
}
/// Evaluate after the catalog allocator has reserved this check's slots.
fn evaluate_with_prediction_allocation_v2(
&self,
ctx: &CheckCtx,
_allocation: PredictionRuleAllocationV2<'_>,
) -> CheckOutput {
self.evaluate(ctx)
}
}
/// The mechanical P0 catalog: no rig roles or clip expectations required.
/// Individual policy signals may still be disabled by default and enabled by
/// an explicit severity.
pub fn mechanical_checks() -> Vec<Box<dyn Check>> {
vec![
Box::new(crate::checks::nan::Nan),
Box::new(crate::checks::time_monotonic::TimeMonotonic),
Box::new(crate::checks::quat_norm::QuatNorm),
Box::new(crate::checks::quat_flip::QuatFlip),
Box::new(crate::checks::duration_sanity::DurationSanity),
Box::new(crate::checks::scale_keys::ScaleKeys),
Box::new(crate::checks::non_uniform_scale::NonUniformScale),
Box::new(crate::checks::constant_nonunit_scale::ConstantNonunitScale),
Box::new(crate::checks::constant_track::ConstantTrack),
]
}
/// The full built-in catalog: mechanical + semantic checks.
pub fn all_checks() -> Vec<Box<dyn Check>> {
let mut checks = mechanical_checks();
checks.push(Box::new(crate::checks::required_bones::RequiredBones));
checks.push(Box::new(crate::checks::rest_world_scale::RestWorldScale));
checks.push(Box::new(crate::checks::missing_bones::MissingBones));
checks.push(Box::new(crate::checks::frozen_bone::FrozenBone));
checks.push(Box::new(
crate::checks::duplicate_loop_endpoint::DuplicateLoopEndpoint,
));
checks.push(Box::new(crate::checks::loop_closure::LoopClosure));
checks.push(Box::new(crate::checks::loop_seam::LoopSeam));
checks.push(Box::new(crate::checks::loop_seam_vel::LoopSeamVelocity));
checks.push(Box::new(crate::checks::loop_seam_rot::LoopSeamRotation));
checks.push(Box::new(crate::checks::root_motion_speed::RootMotionSpeed));
checks.push(Box::new(crate::checks::gait_group::GaitGroup));
checks.push(Box::new(crate::checks::sync_group::SyncGroupCheck));
checks.push(Box::new(crate::checks::time_complement::TimeComplement));
checks.push(Box::new(crate::checks::in_place::InPlace));
checks.push(Box::new(crate::checks::fps::Fps));
checks.push(Box::new(crate::checks::bind_pose::BindPose));
checks.push(Box::new(crate::checks::foot_slide::FootSlide));
checks
}