From adffb1e11dfa05f463a2e072855b87ebae1e3d8e Mon Sep 17 00:00:00 2001 From: vickydotbat Date: Sun, 19 Jul 2026 09:25:39 +0200 Subject: [PATCH] topdata: stop hardcoding the skills dataset location The skills dataset moved into a tree (skills/core, skills/specs, ...). Resolve it data-driven instead of by name: - The required-family check no longer pins skill focus families to dataset "skills"; the family spec's child_source names the dataset. - Affinity generation and display names use the skill_focus spec's child_source dataset. - Child slugs derive from the row key's own namespace (text after the first colon) instead of assuming namespace == dataset path. - The wiki loader accepts both layouts ("skills", then "skills/core"). Co-Authored-By: Claude Fable 5 --- internal/topdata/native.go | 21 ++++++++++++++++++--- internal/topdata/topdata.go | 13 +++++++++---- internal/topdata/wiki_native.go | 14 ++++++++------ 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/internal/topdata/native.go b/internal/topdata/native.go index 644f9c4..c29a7bb 100644 --- a/internal/topdata/native.go +++ b/internal/topdata/native.go @@ -2317,6 +2317,18 @@ func (c *featGeneratedContext) familyHasExistingRows(familyKey string) bool { return false } +// skillsDatasetName returns the dataset the skill_focus family sources its +// children from. The skills dataset location is data-driven via that spec; +// "skills" is only the fallback when no spec is loaded. +func (c *featGeneratedContext) skillsDatasetName() string { + for key, spec := range c.familySpecs { + if normalizeKeyIdentity(key) == "skillfocus" && spec.ChildSource.Dataset != "" { + return spec.ChildSource.Dataset + } + } + return "skills" +} + func (c *featGeneratedContext) datasetRows(name string) (map[string]map[string]any, error) { if rows, ok := c.rowsByDataset[name]; ok { return rows, nil @@ -2555,7 +2567,10 @@ func buildFamilyExpansionGeneratedModule(path string, obj map[string]any, ctx *f if !include { continue } - slug := strings.TrimPrefix(sourceKey, spec.ChildSource.Dataset+":") + slug := sourceKey + if idx := strings.Index(slug, ":"); idx >= 0 { + slug = slug[idx+1:] + } featKey, rowID, hasID, err := ctx.resolveGeneratedFeatIdentity(spec, slug, row) if err != nil { return nil, fmt.Errorf("generated feat file %s: %w", path, err) @@ -2655,7 +2670,7 @@ func buildRacialtypesSkillAffinityModule(ctx *featGeneratedContext) (map[string] if len(grants) == 0 { return nil, nil } - skillRows, err := ctx.datasetRows("skills") + skillRows, err := ctx.datasetRows(ctx.skillsDatasetName()) if err != nil { return nil, err } @@ -2854,7 +2869,7 @@ func (c *featGeneratedContext) displayNameForGeneratedSource(dataset string, row return text } switch dataset { - case "skills": + case c.skillsDatasetName(): return displayNameForSkill(row) case "baseitems": return displayNameForBaseitem(row) diff --git a/internal/topdata/topdata.go b/internal/topdata/topdata.go index 8291c1e..e06958d 100644 --- a/internal/topdata/topdata.go +++ b/internal/topdata/topdata.go @@ -1867,8 +1867,10 @@ func validateGeneratedFeatFamilies(dataDir string, report *ValidationReport) { } required := []requiredFeatFamily{ - {FamilyKey: "skillfocus", Dataset: "skills", Predicate: "accessible"}, - {FamilyKey: "greaterskillfocus", Dataset: "skills", Predicate: "accessible"}, + // skill families: the source dataset is data-driven (family spec), only + // the accessibility predicate is required + {FamilyKey: "skillfocus", Predicate: "accessible"}, + {FamilyKey: "greaterskillfocus", Predicate: "accessible"}, {FamilyKey: "weaponfocus", Dataset: "baseitems", Column: "WeaponFocusFeat"}, {FamilyKey: "weaponspecialization", Dataset: "baseitems", Column: "WeaponSpecializationFeat"}, {FamilyKey: "improvedcritical", Dataset: "baseitems", Column: "WeaponImprovedCriticalFeat"}, @@ -1882,7 +1884,7 @@ func validateGeneratedFeatFamilies(dataDir string, report *ValidationReport) { if !ok { continue } - if spec.ChildSource.Dataset != requirement.Dataset || + if (requirement.Dataset != "" && spec.ChildSource.Dataset != requirement.Dataset) || spec.ChildSource.Column != requirement.Column || spec.ChildSource.Predicate != requirement.Predicate { report.Diagnostics = append(report.Diagnostics, Diagnostic{ @@ -2081,7 +2083,10 @@ func validateGeneratedFeatFamilyCompleteness(path string, spec familyExpansionSp } if include { if familyAllowlist { - slug := strings.TrimPrefix(sourceKey, spec.ChildSource.Dataset+":") + slug := sourceKey + if idx := strings.Index(slug, ":"); idx >= 0 { + slug = slug[idx+1:] + } featKey, _, _, err := ctx.resolveGeneratedFeatIdentity(spec, slug, row) if err != nil { report.Diagnostics = append(report.Diagnostics, Diagnostic{ diff --git a/internal/topdata/wiki_native.go b/internal/topdata/wiki_native.go index 1169bec..dc43654 100644 --- a/internal/topdata/wiki_native.go +++ b/internal/topdata/wiki_native.go @@ -569,11 +569,13 @@ func loadWikiContext(dataDir, sourceDir string) (*wikiContext, error) { if err != nil { return nil, err } - loadBase := func(name string) (nativeCollectedDataset, bool, error) { - for _, dataset := range datasets { - if dataset.Name == name { - collected, err := collectNativeDataset(dataset) - return collected, true, err + loadBase := func(names ...string) (nativeCollectedDataset, bool, error) { + for _, name := range names { + for _, dataset := range datasets { + if dataset.Name == name { + collected, err := collectNativeDataset(dataset) + return collected, true, err + } } } return nativeCollectedDataset{}, false, nil @@ -583,7 +585,7 @@ func loadWikiContext(dataDir, sourceDir string) (*wikiContext, error) { if err != nil { return nil, err } - skillDataset, skillOK, err := loadBase("skills") + skillDataset, skillOK, err := loadBase("skills", "skills/core") if err != nil { return nil, err }