Global json adjustments

This commit is contained in:
2026-06-05 16:35:14 +02:00
parent c672a1ff33
commit dfdc47c2dc
3 changed files with 932 additions and 17 deletions
+411
View File
@@ -180,6 +180,69 @@ func TestValidateProjectRejectsInvalidModuleColumnExpansionFile(t *testing.T) {
}
}
func TestValidateProjectRejectsInvalidGlobalJSONDefaults(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "spells"))
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "base.json"), `{
"output": "spells.2da",
"columns": ["Label", "ImpactScript"],
"rows": []
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "lock.json"), "{}\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "global.json"), `{
"defaults": [
{"match": {"source": "entries"}, "values": {"ImpactScript": {"format": "ss_{missing}"}}},
{"match": {"source": "unknown"}, "values": {"ImpactScript": "x"}},
{"match": {"source": "entries", "extra": "bad"}, "values": {"ImpactScript": "x"}},
{"match": {"source": "entries"}, "values": {"NotAColumn": "x"}},
{"match": {"source": "entries"}}
]
}`+"\n")
report := ValidateProject(testProject(root))
if !report.HasErrors() {
t.Fatal("expected invalid global defaults to fail validation")
}
text := diagnosticsText(report.Diagnostics)
for _, want := range []string{
"default 0 values.ImpactScript format token {missing} is not supported",
"default 1 match.source is not supported",
"default 2 match.extra is not supported",
"default 3 values.NotAColumn is not a known column",
"default 4 values is required",
} {
if !strings.Contains(text, want) {
t.Fatalf("expected diagnostic %q, got:\n%s", want, text)
}
}
}
func TestValidateProjectRejectsGlobalJSONDefaultsWithoutBaseDataset(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "classes", "skills"))
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
writeFile(t, filepath.Join(root, "topdata", "data", "classes", "skills", "global.json"), `{
"defaults": [
{"match": "all", "values": {"ClassSkill": 1}}
]
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "classes", "skills", "fighter.json"), `{
"columns": ["SkillLabel", "ClassSkill"],
"rows": [
{"id": 0, "SkillLabel": "Concentration", "ClassSkill": 1}
]
}`+"\n")
report := ValidateProject(testProject(root))
if !report.HasErrors() {
t.Fatal("expected global defaults without a base dataset to fail validation")
}
if !diagnosticsContain(report.Diagnostics, "global defaults require a sibling base.json dataset") {
t.Fatalf("expected unsupported defaults diagnostic, got:\n%s", diagnosticsText(report.Diagnostics))
}
}
func TestBuildNativeEncodesConfiguredPackedHexLists(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "modules"))
@@ -678,6 +741,54 @@ func TestBuildNativeAppliesConfiguredValueDefaults(t *testing.T) {
}
}
func TestBuildNativeGlobalJSONDefaultsReplaceConfiguredValueDefaults(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "racialtypes", "core"))
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
writeFile(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "base.json"), `{
"output": "racialtypes.2da",
"columns": ["Label", "ECL", "DefaultScale"],
"rows": [
{"id": 6, "key": "racialtypes:human", "Label": "Human"},
{"id": 7, "key": "racialtypes:elf", "Label": "Elf", "ECL": null, "DefaultScale": null},
{"id": 8, "key": "racialtypes:drow", "Label": "Drow", "ECL": 2, "DefaultScale": "0.95"}
]
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "lock.json"), `{"racialtypes:human":6,"racialtypes:elf":7,"racialtypes:drow":8}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "global.json"), `{
"defaults": [
{
"match": "all",
"values": {
"ECL": 0,
"DefaultScale": 1
}
}
]
}`+"\n")
proj := testProject(root)
proj.Config.TopData.ReferenceBuilder = ""
result, err := BuildNativeWithOptions(proj, NativeBuildOptions{BuildWiki: false}, nil)
if err != nil {
t.Fatalf("BuildNativeWithOptions failed: %v", err)
}
raw, err := os.ReadFile(filepath.Join(result.Output2DADir, "racialtypes.2da"))
if err != nil {
t.Fatalf("read racialtypes.2da: %v", err)
}
got := string(raw)
for _, want := range []string{
"6\tHuman\t0\t1\n",
"7\tElf\t0\t1\n",
"8\tDrow\t2\t0.95\n",
} {
if !strings.Contains(got, want) {
t.Fatalf("expected racialtypes.2da to contain %q, got:\n%s", want, got)
}
}
}
func TestValidateProjectRejectsInvalidConfiguredPackedHexList(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "modules"))
@@ -11754,6 +11865,58 @@ func TestBuildSupportsCanonicalSpells(t *testing.T) {
}
}
func TestBuildCanonicalSpellsImpactScriptsComeFromGlobalDefaults(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "spells", "modules"))
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "base.json"), `{
"output": "spells.2da",
"columns": ["Label", "ImpactScript"],
"rows": [
{"id": 0, "key": "spells:acid_fog", "Label": "AcidFog", "ImpactScript": "NW_S0_AcidFog"}
]
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "lock.json"), `{
"spells:acid_fog": 0,
"spells:special_attacks": 840
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "modules", "specialattacks.json"), `{
"entries": {
"spells:special_attacks": {
"Label": "SpecialAttacks"
}
}
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "global.json"), `{
"defaults": [
{
"match": {"source": "entries"},
"values": {
"ImpactScript": {"format": "ss_{id}"}
}
}
]
}`+"\n")
proj := testProject(root)
proj.Config.TopData.ReferenceBuilder = ""
result, err := BuildNativeWithOptions(proj, NativeBuildOptions{BuildWiki: false}, nil)
if err != nil {
t.Fatalf("BuildNative failed: %v", err)
}
raw, err := os.ReadFile(filepath.Join(result.Output2DADir, "spells.2da"))
if err != nil {
t.Fatalf("read spells.2da: %v", err)
}
text := string(raw)
if !strings.Contains(text, "0\tAcidFog\tNW_S0_AcidFog\n") {
t.Fatalf("expected base ImpactScript to be preserved, got:\n%s", text)
}
if !strings.Contains(text, "840\tSpecialAttacks\tss_840\n") {
t.Fatalf("expected global default generated ImpactScript, got:\n%s", text)
}
}
func TestBuildCanonicalSpellsMatchesReference(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data"))
@@ -14019,6 +14182,232 @@ func TestBuildNativeAppliesGlobalJSONAllOverrideToBaseDataset(t *testing.T) {
}
}
func TestBuildNativeAppliesGlobalJSONDefaultsToEntryRows(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "spells", "modules"))
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "base.json"), `{
"output": "spells.2da",
"columns": ["Label", "ImpactScript"],
"rows": [
{"id": 0, "key": "spells:base_missing", "Label": "BaseMissing"},
{"id": 1, "key": "spells:base_null", "Label": "BaseNull", "ImpactScript": null}
]
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "lock.json"), `{
"spells:base_missing": 0,
"spells:base_null": 1,
"spells:new_missing": 10,
"spells:new_null": 11,
"spells:new_stars": 12,
"spells:new_empty": 13,
"spells:new_explicit": 14
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "modules", "new.json"), `{
"entries": {
"spells:new_missing": {"Label": "NewMissing"},
"spells:new_null": {"Label": "NewNull", "ImpactScript": null},
"spells:new_stars": {"Label": "NewStars", "ImpactScript": "****"},
"spells:new_empty": {"Label": "NewEmpty", "ImpactScript": ""},
"spells:new_explicit": {"Label": "NewExplicit", "ImpactScript": "custom_spell_script"}
}
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "global.json"), `{
"defaults": [
{
"match": {"source": "entries"},
"values": {
"ImpactScript": {"format": "ss_{id}"}
}
}
]
}`+"\n")
proj := testProject(root)
proj.Config.TopData.ReferenceBuilder = ""
result, err := BuildNativeWithOptions(proj, NativeBuildOptions{BuildWiki: false}, nil)
if err != nil {
t.Fatalf("BuildNative failed: %v", err)
}
got, err := os.ReadFile(filepath.Join(result.Output2DADir, "spells.2da"))
if err != nil {
t.Fatalf("read spells.2da: %v", err)
}
text := string(got)
for _, want := range []string{
"0\tBaseMissing\t****\n",
"1\tBaseNull\t****\n",
"10\tNewMissing\tss_10\n",
"11\tNewNull\tss_11\n",
"12\tNewStars\tss_12\n",
"13\tNewEmpty\tss_13\n",
"14\tNewExplicit\tcustom_spell_script\n",
} {
if !strings.Contains(text, want) {
t.Fatalf("expected spells.2da to contain %q, got:\n%s", want, text)
}
}
}
func TestBuildNativeGlobalJSONDefaultsUseDistinctProvenanceForDuplicateIDs(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "spells", "modules"))
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "base.json"), `{
"output": "spells.2da",
"columns": ["Label", "ImpactScript"],
"rows": [
{"id": 10, "key": "spells:base", "Label": "BaseDuplicate"},
{"id": 10, "key": "spells:entry", "Label": "EntryDuplicate"}
]
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "modules", "entry.json"), `{
"entries": {
"spells:entry": {"Label": "EntryDuplicate"}
}
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "global.json"), `{
"defaults": [
{
"match": {"source": "entries"},
"values": {
"ImpactScript": {"format": "ss_{key}"}
}
}
]
}`+"\n")
proj := testProject(root)
proj.Config.TopData.ReferenceBuilder = ""
result, err := BuildNativeWithOptions(proj, NativeBuildOptions{BuildWiki: false}, nil)
if err != nil {
t.Fatalf("BuildNative failed: %v", err)
}
raw, err := os.ReadFile(filepath.Join(result.Output2DADir, "spells.2da"))
if err != nil {
t.Fatalf("read spells.2da: %v", err)
}
got := string(raw)
for _, want := range []string{
"10\tBaseDuplicate\t****\n",
"10\tEntryDuplicate\tss_spells:entry\n",
} {
if !strings.Contains(got, want) {
t.Fatalf("expected spells.2da to contain %q, got:\n%s", want, got)
}
}
}
func TestBuildNativeGlobalJSONDefaultsPreserveProvenanceForDisplacedRows(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "spells", "modules"))
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "base.json"), `{
"output": "spells.2da",
"columns": ["Label", "ImpactScript"],
"rows": [
{"id": 0, "key": "spells:base", "Label": "BaseMoved"}
]
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "lock.json"), `{
"spells:base": 0,
"spells:entry": 10
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "modules", "entry.json"), `{
"entries": {
"spells:entry": {"Label": "EntryDisplaced"}
}
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "spells", "global.json"), `{
"overrides": [
{"id": 0, "key": "spells:entry"}
],
"defaults": [
{
"match": {"source": "entries"},
"values": {
"ImpactScript": "entry_default"
}
}
]
}`+"\n")
proj := testProject(root)
proj.Config.TopData.ReferenceBuilder = ""
result, err := BuildNativeWithOptions(proj, NativeBuildOptions{BuildWiki: false}, nil)
if err != nil {
t.Fatalf("BuildNative failed: %v", err)
}
raw, err := os.ReadFile(filepath.Join(result.Output2DADir, "spells.2da"))
if err != nil {
t.Fatalf("read spells.2da: %v", err)
}
got := string(raw)
for _, want := range []string{
"0\tBaseMoved\t****\n",
"10\tEntryDisplaced\tentry_default\n",
} {
if !strings.Contains(got, want) {
t.Fatalf("expected spells.2da to contain %q, got:\n%s", want, got)
}
}
}
func TestBuildNativeGlobalJSONDefaultsSupportLiteralAllRows(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "racialtypes", "core"))
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
writeFile(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "base.json"), `{
"output": "racialtypes.2da",
"columns": ["Label", "ECL", "DefaultScale"],
"rows": [
{"id": 6, "key": "racialtypes:human", "Label": "Human"},
{"id": 7, "key": "racialtypes:elf", "Label": "Elf", "ECL": null, "DefaultScale": null},
{"id": 8, "key": "racialtypes:drow", "Label": "Drow", "ECL": 2, "DefaultScale": "0.95"}
]
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "lock.json"), `{
"racialtypes:human": 6,
"racialtypes:elf": 7,
"racialtypes:drow": 8
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "global.json"), `{
"defaults": [
{
"match": "all",
"values": {
"ECL": 0,
"DefaultScale": 1
}
}
]
}`+"\n")
proj := testProject(root)
proj.Config.TopData.ReferenceBuilder = ""
result, err := BuildNativeWithOptions(proj, NativeBuildOptions{BuildWiki: false}, nil)
if err != nil {
t.Fatalf("BuildNative failed: %v", err)
}
raw, err := os.ReadFile(filepath.Join(result.Output2DADir, "racialtypes.2da"))
if err != nil {
t.Fatalf("read racialtypes.2da: %v", err)
}
got := string(raw)
for _, want := range []string{
"6\tHuman\t0\t1\n",
"7\tElf\t0\t1\n",
"8\tDrow\t2\t0.95\n",
} {
if !strings.Contains(got, want) {
t.Fatalf("expected racialtypes.2da to contain %q, got:\n%s", want, got)
}
}
}
func TestBuildNativeAppliesRecursiveGlobalJSONPlainDatasetInjections(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "classes", "skills", "baseclasses"))
@@ -14089,6 +14478,28 @@ func TestBuildNativeAppliesRecursiveGlobalJSONPlainDatasetInjections(t *testing.
}
}
func TestBuildNativeRejectsGlobalDefaultsOnPlainDataset(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "classes", "skills"))
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
writeFile(t, filepath.Join(root, "topdata", "data", "classes", "skills", "global.json"), `{
"defaults": [
{"match": "all", "values": {"ClassSkill": 1}}
]
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "classes", "skills", "fighter.json"), `{
"columns": ["SkillLabel", "ClassSkill"],
"rows": [
{"id": 0, "SkillLabel": "Concentration", "ClassSkill": 1}
]
}`+"\n")
_, err := BuildNativeWithOptions(testProject(root), NativeBuildOptions{BuildWiki: false}, nil)
if err == nil || !strings.Contains(err.Error(), "defaults") || !strings.Contains(err.Error(), "base.json dataset") {
t.Fatalf("expected plain dataset defaults build error, got %v", err)
}
}
func TestBuildNativeExtendsConfiguredPlainRowsByRepeatingLastLevel(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "classes", "spellsgained"))