aboutsummaryrefslogtreecommitdiff
path: root/internal/validate/checks.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-06 12:57:23 -0400
committerhistoria <[not public]>2026-10-06 12:57:23 -0400
commit9666676766bf21364f4fc4d0d1d336e72f2a91b4 (patch)
tree0cade34091d7602790dc38cd44053f0b74bbadf2 /internal/validate/checks.go
parent4bc1daaf259e0c19c5fe4a12ec8bc7f6e5a735c1 (diff)
downloadthehouseoficarus-9666676766bf21364f4fc4d0d1d336e72f2a91b4.tar.gz
refactor: split casino.go, dedup helpers, remove audit docs
Diffstat (limited to 'internal/validate/checks.go')
-rw-r--r--internal/validate/checks.go64
1 files changed, 64 insertions, 0 deletions
diff --git a/internal/validate/checks.go b/internal/validate/checks.go
index c636b4f..a152956 100644
--- a/internal/validate/checks.go
+++ b/internal/validate/checks.go
@@ -816,6 +816,17 @@ func validateObjects(s Source) []Issue {
return issues
}
+var validEquipSlots = map[item.EquipSlot]bool{
+ item.SlotHead: true, item.SlotNeck: true, item.SlotTorso: true, item.SlotLegs: true,
+ item.SlotHands: true, item.SlotFeet: true, item.SlotBack: true, item.SlotAmmo: true,
+ item.SlotMainHand: true, item.SlotOffHand: true, item.SlotRing: true,
+}
+
+var validEquipTypes = map[item.EquipmentType]bool{
+ item.EquipMeleeWeapon: true, item.EquipRangedWeapon: true, item.EquipScienceWeapon: true,
+ item.EquipArmor: true, item.EquipTool: true, item.EquipAmmo: true,
+}
+
func validateItems(s Source) []Issue {
var issues []Issue
itemIDs := s.Items.IDSet()
@@ -842,6 +853,27 @@ func validateItems(s Source) []Issue {
})
}
+ // Equipment fields are free-form YAML; a typo'd slot/type/attack_type
+ // equips into an invisible slot or grants bonuses from nowhere.
+ if def.Equipment != nil {
+ if def.Equipment.Type != "" && !validEquipTypes[def.Equipment.Type] {
+ issues = append(issues, Issue{Level: "ERROR", Type: "config",
+ Message: fmt.Sprintf("Item %q: unknown equipment.type %q", id, def.Equipment.Type)})
+ }
+ if def.Equipment.Slot != "" && !validEquipSlots[def.Equipment.Slot] {
+ issues = append(issues, Issue{Level: "ERROR", Type: "config",
+ Message: fmt.Sprintf("Item %q: unknown equipment.slot %q", id, def.Equipment.Slot)})
+ }
+ if def.Equipment.AttackType != "" && !combat.IsValidAttackType(def.Equipment.AttackType) {
+ issues = append(issues, Issue{Level: "ERROR", Type: "config",
+ Message: fmt.Sprintf("Item %q: invalid equipment.attack_type %q", id, def.Equipment.AttackType)})
+ }
+ }
+ if def.HouseEdgePrecision != nil && *def.HouseEdgePrecision != 0 && *def.HouseEdgePrecision != 2 {
+ issues = append(issues, Issue{Level: "ERROR", Type: "config",
+ Message: fmt.Sprintf("Item %q: house_edge_precision must be 0 or 2, got %d", id, *def.HouseEdgePrecision)})
+ }
+
if len(def.Search) > 0 {
issues = append(issues, validateTriggerBlock(
fmt.Sprintf("Item %q: search", id), def.Search, itemIDs, roomIndex, mobIDs, dropIDs)...)
@@ -911,6 +943,16 @@ func validateCraftBlocks(s Source) []Issue {
craftLabel = c.Type + "/" + c.Subtype
}
+ // An empty ingredient list is unsatisfiable: planIngredients returns
+ // nil, so the craft can never produce output.
+ if len(c.Ingredients) == 0 {
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Item %q (craft: %s): has no ingredients; the recipe can never be satisfied", item.ID, craftLabel),
+ })
+ }
+
// The runtime selects the action by subtype (or type when no subtype
// is set), defaulting to combine. An unknown key leaves the action
// set but never advancing, so the player appears stuck.
@@ -1106,6 +1148,13 @@ func validateDropTables(s Source) []Issue {
}
for i, d := range dt.Drops {
+ if d.Weight <= 0 {
+ issues = append(issues, Issue{
+ Level: "WARN",
+ Type: "integrity",
+ Message: fmt.Sprintf("Drop table %q entry %d: weight %d is never selectable", id, i+1, d.Weight),
+ })
+ }
if d.ItemID != "" && !itemIDs[d.ItemID] {
issues = append(issues, Issue{
Level: "ERROR",
@@ -1314,6 +1363,21 @@ func validateFlagTriggerFiles(s Source) []Issue {
if t.Condition != nil {
issues = append(issues, validateCondition(rel, t.Condition, roomIndex, itemIDs)...)
}
+ // Flag triggers fire only on Condition/Steps/Lock; item_id is dead here.
+ if t.ItemID != "" {
+ issues = append(issues, Issue{
+ Level: "WARN",
+ Type: "trigger",
+ Message: fmt.Sprintf("trigger %s: item_id %q is ignored for flag triggers", rel, t.ItemID),
+ })
+ if !itemIDs[t.ItemID] {
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("trigger %s: item_id %q does not exist", rel, t.ItemID),
+ })
+ }
+ }
for si := range t.Steps {
issues = append(issues, validateStep(fmt.Sprintf("%s: step[%d]", rel, si), &t.Steps[si], itemIDs, roomIndex, mobIDs, dropIDs)...)
}