# `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:

```json
{
  "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"
  },
  "overrides": {
    "skills:craft_alchemy": {
      "ICON": "ife_foc_alchm"
    }
  }
}
```

Weapon family shape:

```json
{
  "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"
  },
  "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`,
  and `auto_prereq_fields`
- `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`
- 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
