Fix Generated Feat Identity Canonicalization
This commit is contained in:
+194
-2
@@ -765,6 +765,11 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
||||
if parsedID == lockedID {
|
||||
return override, nil
|
||||
}
|
||||
if dataset.Name == "feat" && generatedCanonicalLockCanMove(rawKey, parsedID, lockData) {
|
||||
delete(lockData, rawKey)
|
||||
lockModified = true
|
||||
return override, nil
|
||||
}
|
||||
cloned := make(map[string]any, len(override)-1)
|
||||
for field, value := range override {
|
||||
if field != "id" {
|
||||
@@ -779,6 +784,11 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
||||
}
|
||||
for lockedKey, lockedID := range lockData {
|
||||
if lockedKey != key && lockedID == rowID {
|
||||
if dataset.Name == "feat" && generatedAliasLockForKey(key, lockedKey) {
|
||||
delete(lockData, lockedKey)
|
||||
lockModified = true
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -2055,13 +2065,24 @@ func featSourceID(value any) (int, error) {
|
||||
func (c *featGeneratedContext) resolveGeneratedFeatIdentityBySource(familyKey, slug string, rawSource any) (string, int, bool, error) {
|
||||
if rawSource != nil && rawSource != nullValue {
|
||||
if parsedID, err := featSourceID(rawSource); err == nil && parsedID > 0 {
|
||||
if existingKey, ok := c.preferredFeatKeyForID(familyKey, parsedID); ok {
|
||||
return existingKey, parsedID, true, nil
|
||||
if generatedKey, ok := c.generatedFeatKeyForID(familyKey, parsedID); ok {
|
||||
return generatedKey, parsedID, true, nil
|
||||
}
|
||||
return c.preferredGeneratedFeatKey(familyKey, slug), parsedID, true, nil
|
||||
}
|
||||
}
|
||||
if rawMap, ok := rawSource.(map[string]any); ok {
|
||||
if ref, ok := rawMap["id"].(string); ok && strings.HasPrefix(ref, "feat:") {
|
||||
if generatedKey, rowID, ok := c.generatedFeatKeyFromCanonicalAlias(ref); ok {
|
||||
return generatedKey, rowID, true, nil
|
||||
}
|
||||
if rowID, hasID, err := c.featID(ref, nil); err != nil {
|
||||
return "", 0, false, err
|
||||
} else if hasID {
|
||||
if generatedKey, ok := c.generatedFeatKeyForID(familyKey, rowID); ok {
|
||||
return generatedKey, rowID, true, nil
|
||||
}
|
||||
}
|
||||
rowID, hasID, err := c.featID(ref, nil)
|
||||
return ref, rowID, hasID, err
|
||||
}
|
||||
@@ -2080,6 +2101,9 @@ func (c *featGeneratedContext) resolveGeneratedFeatIdentity(spec familyExpansion
|
||||
return c.resolveGeneratedFeatIdentityBySource(spec.FamilyKey, slug, rawSource)
|
||||
}
|
||||
featKey := c.preferredGeneratedFeatKey(spec.FamilyKey, slug)
|
||||
if legacyKey, legacyID, ok := c.generatedFeatKeyFromLegacyAlias(spec.FamilyKey, slug); ok {
|
||||
return legacyKey, legacyID, true, nil
|
||||
}
|
||||
rowID, hasID, err := c.featID(featKey, nil)
|
||||
return featKey, rowID, hasID, err
|
||||
}
|
||||
@@ -2116,6 +2140,174 @@ func generatedFamilyPrereqKey(ctx *featGeneratedContext, sourceRow map[string]an
|
||||
return featKey, true
|
||||
}
|
||||
|
||||
func generatedFamilyLegacyAliases(familyKey string) []string {
|
||||
switch familyKey {
|
||||
case "skillfocus":
|
||||
return []string{"skillfocus"}
|
||||
case "greaterskillfocus":
|
||||
return []string{"epicskillfocus"}
|
||||
case "greaterweaponfocus":
|
||||
return []string{"epicweaponfocus"}
|
||||
case "greaterweaponspecialization":
|
||||
return []string{"epicweaponspecialization"}
|
||||
case "overwhelmingcritical":
|
||||
return []string{"epicoverwhelmingcritical"}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func generatedAliasLockForKey(canonicalKey, lockedKey string) bool {
|
||||
canonicalKey = strings.TrimPrefix(strings.TrimSpace(canonicalKey), "feat:")
|
||||
lockedKey = strings.TrimPrefix(strings.TrimSpace(lockedKey), "feat:")
|
||||
if canonicalKey == "" || lockedKey == "" {
|
||||
return false
|
||||
}
|
||||
identity := splitFamilyExpansionIdentity(canonicalKey)
|
||||
if identity.Parent == "" || identity.Child == "" {
|
||||
return false
|
||||
}
|
||||
legacyChildren := legacyFamilyChildAliases(identity.Parent, identity.Child)
|
||||
for _, alias := range generatedFamilyLegacyAliases(identity.Parent) {
|
||||
prefix := alias + "_"
|
||||
if !strings.HasPrefix(lockedKey, prefix) {
|
||||
continue
|
||||
}
|
||||
lockedChild := strings.TrimPrefix(lockedKey, prefix)
|
||||
for _, legacyChild := range legacyChildren {
|
||||
if normalizeKeyIdentity(lockedChild) == normalizeKeyIdentity(legacyChild) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func generatedCanonicalLockCanMove(canonicalKey string, rowID int, lockData map[string]int) bool {
|
||||
if rowID <= 0 {
|
||||
return false
|
||||
}
|
||||
for lockedKey, lockedID := range lockData {
|
||||
if lockedID == rowID && generatedAliasLockForKey(canonicalKey, lockedKey) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *featGeneratedContext) generatedFeatKeyForID(familyKey string, rowID int) (string, bool) {
|
||||
if rowID <= 0 {
|
||||
return "", false
|
||||
}
|
||||
for _, candidates := range []map[string]int{c.lockData, c.supplementalID} {
|
||||
matches := make([]string, 0)
|
||||
prefix := "feat:" + familyKey + "_"
|
||||
for key, candidateID := range candidates {
|
||||
if candidateID == rowID && strings.HasPrefix(key, prefix) {
|
||||
matches = append(matches, key)
|
||||
}
|
||||
}
|
||||
if best, ok := preferredExpandedKey(matches); ok {
|
||||
return best, true
|
||||
}
|
||||
}
|
||||
for _, alias := range generatedFamilyLegacyAliases(familyKey) {
|
||||
for _, candidates := range []map[string]int{c.lockData, c.supplementalID} {
|
||||
matches := make([]string, 0)
|
||||
prefix := "feat:" + alias + "_"
|
||||
for key, candidateID := range candidates {
|
||||
if candidateID != rowID || !strings.HasPrefix(key, prefix) {
|
||||
continue
|
||||
}
|
||||
child := strings.TrimPrefix(key, prefix)
|
||||
matches = append(matches, "feat:"+familyKey+"_"+child)
|
||||
}
|
||||
if best, ok := preferredExpandedKey(matches); ok {
|
||||
return best, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (c *featGeneratedContext) generatedFeatKeyFromLegacyAlias(familyKey, sourceSlug string) (string, int, bool) {
|
||||
targets := legacyFamilyChildAliases(familyKey, sourceSlug)
|
||||
canonicalKey := c.preferredGeneratedFeatKey(familyKey, sourceSlug)
|
||||
for _, alias := range generatedFamilyLegacyAliases(familyKey) {
|
||||
for _, candidates := range []map[string]int{c.lockData, c.supplementalID} {
|
||||
prefix := "feat:" + alias + "_"
|
||||
for _, target := range targets {
|
||||
for key, rowID := range candidates {
|
||||
if rowID <= 0 || !strings.HasPrefix(key, prefix) {
|
||||
continue
|
||||
}
|
||||
child := strings.TrimPrefix(key, prefix)
|
||||
if normalizeKeyIdentity(child) == normalizeKeyIdentity(target) {
|
||||
return canonicalKey, rowID, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", 0, false
|
||||
}
|
||||
|
||||
func legacyFamilyChildAliases(familyKey, sourceSlug string) []string {
|
||||
aliases := []string{}
|
||||
if familyKey != "greaterskillfocus" && familyKey != "skillfocus" {
|
||||
return []string{sourceSlug}
|
||||
}
|
||||
switch normalizeKeyIdentity(sourceSlug) {
|
||||
case "acrobatics":
|
||||
aliases = append(aliases, "tumble")
|
||||
case "animalhandling":
|
||||
aliases = append(aliases, "animalempathy")
|
||||
case "craftarmorsmithing":
|
||||
aliases = append(aliases, "craftarmor")
|
||||
case "crafttinkering":
|
||||
aliases = append(aliases, "settrap")
|
||||
case "craftweaponsmithing":
|
||||
aliases = append(aliases, "craftweapon")
|
||||
case "craftwoodworking":
|
||||
aliases = append(aliases, "crafttrap")
|
||||
case "disabledevice":
|
||||
aliases = append(aliases, "disabletrap")
|
||||
case "influence":
|
||||
aliases = append(aliases, "persuade")
|
||||
case "sleightofhand":
|
||||
aliases = append(aliases, "pickpocket")
|
||||
}
|
||||
aliases = append(aliases, sourceSlug)
|
||||
return aliases
|
||||
}
|
||||
|
||||
func (c *featGeneratedContext) generatedFeatKeyFromCanonicalAlias(canonicalKey string) (string, int, bool) {
|
||||
stripped := strings.TrimPrefix(strings.TrimSpace(canonicalKey), "feat:")
|
||||
identity := splitFamilyExpansionIdentity(stripped)
|
||||
if identity.Parent == "" || identity.Child == "" {
|
||||
return "", 0, false
|
||||
}
|
||||
legacyChildren := legacyFamilyChildAliases(identity.Parent, identity.Child)
|
||||
for _, alias := range generatedFamilyLegacyAliases(identity.Parent) {
|
||||
for _, legacyChild := range legacyChildren {
|
||||
if _, rowID, ok := c.featKeyForFamilyChild(alias, legacyChild); ok {
|
||||
return "feat:" + identity.Parent + "_" + identity.Child, rowID, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", 0, false
|
||||
}
|
||||
|
||||
func (c *featGeneratedContext) featKeyForFamilyChild(familyKey, child string) (string, int, bool) {
|
||||
key := "feat:" + familyKey + "_" + child
|
||||
for _, candidates := range []map[string]int{c.lockData, c.supplementalID} {
|
||||
if rowID, ok := candidates[key]; ok && rowID > 0 {
|
||||
return key, rowID, true
|
||||
}
|
||||
}
|
||||
return "", 0, false
|
||||
}
|
||||
|
||||
func (c *featGeneratedContext) preferredFeatKeyForID(familyKey string, rowID int) (string, bool) {
|
||||
prefix := "feat:" + familyKey
|
||||
type candidateBuckets struct {
|
||||
|
||||
@@ -982,6 +982,31 @@ func validateNativeLockAllocation(dataDir string, report *ValidationReport) {
|
||||
})
|
||||
continue
|
||||
}
|
||||
if dataset.Name == "feat" {
|
||||
generatedModules, err := collectGeneratedModules(dataset, lockData)
|
||||
if err != nil {
|
||||
report.Diagnostics = append(report.Diagnostics, Diagnostic{
|
||||
Severity: SeverityError,
|
||||
Path: dataset.GeneratedDir,
|
||||
Message: fmt.Sprintf("collect generated feat modules for lock allocation validation: %v", err),
|
||||
})
|
||||
continue
|
||||
}
|
||||
for _, module := range generatedModules {
|
||||
ids, err := collectExplicitModuleIDs(dataset.Name, module.Name, module.Data)
|
||||
if err != nil {
|
||||
report.Diagnostics = append(report.Diagnostics, Diagnostic{
|
||||
Severity: SeverityError,
|
||||
Path: module.Name,
|
||||
Message: err.Error(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
for key, rowID := range ids {
|
||||
explicitModuleIDs[key] = rowID
|
||||
}
|
||||
}
|
||||
}
|
||||
invalid := 0
|
||||
for key, rowID := range lockData {
|
||||
if rowID > baseBoundaryID {
|
||||
|
||||
@@ -2191,10 +2191,11 @@ func writeFeatGeneratedHarness(t *testing.T, root string, generatedFiles map[str
|
||||
{"id": 5, "key": "masterfeats:greaterskillfocus", "LABEL": "GreaterSkillFocus", "STRREF": {"tlk": {"key": "masterfeats:greaterskillfocus.name", "text": "Greater Skill Focus"}}, "DESCRIPTION": {"tlk": {"key": "masterfeats:greaterskillfocus.description", "text": "Choose a skill again."}}, "ICON": "ife_skfoc", "CRValue": "1", "ReqSkillMinRanks": "6"},
|
||||
{"id": 6, "key": "masterfeats:weaponfocus", "LABEL": "WeaponFocus", "STRREF": {"tlk": {"key": "masterfeats:weaponfocus.name", "text": "Weapon Focus"}}, "DESCRIPTION": {"tlk": {"key": "masterfeats:weaponfocus.description", "text": "Focus on one weapon."}}, "ICON": "ife_wepfoc", "CRValue": "1", "MINATTACKBONUS": "1", "ALLCLASSESCANUSE": "1", "PreReqEpic": "0", "ReqAction": "1", "TOOLSCATEGORIES": "1"},
|
||||
{"id": 7, "key": "masterfeats:improvedcritical", "LABEL": "ImprovedCritical", "STRREF": {"tlk": {"key": "masterfeats:improvedcritical.name", "text": "Improved Critical"}}, "DESCRIPTION": {"tlk": {"key": "masterfeats:improvedcritical.description", "text": "Improved critical threat."}}, "ICON": "ife_impcrit", "CRValue": "1", "MINATTACKBONUS": "8", "ALLCLASSESCANUSE": "1", "PreReqEpic": "0", "ReqAction": "1", "TOOLSCATEGORIES": "1"},
|
||||
{"id": 8, "key": "masterfeats:weaponproficiencycommoner", "LABEL": "WeaponProficiencyCommoner", "STRREF": {"tlk": {"key": "masterfeats:weaponproficiencycommoner.name", "text": "Commoner Weapon Proficiency"}}, "DESCRIPTION": {"tlk": {"key": "masterfeats:weaponproficiencycommoner.description", "text": "Simple commoner weapon proficiency."}}, "ICON": "ife_weppro", "CRValue": "0.2", "SUCCESSOR": "9", "ALLCLASSESCANUSE": "1", "PreReqEpic": "0", "ReqAction": "1"}
|
||||
{"id": 8, "key": "masterfeats:weaponproficiencycommoner", "LABEL": "WeaponProficiencyCommoner", "STRREF": {"tlk": {"key": "masterfeats:weaponproficiencycommoner.name", "text": "Commoner Weapon Proficiency"}}, "DESCRIPTION": {"tlk": {"key": "masterfeats:weaponproficiencycommoner.description", "text": "Simple commoner weapon proficiency."}}, "ICON": "ife_weppro", "CRValue": "0.2", "SUCCESSOR": "9", "ALLCLASSESCANUSE": "1", "PreReqEpic": "0", "ReqAction": "1"},
|
||||
{"id": 9, "key": "masterfeats:greaterweaponfocus", "LABEL": "GreaterWeaponFocus", "STRREF": {"tlk": {"key": "masterfeats:greaterweaponfocus.name", "text": "Greater Weapon Focus"}}, "DESCRIPTION": {"tlk": {"key": "masterfeats:greaterweaponfocus.description", "text": "Focus harder on one weapon."}}, "ICON": "ife_gwepfoc", "CRValue": "1", "MINATTACKBONUS": "8", "ALLCLASSESCANUSE": "0", "PreReqEpic": "0", "ReqAction": "1", "TOOLSCATEGORIES": "1"}
|
||||
]
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "data", "masterfeats", "lock.json"), `{"masterfeats:skillfocus":4,"masterfeats:greaterskillfocus":5,"masterfeats:weaponfocus":6,"masterfeats:improvedcritical":7,"masterfeats:weaponproficiencycommoner":8}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "data", "masterfeats", "lock.json"), `{"masterfeats:skillfocus":4,"masterfeats:greaterskillfocus":5,"masterfeats:weaponfocus":6,"masterfeats:improvedcritical":7,"masterfeats:weaponproficiencycommoner":8,"masterfeats:greaterweaponfocus":9}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "data", "skills", "base.json"), `{
|
||||
"output": "skills.2da",
|
||||
"columns": ["Label", "Name", "Description", "Icon", "Untrained", "KeyAbility", "ArmorCheckPenalty", "AllClassesCanUse", "Category", "MaxCR", "Constant", "HostileSkill", "HideFromLevelUp"],
|
||||
@@ -2310,6 +2311,122 @@ func TestPreferredGeneratedFeatKeyReusesLegacyTLKStateKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGeneratedFeatIdentityTranslatesLegacyFamilySourceID(t *testing.T) {
|
||||
ctx := &featGeneratedContext{
|
||||
lockData: map[string]int{"feat:epicweaponfocus_club": 619},
|
||||
supplementalID: map[string]int{},
|
||||
tlkStateKeys: map[string]struct{}{},
|
||||
existingFeat: map[string]struct{}{"feat:epicweaponfocus_club": {}},
|
||||
}
|
||||
got, rowID, hasID, err := ctx.resolveGeneratedFeatIdentityBySource("greaterweaponfocus", "club", "619")
|
||||
if err != nil {
|
||||
t.Fatalf("resolveGeneratedFeatIdentityBySource: %v", err)
|
||||
}
|
||||
if got != "feat:greaterweaponfocus_club" || !hasID || rowID != 619 {
|
||||
t.Fatalf("expected translated greater weapon focus identity at id 619, got key=%q id=%d hasID=%v", got, rowID, hasID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGeneratedFeatIdentityMovesExplicitCanonicalRefToLegacyAliasID(t *testing.T) {
|
||||
ctx := &featGeneratedContext{
|
||||
lockData: map[string]int{
|
||||
"feat:greaterweaponfocus_shortspear": 1289,
|
||||
"feat:epicweaponfocus_shortspear": 627,
|
||||
},
|
||||
supplementalID: map[string]int{},
|
||||
tlkStateKeys: map[string]struct{}{},
|
||||
}
|
||||
got, rowID, hasID, err := ctx.resolveGeneratedFeatIdentityBySource("greaterweaponfocus", "shortspear", map[string]any{"id": "feat:greaterweaponfocus_shortspear"})
|
||||
if err != nil {
|
||||
t.Fatalf("resolveGeneratedFeatIdentityBySource: %v", err)
|
||||
}
|
||||
if got != "feat:greaterweaponfocus_shortspear" || !hasID || rowID != 627 {
|
||||
t.Fatalf("expected explicit canonical ref to move to legacy alias id 627, got key=%q id=%d hasID=%v", got, rowID, hasID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGeneratedFeatIdentityUsesLegacyAliasForNoSourceFamily(t *testing.T) {
|
||||
ctx := &featGeneratedContext{
|
||||
lockData: map[string]int{
|
||||
"feat:skillfocus_animalhandling": 1307,
|
||||
"feat:skillfocus_animalempathy": 34,
|
||||
"feat:greaterskillfocus_appraise": 1316,
|
||||
"feat:epicskillfocus_appraise": 588,
|
||||
"feat:epicskillfocus_animalempathy": 587,
|
||||
},
|
||||
supplementalID: map[string]int{},
|
||||
tlkStateKeys: map[string]struct{}{},
|
||||
}
|
||||
spec := familyExpansionSpec{FamilyKey: "skillfocus"}
|
||||
got, rowID, hasID, err := ctx.resolveGeneratedFeatIdentity(spec, "animalhandling", map[string]any{})
|
||||
if err != nil {
|
||||
t.Fatalf("resolveGeneratedFeatIdentity skill focus renamed skill: %v", err)
|
||||
}
|
||||
if got != "feat:skillfocus_animalhandling" || !hasID || rowID != 34 {
|
||||
t.Fatalf("expected skill focus renamed child to take legacy alias id 34, got key=%q id=%d hasID=%v", got, rowID, hasID)
|
||||
}
|
||||
|
||||
spec = familyExpansionSpec{FamilyKey: "greaterskillfocus"}
|
||||
got, rowID, hasID, err = ctx.resolveGeneratedFeatIdentity(spec, "appraise", map[string]any{})
|
||||
if err != nil {
|
||||
t.Fatalf("resolveGeneratedFeatIdentity: %v", err)
|
||||
}
|
||||
if got != "feat:greaterskillfocus_appraise" || !hasID || rowID != 588 {
|
||||
t.Fatalf("expected no-source generated family to take legacy alias id 588, got key=%q id=%d hasID=%v", got, rowID, hasID)
|
||||
}
|
||||
got, rowID, hasID, err = ctx.resolveGeneratedFeatIdentity(spec, "animalhandling", map[string]any{})
|
||||
if err != nil {
|
||||
t.Fatalf("resolveGeneratedFeatIdentity renamed skill: %v", err)
|
||||
}
|
||||
if got != "feat:greaterskillfocus_animalhandling" || !hasID || rowID != 587 {
|
||||
t.Fatalf("expected renamed no-source generated family to take legacy alias id 587, got key=%q id=%d hasID=%v", got, rowID, hasID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFamilyExpansionMovesCanonicalLockToLegacyAliasID(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
writeFeatGeneratedHarness(t, root, map[string]string{
|
||||
"greater_skill_focus.json": `{
|
||||
"family": "greater_skill_focus",
|
||||
"family_key": "greaterskillfocus",
|
||||
"template": "masterfeats:greaterskillfocus",
|
||||
"name_prefix": "Greater Skill Focus",
|
||||
"label_prefix": "FEAT_GREATER_SKILL_FOCUS",
|
||||
"constant_prefix": "FEAT_GREATER_SKILL_FOCUS",
|
||||
"child_ref_field": "REQSKILL",
|
||||
"child_source": {"dataset":"skills","predicate":"accessible"},
|
||||
"overrides": {"skills:concentration":{"ICON":"ife_greater_concentration"}}
|
||||
}` + "\n",
|
||||
}, map[string]int{
|
||||
"feat:greaterskillfocus_concentration": 1317,
|
||||
"feat:epicskillfocus_concentration": 589,
|
||||
})
|
||||
|
||||
result, err := BuildNative(testProject(root), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildNative: %v", err)
|
||||
}
|
||||
got, err := os.ReadFile(filepath.Join(result.Output2DADir, "feat.2da"))
|
||||
if err != nil {
|
||||
t.Fatalf("read feat.2da: %v", err)
|
||||
}
|
||||
text := string(got)
|
||||
if !strings.Contains(text, "589\tFEAT_GREATER_SKILL_FOCUS_CONCENTRATION") {
|
||||
t.Fatalf("expected greater skill focus to move to legacy alias id 589, got:\n%s", text)
|
||||
}
|
||||
lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "feat", "lock.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("read feat lock: %v", err)
|
||||
}
|
||||
lockText := string(lockRaw)
|
||||
if !strings.Contains(lockText, `"feat:greaterskillfocus_concentration": 589`) {
|
||||
t.Fatalf("expected canonical lock to move to legacy alias id, got:\n%s", lockText)
|
||||
}
|
||||
if strings.Contains(lockText, "epicskillfocus_concentration") {
|
||||
t.Fatalf("expected stale epic skill focus lock to be pruned, got:\n%s", lockText)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPruneRetiredGeneratedFeatTLKEntries(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "feat", "generated"))
|
||||
|
||||
Reference in New Issue
Block a user