Tags and Classification
Status: authoritative definition of the tag vocabulary and classification
system. The committed tag list lives in Content/common/tags.secs and
Generated/Vocabulary/Tags.cs (14 tags as of Wave D). Terrain-derived tags
described in gd-location-generation-contract.md
are planned but not yet committed to Content. The tag/category split for
Feature families is current prototype shape: Feature category and tag are
distinct technical constructs and are expected to remain so until a future
ADR explicitly changes that.
Purpose
This document defines how tags work in Valenar, what the namespace rules
are, how modifier tags differ from structural/activity-predicate tags, how
Feature categories differ from tags, and what validation rules apply. The catalog
of committed and planned tags lives in
catalogs/gd-tag-catalog.md — that page
enumerates each committed TagId constant and lists the planned terrain
tags that are described in generation contracts but not yet committed.
This page is the design surface; the catalog is the lookup surface. When a reader needs to know what a tag does, they read this page. When a reader needs to know whether a particular tag name is committed and what its canonical id string is, they read the catalog.
Namespace Rules
All committed Valenar tags use the canonical namespace
valenar:tag/<name> (colon-slash format, per the committed SECS identity
string convention in docs/design/01-world-shape.md).
No bare-name and no dot-form tag identities are permitted in live Content
or Generated code. The TagId.Create("valenar:tag/<name>") pattern in
Content/common/tags.secs and Generated/Vocabulary/Tags.cs is the
committed form; any new tag must follow it.
A tag declared in mod content uses the mod's source-set namespace rather
than valenar: — for example, a tag declared in a cautious_survival
mod would use cautious_survival:tag/<name>. Tags from different
namespaces are distinct identities even when their <name> segment is
the same, and they hash to distinct TagId constants.
Modifier Tags vs Template / Activity-Predicate Tags
The two groups of tags in Content/common/tags.secs serve different
purposes.
Modifier tags (Buff, Debuff, Disease, Military, Seasonal, Economic)
are used by bulk modifier operations. They categorize effects for
targeting, removal, stacking policy, and propagation filters. A
remove all modifiers with tag valenar:tag/disease operation matches
modifiers whose declaration carries the Disease tag in its tags = ...
slot. Modifier tags do not classify templates.
Structural-predicate tags (Food, Mineral, Material, Magical,
Strategic, Perishable, Currency, Durable) are used by has_tag
predicates in propagates_to and aggregate-where clauses, and by
Resource template categories. A scope filter such as
where item has_tag valenar:tag/food matches templates whose
declaration carries the Food tag in its tags = ... slot.
Structural-predicate tags do not classify modifiers. Activity tags and
policy from tags selector provenance are deferred until a scoped
tag-fixture wave lands .secs, Generated, and tests together.
A tag in the modifier group should not be confused with a Feature
category or a terrain-derived class. Modifier tags are not used in
template tags = ... slots, and structural-predicate tags are not used
in modifier tags = ... slots. The two namespaces of usage are
disjoint by convention even though both groups share the same
valenar:tag/<name> namespace; the discipline is enforced by the
authors of Content/common/tags.secs rather than by a syntactic split.
Activity Tags and Policy Selectors
Activity tags use the committed source form tags = Food; in an
activity body and lower to SecsActivity.Tags. Policy selectors use
consider activities from tags = Food; and lower to
SelectorSource.FromTags. These are committed compiler/runtime
contracts, but Valenar does not currently carry live executable
provenance for them.
The scoped tag-fixture wave that proves this surface should tag only real food-yielding site activities, such as:
ForageForest, because it yields food and wood.ForageFoodPatch, because it yields food.HuntForestTracks, because it yields food.
Nearby activities that survey food signs or practice foraging but do not store food yields should remain untagged. This keeps the future fixture tied to actual activity output rather than to a broad prose theme.
CharacterSurvival.FoodSelector is the intended policy selector fixture,
but it remains deferred until the same wave lands its .secs, Generated,
and tests together. It must not be documented as live Valenar provenance
until those artifacts land in scope.
RestSelector remains all_registered over RestAtCamp because
converting it to tag selection would require inventing a rest tag or
changing selector semantics.
Deferred has_tag Valenar Fixtures
The SECS runtime supports both propagates_to children where has_tag ...
and aggregate sources such as Children.Where(has_tag ...).Sum(...).
Valenar does not currently have a real modifier propagation or aggregate
channel that needs tag-filtered children, so no live Valenar source
fixture is authored for those surfaces. They stay deferred until a real
gameplay system needs them; generic engine tests cover the runtime
behavior meanwhile.
Feature Category vs Tag Distinction
Feature categories — the FeatureFamily enum values
(NaturalResource, Mineral, Ruin, Threat, Landmark,
SettlementOpportunity, Story) plus the broader feature-family
language used in
gd-feature-generation-contract.md
(NaturalResource, Water, Food, Forest, Mineral, Travel, Shelter, Ruin,
Threat, Corruption, Landmark, Nexus, SettlementOpportunity, Story) —
classify Feature records into generation families. They are NOT tags
in the valenar:tag/<name> sense:
- Feature categories are NOT
TagIdconstants and do not appear inContent/common/tags.secsorGenerated/Vocabulary/Tags.cs. - Feature categories are NOT used in
has_tagpredicates. - Feature categories live as an enum (
FeatureFamilyinGenerated/Locations/FeaturePlacement/FeaturePlacementTypes.cs), not as aTagId.
Tags are a separate classification layer that can overlap with Feature
category but exists on a different axis. A Feature in the Food
generation family may also carry the valenar:tag/food
structural-predicate tag — but the category and the tag are different
technical constructs. The category drives generation placement; the
tag drives propagation filters and bulk modifier selection.
Validation Rules
Tags must be declared in Content/common/tags.secs and lowered to
Generated/Vocabulary/Tags.cs as TagId constants. The future SECS
compiler will emit TagId constants from tags.secs; today they are
hand-written, per
pr-docs-codegen-rules.md. A
tag that appears in a modifier or template body but is not declared in
tags.secs is a provenance violation. The future compiler should reject
symbolic missing tags during binding (SECS0212); today the
tests/Valenar.Host.Tests/GeneratedProvenanceTests.cs provenance
guard catches authored references with no source, and runtime registry
static analysis warns if generated raw ids lack a corresponding
RegisterTag entry.
Tag names must use lower_snake_case after the namespace prefix. The
canonical id string is constructed once in tags.secs and reused
through the TagId field; do not re-compute the id string at the
call site or the FNV-1a-64 hash will not match.
The gd-tag-catalog.md lists all committed
tags and distinguishes them from planned tags. Adding a tag means
editing both tags.secs and Tags.cs, then updating the catalog.
Future Direction
When the Roslyn fork ships and the SECS compiler is live, TagId
constants will be emitted from tags.secs rather than hand-written.
The TagId.Create(...) pattern is the lowering target the future
compiler must satisfy.
Terrain-derived tags (Fertile, ForestEdge, EasyTravel, GoodRoadLand,
and similar — listed in
gd-location-generation-contract.md)
should be committed to tags.secs once the production generator is in
use. Their lifecycle is described in
gd-tag-catalog.md under "Planned Tags".
Until they are committed, they are description-only language in the
generation contract and must not be referenced as Tags.<Name>
constants anywhere in code.
The Feature category system may eventually be expressed as a
tag-like declaration that lets generation rules and propagation
filters share a single identity layer, but that direction is deferred
pending a future ADR. Open questions in this area live in
pr-open-questions.md under "Tag
Taxonomy" and "Feature Category vs Tag".
Cross-Links
- catalogs/gd-tag-catalog.md - the authoritative committed-and-planned tag list with canonical id strings and one-line semantics.
- generation/gd-feature-generation-contract.md - Feature template shape, Feature families, and how categories relate to tags during generation.
- generation/gd-location-generation-contract.md - derived classes and the planned terrain-tag set.
- implementation/pr-open-questions.md - open questions about the tag taxonomy and Feature category vs tag unification.