Files
sow-tools/internal/topdata/FEAT_GENERATED_FAMILIES_CONTRACT.md
T

6.7 KiB

feat Generated Families Contract

Status Snapshot

Current state as of 2026-05-13:

  • Implemented for the current native feat pipeline.
  • Native tests cover generated-family expansion, family-owned shared defaults, masterfeat-backed inheritance, and lock/TLK identity preservation.
  • The one explicitly deferred item in this contract is still current: feat.2da remains native-built but excluded from compare-topdata reference-catalog coverage through compare_reference: false.

Canonical authored paths:

  • topdata/data/feat/base.json
  • topdata/data/feat/lock.json
  • topdata/data/feat/generated/skill_focus.json
  • topdata/data/feat/generated/greater_skill_focus.json
  • topdata/data/feat/generated/weapon_focus.json
  • topdata/data/feat/generated/weapon_specialization.json
  • topdata/data/feat/generated/greater_weapon_focus.json
  • topdata/data/feat/generated/greater_weapon_specialization.json
  • topdata/data/feat/generated/improved_critical.json
  • topdata/data/feat/generated/overwhelming_critical.json
  • topdata/data/feat/modules/activecombat/core.json
  • topdata/data/feat/modules/activecombat/specialattacks.json
  • topdata/data/feat/modules/class/core.json
  • topdata/data/feat/modules/combat/core.json
  • topdata/data/feat/modules/defensive/core.json
  • topdata/data/feat/modules/magical/core.json
  • topdata/data/feat/modules/other/core.json
  • topdata/data/feat/modules/proficiency/core.json
  • topdata/data/feat/modules/racial/core.json
  • topdata/data/feat/modules/removedandhidden/core.json
  • topdata/data/feat/modules/skillfeat/affinity.json
  • topdata/data/feat/modules/skillfeat/focus.json
  • topdata/data/feat/modules/skillfeat/greaterfocus.json
  • topdata/data/feat/modules/skillfeat/other.json

skill_affinity is no longer authored as a generated feat file. It is synthesized from canonical racialtypes race grants.

Compact family-expansion shape:

{
  "family": "skill_focus",
  "family_key": "skillfocus",
  "template": "masterfeats:skillfocus",
  "name_prefix": "Skill Focus",
  "label_prefix": "FEAT_SKILL_FOCUS",
  "constant_prefix": "FEAT_SKILL_FOCUS",
  "default_fields": {
    "DESCRIPTION": {
      "tlk": {
        "key": "masterfeats:skillfocus.description",
        "text": "..."
      }
    },
    "CRValue": "0.5",
    "ReqSkillMinRanks": "1",
    "TOOLSCATEGORIES": "6"
  },
  "apply_after_modules": true,
  "child_ref_field": "REQSKILL",
  "child_source": {
    "dataset": "skills",
    "predicate": "accessible"
  },
  "title_style": {
    "child_case": "lower",
    "child_parenthetical": "comma"
  },
  "overrides": {
    "skills:craft_alchemy": {
      "ICON": "ife_foc_alchm"
    }
  }
}

Weapon family shape:

{
  "family": "weapon_focus",
  "family_key": "weaponfocus",
  "template": "masterfeats:weaponfocus",
  "name_prefix": "Weapon Focus",
  "label_prefix": "FEAT_WEAPON_FOCUS",
  "constant_prefix": "FEAT_WEAPON_FOCUS",
  "legacy_family_keys": ["epic_weapon_focus"],
  "default_fields": {
    "DESCRIPTION": {
      "tlk": {
        "key": "masterfeats:weaponfocus.description",
        "text": "..."
      }
    },
    "MINATTACKBONUS": "1",
    "TOOLSCATEGORIES": "1"
  },
  "apply_after_modules": true,
  "identity_source": "child_source_value",
  "child_source": {
    "dataset": "baseitems",
    "column": "WeaponFocusFeat"
  },
  "title_style": {
    "child_case": "lower",
    "child_parenthetical": "preserve"
  },
  "overrides": {
    "baseitems:heavymace": {
      "ICON": "ife_wepfoc_Lma"
    }
  }
}

Rules:

  • any generated feat file with family-expansion fields is treated as an authored family-expansion definition
  • family is descriptive only; the builder does not hardcode family names
  • family behavior is driven by authored fields such as template_fields, default_fields, apply_after_modules, identity_source, child_ref_field, auto_prereq_fields, and optional title_style
  • default_fields is a family-wide shared layer; it applies to existing and newly generated child rows before per-child overrides are merged
  • apply_after_modules: true makes the generated family the final authoritative shared layer for legacy explicitly authored child rows, so shared baselines can live in the generated family file instead of masterfeats overrides
  • legacy_family_keys declares old generated-family prefixes whose existing row IDs should be inherited by the current family; this is how renamed families such as greater_skill_focus retaining vanilla epic_skill_focus rows preserve hardcoded engine behavior without hardcoding the rename in toolkit logic
  • family-expansion files must declare template, family_key, and child_source.dataset
  • child_source.column is used when expansion is gated by a non-null source field
  • child_source.predicate: "accessible" currently means HideFromLevelUp != 1
  • title_style only changes generated child display text while composing titles; generated feat keys, TLK keys, row IDs, family metadata, and source references remain tied to generated family identity
  • generated family title text is authoritative for family-managed existing rows as well as newly allocated rows, so in-game TLK names and generated wiki titles are derived from the same normalized FEAT text
  • title_style.child_case defaults to preserve; lower lowercases the resolved child display text after parenthetical formatting
  • title_style.child_parenthetical defaults to preserve; comma flattens one child parenthetical suffix such as Knowledge (local) into Knowledge, local before the child text is wrapped by the family title
  • unsupported title_style values fail generated-family validation instead of silently changing title behavior
  • skill focus families intentionally do not set allow_existing_only, so any new accessible skill automatically receives matching focus rows
  • compact overrides are keyed by source dataset key:
    • skills:* for skill families
    • baseitems:* for weapon families
  • generated rows carry meta.family so the underscore family rule is preserved even before wiki generation exists
  • emitted feat keys must prefer existing canonical lock/TLK identities over newly derived child spellings
  • manual and irregular feat content continues to live under feat/modules/
  • generated families and module-authored feats are merged into the same final feat.2da pipeline and share inheritance, override, TLK, and family-metadata behavior

Current rollout policy:

  • feat.2da still builds natively
  • feat.2da is still excluded from compare-topdata output-catalog self-check coverage via compare_reference: false
  • manual and irregular feat families remain authored directly in modules rather than compact generated-family files