4.0 KiB
4.0 KiB
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, andtopdata_test.gocover 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_childmeanschildis an expansion ofparent- 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
- parent:
gnome_rock- parent:
gnome - child:
rock
- parent:
toughness_10- parent:
toughness - child:
10
- parent:
Standalone keys without an underscore are treated as a parent identity with no child.
Generated Family Files
Canonical generated families declare:
{
"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",
"auto_prereq_fields": {
"PREREQFEAT1": "weaponfocus"
},
"child_source": {
"dataset": "baseitems",
"column": "WeaponFocusFeat"
}
}
Rules:
familyis an authored label and is not a hardcoded switch keyfamily_keyis the structural parent identity used in generated child keystemplateis optional for the primitive in general, but required by current masterfeat-backedfeatfamiliesname_prefix,label_prefix, andconstant_prefixdefine how new child rows are named when they are not already authoredtemplate_fieldslists template-backed fields copied from the referenced template row when authored; it is optional if the family owns its shared values directlydefault_fieldsapplies authored shared/default values to every generated family row and can be used to move family-wide shared values out of the template rowapply_after_modules: truere-applies the generated family after normal module files, so authored family-shared values remain authoritative even for legacy explicitly authored child rowschild_ref_fieldbinds the generated child row back to the source row key, e.g.REQSKILLidentity_source: "child_source_value"tells the builder to reuse IDs/keys from the source column when that column already carries feat identitylegacy_family_keysdeclares previous generated-family prefixes that should donate existing locked row IDs to the current family when a family is renamedauto_prereq_fieldsbinds prerequisite fields to other authored families byfamily_key, without code changesallow_existing_onlylimits expansion to already-authored child rows when a family is intentionally partialchild_source.datasetidentifies the canonical dataset driving expansionchild_source.columnis used when expansion is gated by a non-null source fieldchild_source.predicateis used when expansion depends on a named rule such as accessibility
Row Metadata
Generated rows can carry:
{
"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.