aboutsummaryrefslogtreecommitdiff
path: root/internal/validate/checks.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/validate/checks.go')
-rw-r--r--internal/validate/checks.go106
1 files changed, 106 insertions, 0 deletions
diff --git a/internal/validate/checks.go b/internal/validate/checks.go
index eb24721..a6eeee8 100644
--- a/internal/validate/checks.go
+++ b/internal/validate/checks.go
@@ -427,6 +427,16 @@ func validateMobs(s Source) []Issue {
})
}
+ // A weakness that matches no module element is dead data: the +weakness
+ // bonus can never trigger against any science module.
+ if s.ModuleElements != nil && def.Combat != nil && def.Combat.Stats.Defenses.Weakness != "" && !s.ModuleElements[def.Combat.Stats.Defenses.Weakness] {
+ issues = append(issues, Issue{
+ Level: "WARN",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: weakness %q matches no science module element (the weakness can never trigger)", id, def.Combat.Stats.Defenses.Weakness),
+ })
+ }
+
if !def.Protected && (def.Combat == nil || def.Combat.Stats.HP <= 0) {
hp := 0
if def.Combat != nil {
@@ -761,6 +771,15 @@ func validateCraftBlocks(s Source) []Issue {
}
for _, e := range c.Ingredients {
+ if len(e.Items) == 0 {
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Item %q (craft: %s): ingredient entry has no items; the recipe can never be satisfied",
+ item.ID, craftLabel),
+ })
+ continue
+ }
// Byproducts pair with the alternatives by index
// (collectByproducts reads Byproducts[j] for Items[j]), so a
// short or reordered list silently returns the wrong
@@ -1165,6 +1184,93 @@ func validateRoomWiring(s Source) []Issue {
})
}
+ // Reachability: an orphan check only catches rooms with zero inbound
+ // edges, so an entire disconnected component (every member linked only
+ // within the component) passes silently. BFS the directed exits from the
+ // configured root rooms and warn about anything with no path in, honouring
+ // ignore_unreachable.
+ reachable := make(map[int]bool)
+ var queue []int
+ for _, src := range s.RootRooms {
+ if roomIndex[src] && !reachable[src] {
+ reachable[src] = true
+ queue = append(queue, src)
+ }
+ }
+ // Transport module destinations are also legitimate entry points: a
+ // player can reach them with a module even when no exit path leads there,
+ // so they must not be reported as unreachable.
+ for _, dest := range s.ModuleDestinations {
+ if dest > 0 && roomIndex[dest] && !reachable[dest] {
+ reachable[dest] = true
+ queue = append(queue, dest)
+ }
+ }
+ for len(queue) > 0 {
+ id := queue[0]
+ queue = queue[1:]
+ room, err := s.World.LoadRoom(id)
+ if err != nil {
+ continue
+ }
+ for _, exit := range room.Exits {
+ next := exit.Room
+ if next <= 0 || reachable[next] || !roomIndex[next] {
+ continue
+ }
+ reachable[next] = true
+ queue = append(queue, next)
+ }
+ }
+ var unreachable []int
+ for id := range roomIndex {
+ if reachable[id] || ignoreTo[id] || sourceSet[id] {
+ continue
+ }
+ unreachable = append(unreachable, id)
+ }
+ if len(unreachable) > 0 {
+ sort.Ints(unreachable)
+ var strs []string
+ for _, id := range unreachable {
+ strs = append(strs, fmt.Sprintf("%d", id))
+ }
+ issues = append(issues, Issue{
+ Level: "WARN",
+ Type: "integrity",
+ Message: fmt.Sprintf("Unreachable rooms (no exit path from a configured root room): %s",
+ strings.Join(strs, ", ")),
+ })
+ }
+
+ return issues
+}
+
+// validateModuleDestinations flags a transport module whose destination room
+// does not exist: the game assigns the id unconditionally, so the player is
+// saved into a void with no exits.
+func validateModuleDestinations(s Source) []Issue {
+ if len(s.ModuleDestinations) == 0 {
+ return nil
+ }
+ roomIndex := s.World.RoomIndex()
+ ids := make([]string, 0, len(s.ModuleDestinations))
+ for id := range s.ModuleDestinations {
+ ids = append(ids, id)
+ }
+ sort.Strings(ids)
+ var issues []Issue
+ for _, id := range ids {
+ dest := s.ModuleDestinations[id]
+ if dest <= 0 || roomIndex[dest] {
+ continue
+ }
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Module %q: destination room %d does not exist", id, dest),
+ })
+ }
return issues
}