From bc8fa3a6fec27ba40575eed94012ba42aa6bb275 Mon Sep 17 00:00:00 2001 From: vickydotbat Date: Sun, 19 Jul 2026 07:33:19 +0000 Subject: [PATCH] topdata: stop hardcoding the skills dataset location (#43) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sow-topdata moved skills into a tree (skills/core, skills/specs, skillcats, skillspecranks), which broke validate/build: the generated feat families hardcoded dataset "skills". Resolution is now data-driven: the family spec's child_source names the dataset, child slugs come from the row key's own namespace, and the wiki loader accepts both the flat and tree layouts (topdata main still uses the flat one until the overhaul merges). Verified against the skill-grouping-overhaul topdata branch: validate/build/wiki all clean with zero lock churn, focus feats stay on their adopted rows. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: https://git.westgate.pw/ShadowsOverWestgate/sow-tools/pulls/43 Co-authored-by: vickydotbat Co-committed-by: vickydotbat --- 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 }