From 28d487565a932e8216309a77ddb052c60998a532 Mon Sep 17 00:00:00 2001 From: vickydotbat Date: Tue, 26 May 2026 14:18:04 +0200 Subject: [PATCH] Fix alignment/id encoding --- README.md | 5 ++ internal/topdata/native.go | 10 ++++ internal/topdata/topdata_test.go | 78 ++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) diff --git a/README.md b/README.md index 1708f55..9ab57fc 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,11 @@ toolkit defaults, YAML values, legacy status, active environment overrides, and provenance. Sensitive override values such as tokens are reported as set without printing the secret. +Topdata value encodings under `topdata.value_encodings` are matched by dataset +and column. Set `dataset: "*"` to apply an encoding to the same column name in +any native topdata dataset; an exact dataset rule takes precedence over the +wildcard rule for that column. + Common configurable defaults: ```yaml diff --git a/internal/topdata/native.go b/internal/topdata/native.go index 421ce51..b08af68 100644 --- a/internal/topdata/native.go +++ b/internal/topdata/native.go @@ -420,6 +420,16 @@ func applyTopDataValueEncodings(datasets []nativeDataset, encodings []project.To } out := append([]nativeDataset(nil), datasets...) for index := range out { + for _, encoding := range encodings { + dataset := filepath.ToSlash(strings.TrimSpace(encoding.Dataset)) + if dataset != "*" { + continue + } + if out[index].ValueEncodings == nil { + out[index].ValueEncodings = map[string]project.TopDataValueEncodingConfig{} + } + out[index].ValueEncodings[strings.ToLower(strings.TrimSpace(encoding.Column))] = encoding + } for _, encoding := range encodings { if filepath.ToSlash(strings.TrimSpace(encoding.Dataset)) != out[index].Name { continue diff --git a/internal/topdata/topdata_test.go b/internal/topdata/topdata_test.go index 0459210..4f3b91a 100644 --- a/internal/topdata/topdata_test.go +++ b/internal/topdata/topdata_test.go @@ -348,6 +348,84 @@ func TestBuildNativeEncodesConfiguredIDHexLists(t *testing.T) { } } +func TestBuildNativeAppliesWildcardConfiguredValueEncodings(t *testing.T) { + root := testProjectRoot(t) + mkdirAll(t, filepath.Join(root, "topdata", "data", "feat")) + mkdirAll(t, filepath.Join(root, "topdata", "data", "classes", "core", "modules")) + mkdirAll(t, filepath.Join(root, "topdata", "data", "baseitems", "modules")) + writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") + writeFile(t, filepath.Join(root, "topdata", "data", "feat", "base.json"), `{ + "output": "feat.2da", + "columns": ["LABEL"], + "rows": [ + {"id": 2, "key": "feat:weapon_proficiency_simple", "LABEL": "WeaponProfSimple"}, + {"id": 9, "key": "feat:weapon_proficiency_martial", "LABEL": "WeaponProfMartial"} + ] +}`+"\n") + writeFile(t, filepath.Join(root, "topdata", "data", "feat", "lock.json"), `{ + "feat:weapon_proficiency_simple": 2, + "feat:weapon_proficiency_martial": 9 +}`+"\n") + writeFile(t, filepath.Join(root, "topdata", "data", "classes", "core", "base.json"), `{ + "output": "classes.2da", + "columns": ["Label", "PreferredAlignments"], + "rows": [ + {"id": 0, "key": "classes:barbarian", "Label": "Barbarian"} + ] +}`+"\n") + writeFile(t, filepath.Join(root, "topdata", "data", "classes", "core", "lock.json"), `{"classes:barbarian":0}`+"\n") + writeFile(t, filepath.Join(root, "topdata", "data", "classes", "core", "modules", "10_barbarian.json"), `{ + "overrides": [ + { + "key": "classes:barbarian", + "PreferredAlignments": {"alignments": ["ng", "cg", "tn", "cn", "ne", "ce"]} + } + ] +}`+"\n") + writeFile(t, filepath.Join(root, "topdata", "data", "baseitems", "base.json"), `{ + "output": "baseitems.2da", + "columns": ["Label", "ProficiencyFeats"], + "rows": [ + {"id": 42, "key": "baseitems:testclub", "Label": "Test Club"} + ] +}`+"\n") + writeFile(t, filepath.Join(root, "topdata", "data", "baseitems", "lock.json"), `{"baseitems:testclub":42}`+"\n") + writeFile(t, filepath.Join(root, "topdata", "data", "baseitems", "modules", "10_testclub.json"), `{ + "overrides": [ + { + "key": "baseitems:testclub", + "ProficiencyFeats": {"ids": ["feat:weapon_proficiency_simple", "feat:weapon_proficiency_martial"]} + } + ] +}`+"\n") + + proj := testProject(root) + proj.Config.TopData.ValueEncodings = []project.TopDataValueEncodingConfig{ + {Dataset: "classes/core", Column: "PreferredAlignments", Mode: "alignment_hex_list", Min: 0, Max: 31, HexWidth: 3}, + {Dataset: "*", Column: "PreferredAlignments", Mode: "alignment_hex_list", Min: 0, Max: 31, HexWidth: 2}, + {Dataset: "*", Column: "ProficiencyFeats", Mode: "id_hex_list", Min: 0, Max: 65535, HexWidth: 4}, + } + + result, err := BuildNativeWithOptions(proj, NativeBuildOptions{BuildWiki: false}, nil) + if err != nil { + t.Fatalf("BuildNativeWithOptions failed: %v", err) + } + classesRaw, err := os.ReadFile(filepath.Join(result.Output2DADir, "classes.2da")) + if err != nil { + t.Fatalf("read classes.2da: %v", err) + } + if got := string(classesRaw); !strings.Contains(got, "0x00900C001005011014") { + t.Fatalf("expected wildcard alignment encoding in classes.2da, got:\n%s", got) + } + baseitemsRaw, err := os.ReadFile(filepath.Join(result.Output2DADir, "baseitems.2da")) + if err != nil { + t.Fatalf("read baseitems.2da: %v", err) + } + if got := string(baseitemsRaw); !strings.Contains(got, "0x00020009") { + t.Fatalf("expected wildcard id encoding in baseitems.2da, got:\n%s", got) + } +} + func TestBuildNativeAppliesConfiguredValueDefaults(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "modules"))