Skip to main content

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 TagId constants and do not appear in Content/common/tags.secs or Generated/Vocabulary/Tags.cs.
  • Feature categories are NOT used in has_tag predicates.
  • Feature categories live as an enum (FeatureFamily in Generated/Locations/FeaturePlacement/FeaturePlacementTypes.cs), not as a TagId.

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".