diff --git a/CONFIGURATION_HARDENING_AUDIT.md b/CONFIGURATION_HARDENING_AUDIT.md index 1bac2ab..bfb9c4a 100644 --- a/CONFIGURATION_HARDENING_AUDIT.md +++ b/CONFIGURATION_HARDENING_AUDIT.md @@ -41,7 +41,7 @@ repository-specific YAML: - Canonical GFF JSON shape. - ERF/MOD/HAK binary serialization. -- Supported autogen algorithms: `parts_rows`, `head_visualeffects`, +- Supported autogen algorithms: `parts_rows`, `accessory_visualeffects`, `trailing_numeric_suffix`, `model_stem`, and `first_path_segment`. - Native topdata dataset internals and generated JSON dataset formats. - NWN validator resource-reference checks. These are candidates for future diff --git a/CONFIGURATION_HARDENING_CONTRACT.md b/CONFIGURATION_HARDENING_CONTRACT.md index 9fc675c..9c7f8d3 100644 --- a/CONFIGURATION_HARDENING_CONTRACT.md +++ b/CONFIGURATION_HARDENING_CONTRACT.md @@ -391,7 +391,7 @@ Hardening direction: - Released autogen manifest discovery derives a Shadows Over Westgate assets repo spec from git remote and environment overrides. - Supported autogen modes are hardcoded: - `parts_rows` - - `head_visualeffects` + - `accessory_visualeffects` - Supported derive kinds are hardcoded: - `trailing_numeric_suffix` - `model_stem` diff --git a/README.md b/README.md index a90b14f..0485a82 100644 --- a/README.md +++ b/README.md @@ -410,9 +410,9 @@ discovered model stems from the configured include globs to the selected `cachedmodels` dataset without `.mdl` extensions and skips models already present in authored rows. -For `head_visualeffects` autogen, producers can publish the group-to-row policy -in `autogen.producers[].head_visualeffects`, and consumers can override or -extend it in `autogen.consumers[].head_visualeffects`. The toolkit uses +For `accessory_visualeffects` autogen, producers can publish the group-to-row policy +in `autogen.producers[].accessory_visualeffects`, and consumers can override or +extend it in `autogen.consumers[].accessory_visualeffects`. The toolkit uses compatibility defaults for older configs and manifests, but YAML can own the group token source, category source, delimiter, case policy, legacy group mapping, model stem prefixes to strip, key/label formats, model column, and diff --git a/internal/pipeline/pipeline_test.go b/internal/pipeline/pipeline_test.go index d3ae836..cf75a57 100644 --- a/internal/pipeline/pipeline_test.go +++ b/internal/pipeline/pipeline_test.go @@ -2198,7 +2198,7 @@ func TestBuildHAKsWritesConfiguredHeadVisualeffectsAutogenManifest(t *testing.T) "autogen": { "producers": [ { - "id": "head_visualeffects", + "id": "accessory_visualeffects", "root": "vfxs", "include": ["chest_accessories/**/*.mdl", "head_accessories/**/*.mdl", "head_decorations/**/*.mdl", "head_features/**/*.mdl"], "derive": { @@ -2210,7 +2210,7 @@ func TestBuildHAKsWritesConfiguredHeadVisualeffectsAutogenManifest(t *testing.T) "asset_name": "sow-head-vfx-manifest.json", "cache_name": "sow-head-vfx-manifest.json" }, - "head_visualeffects": { + "accessory_visualeffects": { "groups": { "head_accessories": { "prefix": "head_accessories", @@ -2266,12 +2266,12 @@ func TestBuildHAKsWritesConfiguredHeadVisualeffectsAutogenManifest(t *testing.T) } text := string(headRaw) for _, want := range []string{ - `"id": "head_visualeffects"`, + `"id": "accessory_visualeffects"`, `"group": "chest_accessories"`, `"model_stem": "tfx_sash"`, `"group": "head_accessories"`, `"model_stem": "hfx_bandana"`, - `"head_visualeffects": {`, + `"accessory_visualeffects": {`, `"model_columns": [`, `"Imp_Root_H_Node"`, `"group": "head_decorations"`, @@ -2305,7 +2305,7 @@ func TestBuildHAKsWritesFolderCategoryInHeadVisualeffectsAutogenManifest(t *test "autogen": { "producers": [ { - "id": "head_visualeffects", + "id": "accessory_visualeffects", "root": "vfxs", "include": ["head_features/**/*.mdl"], "derive": { @@ -2317,7 +2317,7 @@ func TestBuildHAKsWritesFolderCategoryInHeadVisualeffectsAutogenManifest(t *test "asset_name": "sow-head-vfx-manifest.json", "cache_name": "sow-head-vfx-manifest.json" }, - "head_visualeffects": { + "accessory_visualeffects": { "groups": { "head_features": { "model_columns": ["Imp_HeadCon_Node", "Imp_Root_H_Node"] diff --git a/internal/project/project.go b/internal/project/project.go index 023e38b..0560c02 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -368,7 +368,7 @@ type AutogenProducerConfig struct { Include []string `json:"include" yaml:"include"` Derive AutogenDeriveConfig `json:"derive" yaml:"derive"` Manifest AutogenManifestConfig `json:"manifest" yaml:"manifest"` - HeadVisualeffects HeadVisualeffectsConfig `json:"head_visualeffects" yaml:"head_visualeffects"` + HeadVisualeffects HeadVisualeffectsConfig `json:"accessory_visualeffects" yaml:"accessory_visualeffects"` } type AutogenConsumerConfig struct { @@ -381,7 +381,7 @@ type AutogenConsumerConfig struct { Include []string `json:"include" yaml:"include"` Derive AutogenDeriveConfig `json:"derive" yaml:"derive"` PartsRows PartsRowsConfig `json:"parts_rows" yaml:"parts_rows"` - HeadVisualeffects HeadVisualeffectsConfig `json:"head_visualeffects" yaml:"head_visualeffects"` + HeadVisualeffects HeadVisualeffectsConfig `json:"accessory_visualeffects" yaml:"accessory_visualeffects"` Manifest AutogenManifestConfig `json:"manifest" yaml:"manifest"` LocalOverrideRoot string `json:"local_override_root,omitempty" yaml:"local_override_root,omitempty"` } @@ -1699,7 +1699,7 @@ func validateAutogenConfig(cfg AutogenConfig) []error { failures = append(failures, validateAutogenDeriveConfig(fieldPrefix+".derive", producer.Derive)...) failures = append(failures, validateAutogenManifestConfig(fieldPrefix+".manifest", producer.Manifest)...) if headVisualeffectsConfigConfigured(producer.HeadVisualeffects) { - failures = append(failures, validateHeadVisualeffectsConfig(fieldPrefix+".head_visualeffects", producer.HeadVisualeffects)...) + failures = append(failures, validateHeadVisualeffectsConfig(fieldPrefix+".accessory_visualeffects", producer.HeadVisualeffects)...) } } @@ -1732,7 +1732,7 @@ func validateAutogenConfig(cfg AutogenConfig) []error { } } switch strings.TrimSpace(consumer.Mode) { - case "parts_rows", "head_visualeffects": + case "parts_rows", "accessory_visualeffects": case "": failures = append(failures, fmt.Errorf("%s.mode is required", fieldPrefix)) default: @@ -1746,8 +1746,8 @@ func validateAutogenConfig(cfg AutogenConfig) []error { } failures = append(failures, validateGlobList(fieldPrefix+".include", consumer.Include)...) failures = append(failures, validateAutogenDeriveConfig(fieldPrefix+".derive", consumer.Derive)...) - if strings.TrimSpace(consumer.Mode) == "head_visualeffects" && headVisualeffectsConfigConfigured(consumer.HeadVisualeffects) { - failures = append(failures, validateHeadVisualeffectsConfig(fieldPrefix+".head_visualeffects", consumer.HeadVisualeffects)...) + if strings.TrimSpace(consumer.Mode) == "accessory_visualeffects" && headVisualeffectsConfigConfigured(consumer.HeadVisualeffects) { + failures = append(failures, validateHeadVisualeffectsConfig(fieldPrefix+".accessory_visualeffects", consumer.HeadVisualeffects)...) } failures = append(failures, validateAutogenManifestConfig(fieldPrefix+".manifest", consumer.Manifest)...) } diff --git a/internal/project/project_test.go b/internal/project/project_test.go index f0365fb..4d6f411 100644 --- a/internal/project/project_test.go +++ b/internal/project/project_test.go @@ -1327,8 +1327,8 @@ func TestValidateLayoutRejectsInvalidAutogenConsumerConfig(t *testing.T) { Autogen: AutogenConfig{ Consumers: []AutogenConsumerConfig{ { - ID: "head_visualeffects", - Producer: "head_visualeffects", + ID: "accessory_visualeffects", + Producer: "accessory_visualeffects", Dataset: "visualeffects", Mode: "unsupported", Root: "vfxs", @@ -1373,10 +1373,10 @@ func TestValidateLayoutRejectsInvalidHeadVisualeffectsConfig(t *testing.T) { Autogen: AutogenConfig{ Consumers: []AutogenConsumerConfig{ { - ID: "head_visualeffects", - Producer: "head_visualeffects", + ID: "accessory_visualeffects", + Producer: "accessory_visualeffects", Dataset: "visualeffects", - Mode: "head_visualeffects", + Mode: "accessory_visualeffects", Root: "vfxs", Include: []string{"head_jewels/**/*.mdl"}, Derive: AutogenDeriveConfig{Kind: "model_stem", GroupFrom: "first_path_segment"}, @@ -1405,10 +1405,10 @@ func TestValidateLayoutRejectsInvalidHeadVisualeffectsConfig(t *testing.T) { t.Fatal("expected head visualeffects validation error") } for _, needle := range []string{ - "autogen.consumers[0].head_visualeffects.groups[head_jewels].prefix", - "autogen.consumers[0].head_visualeffects.groups[head_jewels].model_columns[0]", - "autogen.consumers[0].head_visualeffects.strip_model_prefixes[0]", - "autogen.consumers[0].head_visualeffects.model_column", + "autogen.consumers[0].accessory_visualeffects.groups[head_jewels].prefix", + "autogen.consumers[0].accessory_visualeffects.groups[head_jewels].model_columns[0]", + "autogen.consumers[0].accessory_visualeffects.strip_model_prefixes[0]", + "autogen.consumers[0].accessory_visualeffects.model_column", } { if !strings.Contains(err.Error(), needle) { t.Fatalf("expected validation error to mention %s, got %v", needle, err) @@ -1429,10 +1429,10 @@ func TestValidateLayoutRejectsInvalidHeadVisualeffectsNamingConfig(t *testing.T) Autogen: AutogenConfig{ Consumers: []AutogenConsumerConfig{ { - ID: "head_visualeffects", - Producer: "head_visualeffects", + ID: "accessory_visualeffects", + Producer: "accessory_visualeffects", Dataset: "visualeffects", - Mode: "head_visualeffects", + Mode: "accessory_visualeffects", Root: "vfxs", Include: []string{"head_features/**/*.mdl"}, Derive: AutogenDeriveConfig{Kind: "model_stem", GroupFrom: "first_path_segment"}, @@ -1461,10 +1461,10 @@ func TestValidateLayoutRejectsInvalidHeadVisualeffectsNamingConfig(t *testing.T) t.Fatal("expected head visualeffects naming validation error") } for _, needle := range []string{ - "autogen.consumers[0].head_visualeffects.group_token_source", - "autogen.consumers[0].head_visualeffects.category_from", - "autogen.consumers[0].head_visualeffects.delimiter", - "autogen.consumers[0].head_visualeffects.case", + "autogen.consumers[0].accessory_visualeffects.group_token_source", + "autogen.consumers[0].accessory_visualeffects.category_from", + "autogen.consumers[0].accessory_visualeffects.delimiter", + "autogen.consumers[0].accessory_visualeffects.case", } { if !strings.Contains(err.Error(), needle) { t.Fatalf("expected validation error to mention %s, got %v", needle, err) @@ -1482,17 +1482,17 @@ paths: source: src autogen: consumers: - - id: head_visualeffects - producer: head_visualeffects + - id: accessory_visualeffects + producer: accessory_visualeffects dataset: visualeffects - mode: head_visualeffects + mode: accessory_visualeffects root: vfxs include: - head_features/**/*.mdl derive: kind: model_stem group_from: first_path_segment - head_visualeffects: + accessory_visualeffects: groups: head_features: model_columns: diff --git a/internal/topdata/autogen.go b/internal/topdata/autogen.go index 1c3cb99..2d0016f 100644 --- a/internal/topdata/autogen.go +++ b/internal/topdata/autogen.go @@ -21,7 +21,7 @@ type autogenManifest struct { Repo string `json:"repo"` Ref string `json:"ref"` GeneratedAt string `json:"generated_at"` - HeadVisualeffects *project.HeadVisualeffectsConfig `json:"head_visualeffects,omitempty"` + HeadVisualeffects *project.HeadVisualeffectsConfig `json:"accessory_visualeffects,omitempty"` Entries []autogenManifestEntry `json:"entries"` } @@ -76,7 +76,7 @@ func applyAutogenConsumers(p *project.Project, collected []nativeCollectedDatase switch consumer.Mode { case "parts_rows": result = augmentWithAutogeneratedParts(result, autogenPartsInventory(entries)) - case "head_visualeffects": + case "accessory_visualeffects": result, err = augmentWithAutogeneratedHeadVisualeffects(result, entries, consumer, manifest.HeadVisualeffects) if err != nil { return nil, err @@ -95,7 +95,7 @@ func autogenConsumerTargetsCollectedDataset(collected []nativeCollectedDataset, if isAutogenEligiblePartsDataset(dataset) { return true } - case "head_visualeffects": + case "accessory_visualeffects": if dataset.Dataset.Name == "visualeffects" { return true } diff --git a/internal/topdata/topdata_test.go b/internal/topdata/topdata_test.go index 8905fc9..307a6bb 100644 --- a/internal/topdata/topdata_test.go +++ b/internal/topdata/topdata_test.go @@ -7338,10 +7338,10 @@ func TestApplyAutogenConsumersAugmentsHeadVisualeffectsFromLocalOverride(t *test p := testProject(root) p.Config.Autogen.Consumers = []project.AutogenConsumerConfig{ { - ID: "head_visualeffects", - Producer: "head_visualeffects", + ID: "accessory_visualeffects", + Producer: "accessory_visualeffects", Dataset: "visualeffects", - Mode: "head_visualeffects", + Mode: "accessory_visualeffects", Root: "vfxs", Include: []string{"head_accessories/**/*.mdl", "head_decorations/**/*.mdl", "head_features/**/*.mdl"}, Derive: project.AutogenDeriveConfig{Kind: "model_stem", GroupFrom: "first_path_segment"}, @@ -7418,10 +7418,10 @@ func TestApplyAutogenConsumersUsesFolderDrivenSlashHeadVisualeffectsNames(t *tes p := testProject(root) p.Config.Autogen.Consumers = []project.AutogenConsumerConfig{ { - ID: "head_visualeffects", - Producer: "head_visualeffects", + ID: "accessory_visualeffects", + Producer: "accessory_visualeffects", Dataset: "visualeffects", - Mode: "head_visualeffects", + Mode: "accessory_visualeffects", Root: "vfxs", Include: []string{"head_features/**/*.mdl"}, Derive: project.AutogenDeriveConfig{Kind: "model_stem", GroupFrom: "first_path_segment"}, @@ -7509,10 +7509,10 @@ topdata: build: build/topdata autogen: consumers: - - id: head_visualeffects - producer: head_visualeffects + - id: accessory_visualeffects + producer: accessory_visualeffects dataset: visualeffects - mode: head_visualeffects + mode: accessory_visualeffects root: vfxs include: - chest_accessories/**/*.mdl @@ -7525,7 +7525,7 @@ autogen: asset_name: sow-head-vfx-manifest.json cache_name: sow-head-vfx-manifest.json local_override_root: autogen-assets - head_visualeffects: + accessory_visualeffects: groups: chest_accessories: prefix: chest_accessories @@ -12802,11 +12802,11 @@ func TestBuildNativeWithReleasedHeadVisualeffectsManifest(t *testing.T) { writeFile(t, filepath.Join(projRoot, "reference", "build.py"), "print('ok')\n") manifestJSON := `{ - "id": "head_visualeffects", + "id": "accessory_visualeffects", "repo": "ShadowsOverWestgate/sow-assets", "ref": "test-manifest", "generated_at": "2026-04-25T00:00:00Z", - "head_visualeffects": { + "accessory_visualeffects": { "groups": { "head_accessories": { "model_columns": ["Imp_HeadCon_Node", "Imp_Root_H_Node"] @@ -12872,10 +12872,10 @@ func TestBuildNativeWithReleasedHeadVisualeffectsManifest(t *testing.T) { Autogen: project.AutogenConfig{ Consumers: []project.AutogenConsumerConfig{ { - ID: "head_visualeffects", - Producer: "head_visualeffects", + ID: "accessory_visualeffects", + Producer: "accessory_visualeffects", Dataset: "visualeffects", - Mode: "head_visualeffects", + Mode: "accessory_visualeffects", Root: "vfxs", Include: []string{"head_accessories/**/*.mdl", "head_features/**/*.mdl"}, Derive: project.AutogenDeriveConfig{ @@ -12939,7 +12939,7 @@ func TestBuildNativePrefersRemoteReleasedHeadVisualeffectsManifestOverFreshCache cachePath := filepath.Join(projRoot, ".cache", "sow-head-vfx-manifest.json") writeFile(t, cachePath, `{ - "id": "head_visualeffects", + "id": "accessory_visualeffects", "generated_at": "2026-04-25T00:00:00Z", "entries": [ { @@ -12953,7 +12953,7 @@ func TestBuildNativePrefersRemoteReleasedHeadVisualeffectsManifestOverFreshCache setFileTime(t, cachePath, cacheTime) manifestJSON := `{ - "id": "head_visualeffects", + "id": "accessory_visualeffects", "repo": "ShadowsOverWestgate/sow-assets", "ref": "remote-manifest", "generated_at": "2026-04-25T12:00:00Z", @@ -12996,10 +12996,10 @@ func TestBuildNativePrefersRemoteReleasedHeadVisualeffectsManifestOverFreshCache Autogen: project.AutogenConfig{ Consumers: []project.AutogenConsumerConfig{ { - ID: "head_visualeffects", - Producer: "head_visualeffects", + ID: "accessory_visualeffects", + Producer: "accessory_visualeffects", Dataset: "visualeffects", - Mode: "head_visualeffects", + Mode: "accessory_visualeffects", Root: "vfxs", Include: []string{"head_accessories/**/*.mdl", "head_features/**/*.mdl"}, Derive: project.AutogenDeriveConfig{ @@ -13052,7 +13052,7 @@ func TestBuildPackageInvalidatesFreshAutogenCacheWhenReleaseAssetIsNewer(t *test setCompiledOutputTimes(t, result, outputTime) cachePath := filepath.Join(root, ".cache", "sow-head-vfx-manifest.json") - writeFile(t, cachePath, `{"id":"head_visualeffects","generated_at":"2026-04-25T10:00:00Z","entries":[{"group":"head_accessories","model_stem":"hfx_bandana","source":"head_accessories/hfx_bandana.mdl"}]}`+"\n") + writeFile(t, cachePath, `{"id":"accessory_visualeffects","generated_at":"2026-04-25T10:00:00Z","entries":[{"group":"head_accessories","model_stem":"hfx_bandana","source":"head_accessories/hfx_bandana.mdl"}]}`+"\n") setFileTime(t, cachePath, time.Now()) var server *httptest.Server @@ -13063,7 +13063,7 @@ func TestBuildPackageInvalidatesFreshAutogenCacheWhenReleaseAssetIsNewer(t *test _, _ = w.Write([]byte(`{"assets":[{"name":"sow-head-vfx-manifest.json","updated_at":"3026-04-25T12:00:00Z","browser_download_url":"` + server.URL + `/downloads/sow-head-vfx-manifest.json"}]}`)) case "/downloads/sow-head-vfx-manifest.json": w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"id":"head_visualeffects","entries":[{"group":"head_accessories","model_stem":"hfx_bandana","source":"head_accessories/hfx_bandana.mdl"}]}`)) + _, _ = w.Write([]byte(`{"id":"accessory_visualeffects","entries":[{"group":"head_accessories","model_stem":"hfx_bandana","source":"head_accessories/hfx_bandana.mdl"}]}`)) default: http.NotFound(w, r) } @@ -13075,10 +13075,10 @@ func TestBuildPackageInvalidatesFreshAutogenCacheWhenReleaseAssetIsNewer(t *test proj.Config.Autogen.Consumers = []project.AutogenConsumerConfig{ { - ID: "head_visualeffects", - Producer: "head_visualeffects", + ID: "accessory_visualeffects", + Producer: "accessory_visualeffects", Dataset: "visualeffects", - Mode: "head_visualeffects", + Mode: "accessory_visualeffects", Optional: true, Root: "vfxs", Include: []string{"head_accessories/**/*.mdl", "head_features/**/*.mdl"}, @@ -13400,10 +13400,10 @@ func TestBuildPackageFailsWhenAutogenManifestCacheIsStale(t *testing.T) { proj.Config.Autogen.Consumers = []project.AutogenConsumerConfig{ { - ID: "head_visualeffects", - Producer: "head_visualeffects", + ID: "accessory_visualeffects", + Producer: "accessory_visualeffects", Dataset: "visualeffects", - Mode: "head_visualeffects", + Mode: "accessory_visualeffects", Optional: true, Root: "vfxs", Include: []string{"head_accessories/**/*.mdl", "head_features/**/*.mdl"}, @@ -13419,7 +13419,7 @@ func TestBuildPackageFailsWhenAutogenManifestCacheIsStale(t *testing.T) { }, } cachePath := filepath.Join(root, ".cache", "sow-head-vfx-manifest.json") - writeFile(t, cachePath, `{"id":"head_visualeffects","entries":[{"group":"head_accessories","model_stem":"hfx_bandana","source":"head_accessories/hfx_bandana.mdl"}]}`+"\n") + writeFile(t, cachePath, `{"id":"accessory_visualeffects","entries":[{"group":"head_accessories","model_stem":"hfx_bandana","source":"head_accessories/hfx_bandana.mdl"}]}`+"\n") setFileTime(t, cachePath, time.Now().Add(-2*autogenManifestCacheMaxAge)) if _, err := BuildPackage(proj, nil); err == nil || !strings.Contains(err.Error(), "older than source file") { @@ -14348,10 +14348,10 @@ func testProject(root string) *project.Project { }, }, { - ID: "head_visualeffects", - Producer: "head_visualeffects", + ID: "accessory_visualeffects", + Producer: "accessory_visualeffects", Dataset: "visualeffects", - Mode: "head_visualeffects", + Mode: "accessory_visualeffects", Optional: true, Root: "vfxs", Include: []string{"head_accessories/**/*.mdl", "head_features/**/*.mdl"},