aboutsummaryrefslogtreecommitdiff
path: root/internal/validate/validate.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-18 01:50:17 -0400
committerhistoria <[not public]>2026-09-18 01:50:17 -0400
commit87d08f0151e643951769e323995f1c5ed89ee200 (patch)
treedc42a0b30ebb5a00dd7ba505cc4eaa44f4f308bc /internal/validate/validate.go
parent32a3c59cebf2a16277e54d715a67adfdbe00b456 (diff)
downloadthehouseoficarus-87d08f0151e643951769e323995f1c5ed89ee200.tar.gz
fix: harden economy recovery, shutdown concurrency, world validation, and inventory-loss paths
Diffstat (limited to 'internal/validate/validate.go')
-rw-r--r--internal/validate/validate.go79
1 files changed, 79 insertions, 0 deletions
diff --git a/internal/validate/validate.go b/internal/validate/validate.go
index 735c9ef..8ca4555 100644
--- a/internal/validate/validate.go
+++ b/internal/validate/validate.go
@@ -6,13 +6,18 @@
package validate
import (
+ "bytes"
"fmt"
"os"
"path/filepath"
"sort"
+ "strconv"
"strings"
+ "gopkg.in/yaml.v3"
+
"thehouseoficarus/internal/behavior"
+ "thehouseoficarus/internal/cardart"
"thehouseoficarus/internal/item"
"thehouseoficarus/internal/object"
"thehouseoficarus/internal/world"
@@ -67,6 +72,9 @@ func Run(s Source) []Issue {
issues = append(issues, validateIDs(s.DataDir, dir.sub, dir.label)...)
}
+ issues = append(issues, validateKnownFields(s.DataDir)...)
+ issues = append(issues, validateRoomFilenames(s.DataDir)...)
+
issues = append(issues, validateRooms(s)...)
issues = append(issues, validateMobs(s)...)
issues = append(issues, validateHazards(s)...)
@@ -84,6 +92,77 @@ func Run(s Source) []Issue {
return issues
}
+// validateRoomFilenames flags room files whose name is not a plain integer:
+// the room index only accepts numeric stems, so a file like
+// "1001_copy.yaml" would otherwise be silently ignored (and, before the
+// strconv fix, could shadow the real room 1001).
+func validateRoomFilenames(dataDir string) []Issue {
+ dir := filepath.Join(dataDir, "rooms")
+ var issues []Issue
+ _ = behavior.WalkYAMLDir(dir, func(path, id string, data []byte) error {
+ if _, err := strconv.Atoi(id); err != nil {
+ rel := strings.TrimPrefix(path, dataDir+"/")
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "integrity",
+ Message: fmt.Sprintf("room %s: filename is not a numeric room ID and the file is ignored", rel),
+ })
+ }
+ return nil
+ })
+ return issues
+}
+
+// validateKnownFields decodes every authored data file with KnownFields(true)
+// so a misspelled or stale YAML key is a startup ERROR instead of being
+// silently discarded by yaml.Unmarshal. Only package-owned content types are
+// covered (world/object/item/behavior); game-owned definitions are checked by
+// their own loaders.
+func validateKnownFields(dataDir string) []Issue {
+ targets := []struct {
+ subDir string
+ label string
+ newDef func() any
+ }{
+ {"rooms", "room", func() any { return new(world.Room) }},
+ {"mobs", "mob", func() any { return new(world.MobDef) }},
+ {"objects", "object", func() any { return new(object.ObjectDef) }},
+ {"items", "item", func() any { return new(item.ItemDef) }},
+ {"drops", "drop table", func() any { return new(behavior.DropTableDef) }},
+ {"hazards", "hazard", func() any { return new(world.HazardDef) }},
+ }
+ var issues []Issue
+ for _, t := range targets {
+ dir := filepath.Join(dataDir, t.subDir)
+ _ = behavior.WalkYAMLDir(dir, func(path, id string, data []byte) error {
+ dec := yaml.NewDecoder(bytes.NewReader(data))
+ dec.KnownFields(true)
+ if err := dec.Decode(t.newDef()); err != nil {
+ rel := strings.TrimPrefix(path, dataDir+"/")
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "unknown-field",
+ Message: fmt.Sprintf("%s %s: %v", t.label, rel, err),
+ })
+ }
+ return nil
+ })
+ }
+ // cards.yaml lives at the data root rather than a subdirectory.
+ if data, err := os.ReadFile(filepath.Join(dataDir, "cards.yaml")); err == nil {
+ dec := yaml.NewDecoder(bytes.NewReader(data))
+ dec.KnownFields(true)
+ if err := dec.Decode(new(cardart.Store)); err != nil {
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "unknown-field",
+ Message: fmt.Sprintf("cards.yaml: %v", err),
+ })
+ }
+ }
+ return issues
+}
+
func validateIDs(dataDir, subDir, label string) []Issue {
dir := filepath.Join(dataDir, subDir)
dups := behavior.CheckDuplicateIDs(dir)