# Generic Family Expansion Contract ## Status Snapshot Current state as of 2026-05-13: - Implemented and actively used by native feat generation. - `family_expansion.go`, `native.go`, and `topdata_test.go` cover the current underscore-family interpretation, generated family metadata, and reapplication behavior. - Remaining work is mostly consumer expansion: use the same primitive in more datasets only when the authored data actually benefits from it. Topdata now treats underscore expansion as a structural rule: - `parent_child` means `child` is an expansion of `parent` - underscores are for family structure, not simulated spaces - existing canonical keys still win when lock or TLK state already established them This is a global interpretation rule, not a wiki-only convention. ## Identity - `weaponspecialization_club` - parent: `weaponspecialization` - child: `club` - `gnome_rock` - parent: `gnome` - child: `rock` - `toughness_10` - parent: `toughness` - child: `10` Standalone keys without an underscore are treated as a parent identity with no child. ## Generated Family Files Canonical generated families declare: ```json { "family": "weapon_focus", "family_key": "weaponfocus", "template": "masterfeats:weaponfocus", "name_prefix": "Weapon Focus", "label_prefix": "FEAT_WEAPON_FOCUS", "constant_prefix": "FEAT_WEAPON_FOCUS", "default_fields": { "DESCRIPTION": { "tlk": { "key": "masterfeats:weaponfocus.description", "text": "..." } }, "MINATTACKBONUS": "1", "TOOLSCATEGORIES": "1" }, "apply_after_modules": true, "identity_source": "child_source_value", "auto_prereq_fields": { "PREREQFEAT1": "weaponfocus" }, "child_source": { "dataset": "baseitems", "column": "WeaponFocusFeat" } } ``` Rules: - `family` is an authored label and is not a hardcoded switch key - `family_key` is the structural parent identity used in generated child keys - `template` is optional for the primitive in general, but required by current masterfeat-backed `feat` families - `name_prefix`, `label_prefix`, and `constant_prefix` define how new child rows are named when they are not already authored - `template_fields` lists template-backed fields copied from the referenced template row when authored; it is optional if the family owns its shared values directly - `default_fields` applies authored shared/default values to every generated family row and can be used to move family-wide shared values out of the template row - `apply_after_modules: true` re-applies the generated family after normal module files, so authored family-shared values remain authoritative even for legacy explicitly authored child rows - `child_ref_field` binds the generated child row back to the source row key, e.g. `REQSKILL` - `identity_source: "child_source_value"` tells the builder to reuse IDs/keys from the source column when that column already carries feat identity - `auto_prereq_fields` binds prerequisite fields to other authored families by `family_key`, without code changes - `allow_existing_only` limits expansion to already-authored child rows when a family is intentionally partial - `child_source.dataset` identifies the canonical dataset driving expansion - `child_source.column` is used when expansion is gated by a non-null source field - `child_source.predicate` is used when expansion depends on a named rule such as accessibility ## Row Metadata Generated rows can carry: ```json { "meta": { "family": { "parent": "weaponfocus", "child": "club", "source": "baseitems:club", "template": "masterfeats:weaponfocus" } } } ``` This metadata is builder-owned and does not affect emitted 2DA columns. It preserves family structure for later phases without enabling wiki generation yet.