Valenar - Feature Generation Contract
This document is the authoritative contract for generating, revealing, and using Features inside Valenar Territories.
Status: authoritative contract with a current runtime implementation for the
prototype map. Valenar derives deterministic TerritoryFacts, evaluates
Feature.EvaluatePlacement(Territory, FeaturePlacementInput) through generated
typed profile/query data, applies budgets/spacing/conflicts, and spawns selected
Feature entities through generated templates. Future compiler work is to emit
the current hand-written Generated/ shape directly from .secs.
The runtime implementation evaluates the multiplicative `score = hardGate
- suitability * rarityModifier * seededNoise * spacingPenalty * storyBias
formula authored in this doc. Static factors (hardGate, suitability, seededNoise) are computed byFeaturePlacementQuery.Evaluate; dynamic factors (rarityModifier =clamp(1 - used/budget, 0, 1), spacingPenalty =clamp(distance / minSpacing, 0, 1)) are applied byFeaturePlacementSelectorat selection time.FeatureFamilycovers all 14 families listed below; seven of those families (Water, Food, Forest, Travel, Shelter, Corruption, Nexus) have no committed templates yet.AvoidanceWeightswas folded intoSuitabilityWeightsas negated entries; the avoidance dictionary is no longer a separate profile field.MinScorethresholds and per-template budget values remain open for a future balance-pass wave (see [../implementation/pr-code-sync-audit.md` "Feature placement balance pass"](/secs/valenar/implementation/pr-code-sync-audit/)).
storyBias is deferred. The current runtime implementation must
throw an explicit `NotImplementedException("StoryBias requires labor
signals (gd-labor-and-capacity.md), pressure signals (gd-corruption-
reclamation.md), ActState (ad-0010), and Quest Thread lane state
(ad-0007) — deferred to Wave 5a (parallel with ad-0008 labor + pressure
- storyBias formula authoring).")
against the placement formula. The historical constant-1.0 stub is the canonical silent-fallback violation per.claude/rules/valenar-contract-backing.md` gate item 4 and is to be removed in Wave 5a — see "storyBias Runtime Contract" below for the closure spec.
Scope
This contract covers:
- Feature families.
- Feature record shape.
- Existence versus discovery.
- Placement formulas.
- Density, rarity, spacing, and budgets.
- Compatibility and exclusion rules.
- Starting Nexus exception.
- Gates, taint, and threat-linked Features.
- Activity generation.
- Causal rewards.
- Site conversion.
- UX query contract.
- Validation.
It does not define:
- final numeric balance
- individual story scripts
- MC combat resolution
- final art or icon treatment
- district development internals after a Feature becomes a developed Site
Core Rule
Feature existence is generated world state. Discovery state controls what the player sees.
A Territory can contain several Features, but most Territories should contain no explicit notable Feature. Do not model a Territory as "one thing". The Territory is the land unit; Features are notable contents, signs, or facts inside it.
Sites are first-class places inside the Territory once content becomes discovered, enterable, exploitable, or developed. Threat is danger, pressure, or hostile presence attached to a Territory, Feature, or Site; it is not a second place ontology.
Territory facts
-> Feature template eligibility
-> suitability score
-> regional budget
-> spacing and conflict resolution
-> Feature entity
-> discovery state
-> activities and effects
Inputs
Feature generation consumes:
worldSeed
worldDataContentHash
Territory records
derived Territory classes and tags
pressure fields
route and topology facts
settlement/start constraints
Feature template registry
regional density budgets
story placement rules
The generator must use real Territory facts. It must not place Features by independent flat chance per Territory.
Outputs
Feature generation outputs Feature records:
Feature
featureId
templateId
territoryId
category
placementReasonTags
generatedStrength
quality
discoveryState
handledState
progress
risk
blockers
available interactions
effectIds
siteConversionState
Names and labels can vary by discovery state:
Hidden:
no label
Hinted:
damp reeds
Discovered:
spring
Surveyed:
clean spring, reliable water
Developed:
secured spring site
Feature Families
Feature families and the surrounding category vocabulary are NOT tags
in the valenar:tag/<name> sense. The tag layer and its
modifier-vs-structural-predicate split, including how tags relate to
Feature categories, are documented in
../systems/gd-tags-and-classification.md;
the committed tag list lives in
../catalogs/gd-tag-catalog.md.
These are generation families for Feature records, not separate player-facing place ontologies. A hostile or pressure-bearing record can stay a Feature sign, or later feed a Threat Site or a Site with linked threats.
Feature families:
NaturalResource
Water
Food
Forest
Mineral
Travel
Shelter
Ruin
Threat
Corruption
Landmark
Nexus
SettlementOpportunity
Story
Examples:
NaturalResource:
WildGrainPatch, HerbMeadow, GrazingGround
Water:
HiddenSpring, SeasonalStream, ShallowPond, ReedPool
Forest:
ForestStand, OldGrowthGrove, GameTrail, HiddenCopse
Mineral:
OreVein, QuarryFace, ClayBank, CollapsedMine
Travel:
RoadTrace, Ford, OldBridge, CaravanCamp, MountainPass
Ruin:
RuinedMill, StandingStones, FallenWatchtower, ArchiveRuin
Threat:
BanditCamp, AmbushSite, PredatorTrail, DemonNest
Corruption:
BlackAshPatch, BlightedField, MinorFissure, TaintedRunoff
Nexus:
DormantNexus, DamagedWardheart, LeylineAnchor
Corruption family lore link: (See lore exposure: lh-game1-world-hooks.md — Gate scarring/taint fields row)
Nexus family lore link: (See lore exposure: lh-game1-world-hooks.md — Nexus manifestation row, Damaged Nexus row)
Feature Template Shape
Every Feature template should define:
id
family
rarity
minSpacing
maxPerTerritory
maxPerProvince
maxPerArea
maxPerRegion
hardRequirements
suitabilityWeights
conflictTags
synergyTags
discoveryDifficulty
surveyDifficulty
interactionDifficulty
defaultDiscoveryState
possibleStates
available interactions
effects
modifiers
rewards
siteConversion
Template data answers:
- where the Feature can exist
- how rare it is
- what it conflicts with
- what it synergizes with
- how it is discovered
- which activities it creates
- which scopes it can affect
- whether it can become a Site
Existence Versus Discovery
The full ladder of state labels — FeatureDiscoveryState enum values
plus their target intensity ranges — is documented in
../systems/gd-state-axes-and-thresholds.md
under "Feature Discovery Axis". This section introduces the ladder
informally; the state-axes doc is the normative reference for label
ordering and intensity thresholds.
Existence is not knowledge.
exists:
the Feature entity is present in world state
hinted:
the player has a clue, but not the real identity
discovered:
the player knows the Feature category or name
surveyed:
quality, risk, blockers, and main outputs are known
handled:
threat or obstacle is resolved, but site may not be developed
developed:
owned or used as an ongoing Site/improvement
A dungeon, ruin interior, or resource node can begin in this ladder as a clue, Feature, or threat sign. Once it becomes enterable, exploitable, or developed, it should convert to a Site entry.
The Territory UI must not show nonexistent Features. It must not show hidden Features as known rows. It may show clues when discovery state permits.
Placement Formula
Use score-and-budget placement:
score(territory, template) =
hardGate(territory, template)
* suitability(territory, template)
* rarityModifier(template)
* seededNoise(worldSeed, territoryId, templateId)
* spacingPenalty(territory, template)
* storyBias(territory, template)
Where:
hardGate:
0 or 1. Required conditions must pass.
suitability:
weighted match between Territory facts and template preferences.
rarityModifier:
global, regional, family, and template budget pressure.
seededNoise:
deterministic variation without losing coherence.
spacingPenalty:
prevents unnatural clusters.
storyBias:
DEFERRED. The closed formula reads labor signals
(gd-labor-and-capacity.md), pressure signals
(gd-corruption-reclamation.md), ActState (ad-0010), Quest Thread
lane state (ad-0007), and starting-Nexus distance. The current
runtime must throw NotImplementedException against the placement
formula until Wave 5a (parallel with ad-0008 labor + pressure +
storyBias formula authoring) commits the inputs and the formula
body. See "storyBias Runtime Contract" below.
storyBias Runtime Contract
storyBias(territory, template) is the multiplicative bias factor in
the placement formula that encourages or suppresses Feature placement
based on narrative state. The closed contract is:
Committed inputs. The future storyBias formula reads from five input families:
- Labor signals from
../systems/gd-labor-and-capacity.md:
LaborShortageSignal,CriticalShortageSignal, and Province-levelProvinceLaborShortage. A Province raising sustained shortage may suppress placement of high-throughput Features (rich ore veins, complex craft sites) whose discovery would compound the labor gap, and may encourage placement of relief-oriented Features (refugee groves, abandoned-camp salvage sites). - Pressure signals from
../systems/gd-corruption-reclamation.md:
Province-level
ProvinceThreatPressure,ProvinceWardIntegrity,ProvinceRouteSafety. High pressure regions bias toward threat- linked Features (gate scarring, taint clusters) and away from pristine-landscape Features. - ActState (the current Act) per ../lore/adr/ad-0010-act-progression-actstate-scope.md. Act 0 / Act 1 placement biases toward starting-survival resources near the starting Nexus; Act 4+ placement opens up to Banner- Network-scale narrative geography (ancient roads, leyline anchors).
- Quest Thread lane state per ../lore/adr/ad-0007-quest-thread-and-mission-scope-primitives.md. Active Quest Threads bias placement toward Features that surface thread-aligned hooks (a thread on Crownrot favors gate-scarring Features; a thread on ward-line restoration favors ward-anchor sites).
- Starting-Nexus distance. A boost factor for Features anchored at or near the starting Nexus (initial Wardheart, first-Outpost candidate Territories, leyline-anchor sites required for early- game survival).
Output range. The storyBias output is a multiplicative factor in
[0, k] where k = Config.Generation.StoryBiasMaxFactor is a
world-size-parameterized configuration field (Discipline 14 — numeric
thresholds become UI-exposed Config fields with world-size-
parameterized defaults, not hardcoded constants). A storyBias of 0
suppresses placement entirely (the formula collapses to zero); a
storyBias of 1 leaves the score unchanged; values above 1 amplify
narrative-favored placement up to the configured ceiling.
Current behavior. The closed runtime is throw new NotImplementedException("StoryBias requires labor signals (gd-labor-and-capacity.md), pressure signals (gd-corruption-reclamation.md), ActState (ad-0010), and Quest Thread lane state (ad-0007) — deferred to Wave 5a (parallel with ad-0008 labor + pressure + storyBias formula authoring)."). The historical
=> 1.0 constant in
legacy/v1/examples/valenar/Generated/Territories/FeaturePlacement/FeaturePlacementQuery.cs
line 190 is the canonical silent-fallback violation per
.claude/rules/valenar-contract-backing.md gate item 4 and is
replaced in Wave 5a.
Next closure wave. Wave 5a (parallel with ad-0008 labor + pressure runtime backing per resolver R17). The wave authors the five input families on their owner docs simultaneously (P1 labor channels, P2 pressure channels, ad-0010 ActState scope binding, ad-0007 Quest Thread lane state) and lands the storyBias formula body that consumes them. Until Wave 5a completes, any placement run that requires storyBias must fail loud rather than silently default to 1.0.
Runtime Backing Status
Status at this section's acceptance time: deferred (closure plan committed; runtime backing pending).
- Host/runtime owner:
legacy/v1/examples/valenar/Generated/Territories/FeaturePlacement/FeaturePlacementQuery.csline 190 (ComputeStoryBias) is the closure site; the formula replaces the constant return with reads against the five input families.Config.Generation.StoryBiasMaxFactoris the new UI- exposed config field authored alongside. - Generated/.secs owner: storyBias has no committed source-keyword
shape; the formula lives as a C# method on
FeaturePlacementQuery. The labor and pressure channels storyBias reads are authored under the P1 and P2 owner docs' Wave 5a closure. - Read-model/UI owner: not applicable directly; storyBias surfaces only through Feature placement outcomes.
- Tests: deferred to Wave 5a alongside the formula body authoring.
- Known gaps: the formula body (how the five input families weight against each other); the per-feature-family bias coefficients; the StoryBiasMaxFactor world-size defaults table.
- Illegal fallback behavior: returning a constant value, returning
1.0, returning a placeholder, or silently coercing missing inputs
to neutral defaults is forbidden. Any storyBias evaluation that
cannot read its committed inputs must throw
NotImplementedExceptionwith the canonical message above. The constant-1.0 stub is the canonical silent-fallback the closure removes. - Next closure wave: Wave 5a (parallel with ad-0008 labor + pressure
- storyBias formula authoring per resolver R17).
AAA Precedent for storyBias
The storyBias primitive draws on three AAA references:
- RimWorld Storyteller (primary) — Cassandra Classic, Phoebe Chillax, and Randy Random read colony state and bias the event selector toward story-shaped challenges. RimWorld's Storyteller is the closest realization of "named bias term reading world state to shape placement / event flow" — the term story bias in the formula here is the same semantic noun as RimWorld's Storyteller output multiplier.
- Civ VI map-script bias tables (secondary) — Civ VI's
worldgen scripts carry per-resource and per-feature
biastables read at placement time. Thebiasrow matches the SECS storyBias multiplicative factor: a positive bias amplifies placement, a negative bias suppresses it, and the bias is read from world state (continent type, latitude, adjacent features). - CK3 Legend system (tertiary) — Crusader Kings 3's Legend feature biases narrative event chains by reading character / dynasty / region state. CK3 Legends are the closest realization of "narrative-state-driven bias on emergent content" outside the worldgen-placement context.
TerritoryFacts and IsWater
The TerritoryFacts consumed by FeaturePlacementQuery.Evaluate
include the IsWater field, wired through the host data layer:
- Host-side declaration at
legacy/v1/examples/valenar/Host/Data/TerritoryData.csline 16 (public int IsWater { get; set; }). - HostBridge read/write/snapshot paths at
legacy/v1/examples/valenar/Host/Bridge/HostBridge.cslines 459, 774, and 1237. - Generated hash entry at
legacy/v1/examples/valenar/Generated/Hashes.csline 41 (H.IsWater). FeaturePlacementTypes.cslines 109-112 declare the R-4 wiring that flows IsWater into the placement profile.
IsWater reports 1 for water Territories and 0 for land
Territories. It is consumed by the hard-gate suitability layer: water
Features (springs, lake-anchored sites) require IsWater = 1 for
hardGate to evaluate to 1; land Features require IsWater = 0. The
field is implemented; no closure wave is needed.
Runtime Backing Status (IsWater)
Status at this section's acceptance time: implemented.
- Host/runtime owner:
legacy/v1/examples/valenar/Host/Data/TerritoryData.csline 16. - Generated/.secs owner:
legacy/v1/examples/valenar/Generated/Hashes.csline 41 carriesH.IsWater; the.secssource authoring side lives underlegacy/v1/examples/valenar/Content/territories/. - Read-model/UI owner: the per-Territory dossier and Feature
placement UI both read
IsWater. - Tests: covered by existing world-import validation tests in
ValenarWorldValidator. - Known gaps: none.
- Illegal fallback behavior: no inference of
IsWaterfrom biome string or polygon adjacency; the host field is the source of truth. - Next closure wave: not applicable; field is shipped.
Candidate selection:
for each region:
for each Feature family:
apply family density budget
for each template in family:
collect eligible candidates
score candidates
sort by score descending
select candidates while budget remains
enforce min spacing
enforce max Features per Territory
enforce conflict rules
record placement reason tags
Do not do this:
for each Territory:
roll 5 percent chance for hidden dungeon clue
roll 5 percent chance for ore
roll 5 percent chance for spring
Independent rolls create incoherent worlds at large scale.
Density And Capacity
Most Territories have zero explicit Features.
Suggested capacity:
ordinary Territory: 0 Features
mildly interesting Territory: 1 Feature
rich, dangerous, strategic Territory: 2 Features
rare landmark Territory: 3 Features
story Territory: 4 Features by override
Capacity formula:
capacityScore =
max(resourceRichness, threat, ancientness, routeImportance, settlementSuitability)
+ landmarkBias
+ seededNoise
Budget layers:
global budget:
maximum count across the whole world
region budget:
keeps macro distribution readable
area budget:
prevents one area from absorbing every rare Feature
province budget:
keeps local play varied
Territory capacity:
prevents clutter inside one Territory
Compatibility And Exclusion
Conflict examples:
DormantNexus conflicts with duplicate Nexus/Wardheart anchor placement
PristineSpring conflicts with SevereTaintSource unless explicitly corrupted
OpenMarketCrossroads conflicts with DeepWildernessOnly
LargeMineSite requires enough orePotential and terrain support
DenseForestFeature requires enough forestCoverage
Synergy examples:
AncientRoadSegment increases chance of RuinedWatchtower nearby.
RiverMeadow increases chance of GoodFarmland and OldMill.
DemonGate increases chance of DemonTracks, BlightedField, and RefugeeCamp nearby.
OreBearingUpland increases chance of OreVein and CollapsedMine.
ForestEdge increases chance of GameTrail, HiddenCopse, and AmbushGrass.
DemonGate synergy lore link: (See lore exposure: lh-game1-world-hooks.md — Demon gates row, gate scarring/taint fields row)
Synergy must be budgeted and deterministic. It must not create unbounded chains.
Starting Nexus Exception
The starting Nexus is a constrained story Feature, not ordinary random ruin placement.
Rules:
starting Territory must contain a dormant or damaged Nexus Feature
starting Nexus starts hidden, dormant, damaged, buried, or unidentified
starting Nexus is revealed through the starting ruin chain
only the starting ruin chain has this guarantee
not every ruin is a Nexus
generic site suitability never unlocks founding by itself
Correct founding path:
find ruin clue
investigate ruin
identify dormant Nexus / Wardheart
stabilize Nexus
unlock Establish the Core
establish the Core once
remove founding activity forever
Gates, Taint, And Pressure Features
Some Features are pressure sources, not rewards.
The pressure itself is the Threat. The Feature record is the sign, hazard source, or partial place information carrying that Threat until a full Site exists.
Examples:
MinorFissure
LesserGate
DemonGate
BlackAshPatch
BlightedField
DemonTracks
CorruptedLeylineTrace
Pressure Features can:
- spread taint
- increase threat
- lower route safety
- block rest
- block claim or designation
- make local activities risky
- spawn nearby clues
- escalate if ignored
- unlock cleansing, assault, containment, or sealing activities
Gate and taint Features must align with World Pressure and Nexus.
Feature State
Recommended state ladder:
Hidden
Hinted
Discovered
Surveyed
Handled
Secured
Developed
Depleted
Destroyed
Not every Feature uses every state. The contract requires enough states to support mystery, partial knowledge, local activity, cleanup, ownership, and development.
Site-facing wording, once a Feature converts, should use explicit place states: wild, discovered, secured, active, owned, developed, depleted, sealed, destroyed.
Quest Hooks from Feature Transitions
Feature state changes are a primary input to the dynamic quest pressure system. The mapping of specific transitions to quest artifacts (clues, objective updates, mission candidates, urgency escalations) is owned by ../systems/gd-dynamic-quest-pressure-model.md under "Feature-Transition Trigger Table". This contract defines what features can transition into; the generator contract defines what those transitions cause for the planning layer.
Activity Generation
Activities are generated from Feature state plus broader state:
availableActivity(feature, actor, territory) =
feature exists
required Feature state passes
Territory knowledge state passes
actor is present or has valid remote access
required tools/resources/act pass
threat blockers pass or the activity handles the threat
route/control requirements pass
Activity families:
Survey
Investigate
Clear
Secure
Harvest
Excavate
Consecrate
Cleanse
Build Outpost
Develop Site
Study
Patrol
Seal
Exploit
Establish the Core
If the MC is not at the Territory, local activities require travel or must state why they are unavailable.
Causal Rewards
Feature rewards must come from Feature identity.
Good examples:
RuinedMill:
tools, blueprint, local workshop unlock, restored production site
AncientForgeRuin:
forge upgrade, metalworking recipe, Forge Core clue
CollapsedMine:
ore vein reveal, mining site, stone or metal salvage
WardRuin:
shield knowledge, ward repair project, taint suppression, Nexus clue
ArchiveRuin:
map fragment, lore, research topic, quest chain
DemonFissure:
shard, taint reduction, gate warning, danger escalation if ignored
Bad pattern:
completed Feature -> random global production bonus
Do not replace Feature identity with generic pseudo-channels such as Research Bonus, Morale Bonus, Mineral Bonus, or Defense Bonus.
Prefer explicit feature-local values or explicit target modifiers instead. Examples only: DiscoveryDifficulty, SurveyDifficulty, InteractionDifficulty, Progress, ThreatLevel, LootValue, or named causal effects on the actually affected scope.
Effects And Modifiers
Apply effects to the scope that is actually affected.
Examples:
WildGrainPatch discovered:
reveals food potential and forage activity
WildGrainPatch secured:
adds local FoodOutput or FarmingSuitability modifier
OreVein surveyed:
reveals ore quality and mining activity
OreVein developed:
adds MetalOutput to owning Territory, outpost, district, or settlement
through the proper scope
DemonNest active:
increases ThreatLevel
lowers RouteSafety and RestSafety
blocks safe work
DemonNest handled:
removes local threat modifier
may reveal salvage, shard, route safety, or taint source information
Use Territory-scoped modifiers first. Promote effects to settlement, province, or region only when ownership, route connection, designation, site development, or story state justifies it.
Site Conversion
Sites and buildings are first-class place content. They are not the same category as generated wild Features, even when a Feature is the discovery path that leads to them.
Resource nodes, ruins, and dungeon leads can begin as Features and convert into Sites when they become enterable, exploitable, defensible, or developed. Threat-heavy places follow the same rule: they become Threat Sites or Sites with linked threats once the place itself is part of play.
Examples:
HiddenSpring -> WaterSite
OreVein -> MineSite
RuinedMill -> RestoredWorkshopSite
WardRuin -> WardSite
DemonFissure -> SealedRiftSite or CleansingSite
Conversion requirements can include:
- discovery state
- survey result
- threat cleared
- ownership or outpost access
- workers
- resources
- route connection
- act unlock
- designation compatibility
Plains Feature Examples
Plains are about food, grazing, visibility, roads, exposure, old travel routes, and pressure.
Possible plains Features:
Food/resource:
WildGrainPatch
GrazingHerd
GoodFarmland
ForagingGround
HerbMeadow
Water:
SeasonalStream
ShallowPond
ReedPool
HiddenSpring
Travel:
RoadTrace
CaravanCamp
MarketCrossroads
OldBoundaryStones
Shelter/defense:
LoneHill
WindbreakCopse
EarthenRise
ExposedApproach
Threat:
AmbushGrass
PredatorTrail
BanditCamp
DemonTracks
BlightedField
History:
StandingStones
BattlefieldRemnant
BuriedRuins
AncientRoadSegment
FallenWatchtower
SealedBarrow
Example WildGrainPatch:
eligible if:
plainsCoverage >= 35
fertility >= 55
corruption <= 50
score =
plainsCoverage * 0.30
+ fertility * 0.45
+ moisture * 0.15
- forestCoverage * 0.10
- corruption * 0.25
+ noise * 0.10
Example RoadTrace:
eligible if:
slopeAvg <= 35
tinyWaterCoverage <= 35
routeImportance >= 40
score =
routeImportance * 0.45
+ plainsCoverage * 0.20
+ roadSuitability * 0.25
- wetlandCoverage * 0.20
- roughness * 0.15
+ noise * 0.10
Example DemonTracks:
eligible if:
demonPressure >= 35
knowledgeState >= Sighted
score =
demonPressure * 0.45
+ corruption * 0.25
+ visibility * 0.10
+ routeImportance * 0.10
+ noise * 0.10
Other Terrain Examples
Deep Forest:
high forestCoverage
medium cover
lower visibility
possible Features:
ForestStand
GameTrail
HiddenCopse
OldShrine
AmbushSigns
WitchPath
Rocky Upland:
high slope and rockyCoverage
lower fertility
higher defensibility
possible Features:
OreVein
QuarryFace
CollapsedMine
WatchHill
CaveMouth
FallenTower
Wet Meadow:
high moisture and fertility
moderate tinyWaterCoverage or water adjacency
possible Features:
SeasonalStream
ReedPool
GoodFarmland
OldMillRace
SoftGround
TaintedRunoff
Blighted Field:
high corruption or corruptedCoverage
lower rest safety
possible Features:
BlackAshPatch
DemonTracks
BlightedCrop
MinorFissure
DeadGrove
CleansingSite
Land-Coastal:
land Territory adjacent to water Territory
possible Features:
FishingCamp
LandingBeach
CliffPath
OldPier
SmugglerCove
StormWreck
Water-Coast or Water-Inland-Lake:
water Territory, not settlement land by default
possible Features:
FishingGround
ShallowReef
FerryRoute
LakeIsland
SunkenRuin
DangerousCurrent
UX Query Contract
The Territory UI consumes generated Feature state.
Rules:
- Do not show nonexistent Features.
- Do not show hidden Features as known rows.
- Show hints only when discovery state permits.
- Group activities by source: Territory, Feature, Approach, Threat, Site.
- Use site-first wording for enterable, exploitable, or developable places.
- Dungeons surface through Site entries once discovered or enterable; before then only clues, Features, or threat signs show.
- Feature panels open inside the Territory workspace.
- Feature detail panels show status, known facts, unknowns, progress, risks, blockers, available activities, possible outcomes, and effects on Territory.
Validation
Feature validation must check:
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
Feature-validation predicates above are registered as named
ValidationPredicate rows into the shared ValidationRegistry defined by
gd-generation-validation.md; a failed
predicate produces a ValidationViolation carried by ValidationReport
and fails world load loudly rather than silently degrading. The fail-loud
contract is the host-runtime expression of gate item 4 of
.claude/rules/valenar-contract-backing.md.
Runtime Migration Direction
Required direction:
1. Extend FeatureData beyond binary discovered/cleared/hostile state.
2. Replace random Feature sprinkling with score-and-budget placement.
3. Store placement reason tags and generated strength/quality.
4. Generate discovery state from Territory knowledge and Feature difficulty.
5. Wire OnDiscovered, OnInteract, OnCleared, and later state transitions.
6. Replace hard-coded Act 0 feature rows with snapshot-backed Features and
hints.
7. Enforce Nexus-gated founding through runtime state.
The current templates for ruins, dungeon-linked clues and sites, ore veins, shrines, groves, and similar world content remain useful as content seeds, but their placement and lifecycle must be driven by this contract.