diff --git a/internal/topdata/FEAT_GENERATED_FAMILIES_CONTRACT.md b/internal/topdata/FEAT_GENERATED_FAMILIES_CONTRACT.md index 943b6c5..6c5f574 100644 --- a/internal/topdata/FEAT_GENERATED_FAMILIES_CONTRACT.md +++ b/internal/topdata/FEAT_GENERATED_FAMILIES_CONTRACT.md @@ -142,6 +142,9 @@ Rules: - `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 diff --git a/internal/topdata/native.go b/internal/topdata/native.go index ea3a839..92a1723 100644 --- a/internal/topdata/native.go +++ b/internal/topdata/native.go @@ -1898,18 +1898,18 @@ func buildFamilyExpansionGeneratedModule(path string, obj map[string]any, ctx *f override[field] = map[string]any{"id": prereqKey} } } + if spec.NamePrefix != "" { + override["FEAT"] = map[string]any{ + "tlk": map[string]any{ + "key": featKey + ".name", + "text": fmt.Sprintf("%s (%s)", spec.NamePrefix, titleChildName), + }, + } + } if !ctx.featKeyExists(featKey) { if spec.LabelPrefix != "" { override["LABEL"] = spec.LabelPrefix + "_" + labelSuffix } - if spec.NamePrefix != "" { - override["FEAT"] = map[string]any{ - "tlk": map[string]any{ - "key": featKey + ".name", - "text": fmt.Sprintf("%s (%s)", spec.NamePrefix, titleChildName), - }, - } - } for _, field := range spec.TemplateFields { override[field] = map[string]any{"field": field, "ref": spec.Template} } diff --git a/internal/topdata/topdata_test.go b/internal/topdata/topdata_test.go index 6ab096d..fe28fe3 100644 --- a/internal/topdata/topdata_test.go +++ b/internal/topdata/topdata_test.go @@ -2994,6 +2994,59 @@ func TestGeneratedWeaponFeatTitleStyleUsesSourceDisplayNameWithStableIdentity(t } } +func TestGeneratedWeaponFeatTitleStyleRewritesExistingFamilyFeatName(t *testing.T) { + root := testProjectRoot(t) + writeFeatGeneratedHarness(t, root, map[string]string{ + "weapon_focus.json": `{ + "family": "weapon_focus", + "family_key": "weapon_focus", + "template": "masterfeats:weaponfocus", + "name_prefix": "Weapon Focus", + "label_prefix": "FEAT_WEAPON_FOCUS", + "constant_prefix": "FEAT_WEAPON_FOCUS", + "identity_source": "child_source_value", + "child_source": {"dataset":"baseitems","column":"WeaponFocusFeat"}, + "title_style": {"child_case":"lower","child_parenthetical":"preserve"}, + "overrides": {} +}` + "\n", + }, map[string]int{"feat:weapon_focus_dwaxe": 3003}) + writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), `{ + "entries": { + "83310": {"text": "Dwarven Waraxe"} + } +}`+"\n") + writeFile(t, filepath.Join(root, "topdata", "data", "baseitems", "base.json"), `{ + "output": "baseitems.2da", + "columns": ["label", "Name", "WeaponFocusFeat"], + "rows": [ + {"id": 108, "key": "baseitems:dwarvenwaraxe", "label": "dwaxe", "Name": "83310", "WeaponFocusFeat": "3003"} + ] +}`+"\n") + writeFile(t, filepath.Join(root, "topdata", "data", "baseitems", "lock.json"), `{"baseitems:dwarvenwaraxe":108}`+"\n") + writeFile(t, filepath.Join(root, "topdata", "data", "feat", "base.json"), `{ + "output": "feat.2da", + "compare_reference": false, + "columns": ["LABEL","FEAT","DESCRIPTION","MASTERFEAT","Constant"], + "rows": [ + { + "id": 3003, + "key": "feat:weapon_focus_dwaxe", + "LABEL": "FEAT_WEAPON_FOCUS_DWAXE", + "FEAT": 83318, + "DESCRIPTION": "****", + "MASTERFEAT": 6, + "Constant": "FEAT_WEAPON_FOCUS_DWAXE" + } + ] +}`+"\n") + + module := buildGeneratedFeatModuleForTest(t, root, "weapon_focus.json") + override := generatedFeatOverrideByKey(t, module, "feat:weapon_focus_dwaxe") + if text := generatedFeatOverrideTitle(t, override); text != "Weapon Focus (dwarven waraxe)" { + t.Fatalf("expected existing generated family row to receive normalized FEAT TLK text, got %q", text) + } +} + func TestGeneratedSkillFeatTitleStyleFlattensChildParenthetical(t *testing.T) { root := testProjectRoot(t) writeFeatGeneratedHarness(t, root, map[string]string{