Generation Validation and Reports
This page owns Valenar's generation-validation primitive: the host-runtime
ValidationReport type that aggregates predicate violations from feature,
territory, and world generation contracts, and the fail-loud-on-violation
contract that runs ValidationReport against world-import and post-
generation boundaries. It is the cross-cutting owner-doc for validation
results; the predicates themselves stay owned by the individual generation
contracts.
Scope
This doc owns:
- The
ValidationReporthost-runtime type shape (the aggregate result of running the registered validation predicates). - The
ValidationViolationhost-runtime type shape (one row per failing predicate). - The predicate-registration contract by which generation contracts publish their validation predicates into the report.
- The fail-loud-on-violation contract that runs validation at world-import and post-generation boundaries and aborts on any violation.
This doc does NOT own:
- The individual validation predicates themselves. Those stay owned by:
- gd-feature-generation-contract.md "Validation" section (14 feature-validation predicates).
- gd-territory-generation-contract.md Validation section (Territory record validation, neighbor topology, facts validity).
- gd-world-generation-contract.md Validation section (top-level world-data import boundary).
- World-import I/O (file paths, JSON parsing, deserialization). The
existing
ValenarWorldValidatoratlegacy/v1/examples/valenar/WorldData/ValenarWorldValidator.csis the seed surface; this doc commits the contract-driven replacement. - Per-feature, per-territory, or per-route runtime gameplay validation (those are gameplay invariants, not generation invariants).
ValidationReport Type Shape
ValidationReport is a host-runtime C# type (NOT a SECS scope, NOT a
SECS contract — validation is a startup/boundary integrity check, not
gameplay state). The shape committed in this doc is:
public sealed record ValidationReport(
IReadOnlyList<ValidationViolation> Violations,
string Summary)
{
public bool IsValid => Violations.Count == 0;
}
Violations— every predicate failure recorded during the validation run. An empty list means the report is valid.Summary— a one-paragraph human-readable summary intended for log output and developer surfaces. The summary describes the run scope (world-import vs. post-generation), the predicate count run, and the violation count.IsValid— derived; true whenViolations.Count == 0.
The type is immutable. Validation predicates accumulate ValidationViolation
rows into a builder/list which is sealed into the report once the run
completes; the report itself is then read-only.
The choice to keep ValidationReport as a host-runtime type (rather than
as a SECS scope) is committed by resolver R16 and aligns with the
following structural distinction: SECS scopes are for runtime gameplay
state walked through the 6-phase channel pipeline and read by gameplay
systems on per-tick boundaries. ValidationReport runs at startup and at
end-of-generation boundaries, does not participate in per-tick
resolution, does not need cross-scope walks, and does not produce
gameplay-visible state. Host ownership is the canonical placement.
ValidationViolation Type Shape
ValidationViolation represents one failing predicate against one
specific context (a Territory, a Feature, a Route, the world overall).
The shape committed in this doc is:
public sealed record ValidationViolation(
string Predicate,
string ContextId,
string Detail);
Predicate— the registered predicate name that failed (e.g.,"feature-has-valid-territory","starting-nexus-feature- exists","territory-references-valid-province").ContextId— the specific entity id the predicate ran against (Territory cell id, Feature id, Route id, or"world"for top-level predicates). For predicates that compare across all rows (e.g., the starting-Nexus existence predicate),ContextIdis the singleton context the predicate runs against, conventionally"world".Detail— a short human-readable description of the specific failure (e.g.,"feature 47 references missing territory 192").
A ValidationViolation is one row per failure. A single run may
produce many violations against the same predicate (e.g., 12 features
all referencing missing territories produce 12 violations of the
"feature-has-valid-territory" predicate, each with a distinct
ContextId).
Predicate Registration Contract
Each generation contract doc's "Validation" section is a registered predicate list. The contract is:
- Each predicate registered with a stable name (the
Predicatefield ofValidationViolation). - Each predicate is a
Func<WorldData, IEnumerable<ValidationViolation>>or equivalent host-runtime signature that reads world state and emits zero-or-more violations. - Predicates are registered at host startup in a
ValidationRegistrythat theValidationReportrunner walks.
The currently-authored predicate sets:
Feature predicates (from gd-feature-generation-contract.md "Validation" section — 14 predicates):
every Feature has a valid Territory
every Feature template id exists
every selected Feature passes hard requirements
Feature count does not exceed Territory capacity
rare Features respect spacing
family and regional budgets hold
incompatible Features do not coexist
synergy placements are deterministic and bounded
story-required Features exist
starting Nexus Feature exists
no visible Feature is Hidden
no activity references a missing Feature, Site, or Territory
all modifiers target valid scopes
all rewards are causal to Feature identity
Territory predicates (from gd-territory-generation-contract.md Validation section): Territory record integrity (color, centroid, polygon, is_water, is_coastal, biome, facts, neighbor cell ids).
World predicates (from gd-world-generation-contract.md Validation section): top-level world-data import integrity.
Each predicate's registration includes the source doc the predicate is authored against, so a violation can be traced back to its specification at a stable doc location.
Fail-Loud-on-Violation Contract
ValidationReport runs at two boundaries:
- World-import boundary. When the host loads a world snapshot
(existing
WorldDatadeserialization, or future map-pack import), the world predicates run before any gameplay system reads the world data. - Post-generation boundary. After Feature placement, Territory facts derivation, and any other generation step completes, the feature and territory predicates run before the post-generation gameplay state is made visible to the host runtime.
If ValidationReport.IsValid is false at either boundary, the host
must abort the world load with an explicit error. The committed
behavior is:
- Log the full violation list to the host's error surface.
- Throw a host-runtime exception (
ValidationFailedExceptionor equivalent) whose message includes the report'sSummaryand the first violation'sPredicate+Detail. - Do NOT continue gameplay execution. Do NOT silently degrade by trimming the offending Features / Territories / Routes. Do NOT emit a warning and proceed.
The fail-loud contract is the central anti-fallback rule of this
primitive. Per .claude/rules/valenar-contract-backing.md gate item 4,
silent skip of failed predicates is the canonical violation. The
report's IsValid is binary; a partially-valid world is not a
permitted state.
Runtime Backing Status
Status at this doc's acceptance time: contract-only.
- Host/runtime owner:
legacy/v1/examples/valenar/WorldData/ValenarWorldValidator.csexists at lines 105 and 227 as the world-import validator seed surface. The current shape is ad-hoc (one-off helper methods likeValidateColor,ValidatePoint,ValidatePolygon,ValidateFlag,ValidateFacts) rather than contract-driven. The next closure wave refactors this surface around theValidationReportandValidationViolationtypes, and registers the predicates from the three generation contract docs into theValidationRegistry. - Generated/.secs owner: not applicable. ValidationReport is a host- runtime type, not a SECS scope or contract.
- Read-model/UI owner: developer/log surface only. A failed world load surfaces to the host log; the React client does not consume ValidationReport directly.
- Tests: covered today by the existing world-import validator tests
exercising
ValenarWorldValidator. The contract-driven refactor in the closure wave extends those tests to cover predicate-by- predicate violations and the fail-loud-on-violation contract. - Known gaps: the
ValidationRegistryruntime type does not yet exist. The 14 feature-validation predicates are runtime-unenforced today — only the world-import predicates run. The contract-driven refactor ofValenarWorldValidatorhas not yet been authored. TheValidationFailedExceptiontype does not yet exist. - Current backing today:
ValenarWorldValidatorruns at world-import time and accumulates anerrorslist which is consumed by the host load path. The shape is ad-hoc; predicates are inline helper method calls rather than registered named predicates. NoValidationReportreturn shape exists; no Feature-side predicates run. - Illegal fallback behavior: silent skip of a failed predicate is
forbidden. A predicate that throws during execution must not be
silently swallowed — the runtime must surface the predicate-internal
failure as a
ValidationViolationwith a synthetic"predicate- errored"row identifying the throwing predicate. A world load that proceeds despiteIsValid == falseis a tenet failure, NOT a recoverable degradation. - Next closure wave: a future runtime-validation wave authors the
ValidationReport,ValidationViolation, andValidationRegistrytypes; refactorsValenarWorldValidatorto register the world predicates into the registry; adds the Feature-validation predicates to the registry; and wires the registry into the world-import and post-generation boundaries.
Glossary Propagation
The following terms MUST land in gd-glossary.md, gd-canon.md, and
README.md:
ValidationReport(host-runtime aggregate result type).ValidationViolation(host-runtime per-failure row type).ValidationPredicate(registered named predicate; the unit of validation registration).ValidationRegistry(host-runtime predicate registration surface).
AAA Precedent
The fail-loud validation primitive draws on three AAA references:
- Dwarf Fortress worldgen rejection passes (primary) — Dwarf Fortress's worldgen runs named invariant checks at each gen-pass boundary and rejects worlds that fail (the iconic "rejected, regenerating" surface). DF's worldgen rejection is the canonical AAA realization of "named predicates that fail world-load on violation rather than silently degrading" — when a worldgen pass produces a world that violates invariants (no caves, no kobold sites, broken river network), the worldgen aborts the world and reruns with a fresh seed rather than shipping the broken world. Valenar's fail-loud contract matches the rejection-rather-than- degradation discipline; the difference is that Valenar runs at import / post-generation boundaries on an authored world rather than during procedural worldgen.
- Factorio data-stage error reporting (secondary) — Factorio's
data-stage (
data.luaload) aborts the game launch with an explicit stack trace when a recipe references a non-existent item, when a prototype declaration is malformed, or when any data-stage invariant fails. Factorio's data-stage error matches the Valenar fail-loud contract: the game does not start with partial / invalid data, and the developer/player sees the exact failing predicate. - Civ VI Map.ValidateAndPlaceResources (tertiary) — Civ VI's
map-script API exposes a
Map.ValidateAndPlaceResourcesboundary that the script must satisfy before the world transitions to gameplay. Resource placement that fails validation aborts the map gen with an explicit error rather than producing an incomplete map.
The pattern is universal across shipped AAA generation pipelines:
fail loud at the boundary, surface the exact failing predicate,
never silently degrade. Valenar's ValidationReport is the SECS
expression of that pattern.
Cross-References
- gd-feature-generation-contract.md
— Feature-validation predicates (14 predicates registered into
ValidationRegistry). - gd-territory-generation-contract.md — Territory-validation predicates.
- gd-world-generation-contract.md — top-level world-data import predicates.
legacy/v1/examples/valenar/WorldData/ValenarWorldValidator.cs— the seed surface that the closure wave refactors aroundValidationReport..claude/rules/valenar-contract-backing.mdgate item 4 — the no-silent-fallback rule this primitive enforces.