Skip to main content

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 ValidationReport host-runtime type shape (the aggregate result of running the registered validation predicates).
  • The ValidationViolation host-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:
  • World-import I/O (file paths, JSON parsing, deserialization). The existing ValenarWorldValidator at legacy/v1/examples/valenar/WorldData/ValenarWorldValidator.cs is 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 when Violations.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), ContextId is 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 Predicate field of ValidationViolation).
  • 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 ValidationRegistry that the ValidationReport runner 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:

  1. World-import boundary. When the host loads a world snapshot (existing WorldData deserialization, or future map-pack import), the world predicates run before any gameplay system reads the world data.
  2. 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 (ValidationFailedException or equivalent) whose message includes the report's Summary and the first violation's Predicate + 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.cs exists at lines 105 and 227 as the world-import validator seed surface. The current shape is ad-hoc (one-off helper methods like ValidateColor, ValidatePoint, ValidatePolygon, ValidateFlag, ValidateFacts) rather than contract-driven. The next closure wave refactors this surface around the ValidationReport and ValidationViolation types, and registers the predicates from the three generation contract docs into the ValidationRegistry.
  • 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 ValidationRegistry runtime type does not yet exist. The 14 feature-validation predicates are runtime-unenforced today — only the world-import predicates run. The contract-driven refactor of ValenarWorldValidator has not yet been authored. The ValidationFailedException type does not yet exist.
  • Current backing today: ValenarWorldValidator runs at world-import time and accumulates an errors list which is consumed by the host load path. The shape is ad-hoc; predicates are inline helper method calls rather than registered named predicates. No ValidationReport return 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 ValidationViolation with a synthetic "predicate- errored" row identifying the throwing predicate. A world load that proceeds despite IsValid == false is a tenet failure, NOT a recoverable degradation.
  • Next closure wave: a future runtime-validation wave authors the ValidationReport, ValidationViolation, and ValidationRegistry types; refactors ValenarWorldValidator to 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.lua load) 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.ValidateAndPlaceResources boundary 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 around ValidationReport.
  • .claude/rules/valenar-contract-backing.md gate item 4 — the no-silent-fallback rule this primitive enforces.