Validation
How the SDK checks a level against the format's rules, repairs what can be repaired and reports what cannot
You call validation yourself. It is not built into saving or loading
The game decides where to run it. Opening a level in the editor reports problems, and playback skips the check. So an author waits for validation, a player never does
A level author meets validation when offering a level to a service. More - Publish profiles
How to call it
ValidationFacade is the one entry point. A caller does not need to know how many passes there are:
var facade = new ValidationFacade();
var report = facade.Validate(level);
if (report.HasErrors)
Console.WriteLine(report);
| Method | What it does |
|---|---|
Validate(root, settings) | checks any root: Level, LevelMeta, UserSettings, a standalone Prefab or EffectData. The graph pass runs for a Level only |
ValidateAndFix(root, analyzerSettings, fixerSettings) | repairs what can be repaired, then reports what is left |
ValidateForPublish(meta, profile, level, now, payload, settings) | all three passes at once, repairs nothing. level may be null to grade the metadata alone |
ValidationReport holds RuleIssues and GraphIssues:
IsValid- no findings at allHasErrors- at least one finding of theErrorgroup
Severity
Every rule belongs to a RuleGroup:
Error- the file cannot be played as writtenWarning- it plays, but not as authored (content that never appears, a broken reference taking a fallback). Or the only repair is destructiveAdvice- playback does not change
Three passes
| Pass | What it asks | Runs on |
|---|---|---|
RuleAnalyzer | is each value in its range, not null, a known enum member | any root |
LevelGraphAnalyzer | do the objects agree with each other | Level only |
PublishReadinessAnalyzer | may the level be handed to strangers under a given profile | LevelMeta, optionally Level |
Rules
A type joins the check with the [RuleContainer] marker. Its properties carry rule attributes:
[RuleNotNull],[RuleInRange],[RuleMinValue],[RuleEnumValid],[RuleCollectionMaxCount]and others[RuleEnumFlagsValid]- for flag enums[RuleOptional]- for a member that may be absent
The limits themselves are constants in the Rules/ tables: FrameRules, ValueRules, LevelRules and others. For example, LevelRules.MaxObjects is 262 144
The walk over all rule containers is generated by BH.SDK.Roslyn, with no reflection. That roughly halves its time on large levels
Fixers
Every rule attribute knows how to repair its own value (Fix).
RuleFixer applies repairs in reverse order of the trace: a repair can shift deeper findings. ValidateAndFix repeats the pass until the result stops changing
The graph pass runs after the repairs. A repair can itself create a graph problem, for example a new id colliding with an existing one
Graph findings are never repaired: DuplicateObjectId, MissingParent, ParentCycle, PrefabRemapBroken, IdCounterNearExhaustion and the rest. Each repair of that kind is a content decision (which of two objects keeps its id). A guess would rewrite the level
ValidateAndFix changes the object you pass in. Run it on a copy when you need the original, for example to show the author what exactly was changed