1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
package validate
import (
"path/filepath"
"testing"
"thehouseoficarus/internal/item"
)
// Regression: validateCraftBlocks must validate byproducts, the required tool,
// and success/fail coherence. A typo in a byproduct previously passed startup
// validation and the runtime wrote a phantom inventory slot with no item
// definition; a tool that names no item was also unchecked; and a fail product
// or fail_message on a recipe with no success formula can never be reached
// (the engine hardcodes a 100% success chance).
func writeCraftItem(t *testing.T, dir, name, body string) {
t.Helper()
writeFile(t, filepath.Join(dir, "items", name+".yaml"), body)
}
func TestCraftByproductMustExist(t *testing.T) {
dir := t.TempDir()
writeCraftItem(t, dir, "test_in", "id: test_in\nname: test in\n")
writeCraftItem(t, dir, "test_out", `id: test_out
name: test out
craft:
- type: crafting
ingredients:
- items: [test_in]
quantity: 1
byproducts: [missing_byproduct]
`)
issues := validateCraftBlocks(newSource(dir))
if !containsMsg(issues, "nonexistent byproduct") {
t.Fatalf("expected nonexistent-byproduct error, got: %+v", issues)
}
}
func TestCraftToolMustExist(t *testing.T) {
dir := t.TempDir()
writeCraftItem(t, dir, "test_in", "id: test_in\nname: test in\n")
writeCraftItem(t, dir, "test_out", `id: test_out
name: test out
craft:
- type: crafting
tool: missing_tool
ingredients:
- items: [test_in]
quantity: 1
`)
issues := validateCraftBlocks(newSource(dir))
if !containsMsg(issues, "nonexistent tool") {
t.Fatalf("expected nonexistent-tool error, got: %+v", issues)
}
}
func TestCraftFailWithoutSuccessFormulaIsWarned(t *testing.T) {
dir := t.TempDir()
writeCraftItem(t, dir, "test_in", "id: test_in\nname: test in\n")
writeCraftItem(t, dir, "burnt", "id: burnt\nname: burnt\n")
writeCraftItem(t, dir, "test_out", `id: test_out
name: test out
craft:
- type: cooking
fail: burnt
fail_message: You burn it.
ingredients:
- items: [test_in]
quantity: 1
`)
issues := validateCraftBlocks(newSource(dir))
if !containsMsg(issues, "unreachable") {
t.Fatalf("expected unreachable fail warning, got: %+v", issues)
}
for _, issue := range issues {
if issue.Level == "ERROR" {
t.Fatalf("dead fail data must not fail startup validation: %+v", issue)
}
}
}
func TestCraftReachableFailProducesNoIssue(t *testing.T) {
dir := t.TempDir()
writeCraftItem(t, dir, "test_in", "id: test_in\nname: test in\n")
writeCraftItem(t, dir, "burnt", "id: burnt\nname: burnt\n")
writeCraftItem(t, dir, "test_out", `id: test_out
name: test out
craft:
- type: cooking
fail: burnt
fail_message: You burn it.
success:
base: 0.5
cap: 0.95
per_level: 0.02
ingredients:
- items: [test_in]
quantity: 1
`)
if issues := validateCraftBlocks(newSource(dir)); len(issues) != 0 {
t.Fatalf("reachable fail branch must validate cleanly, got: %+v", issues)
}
}
func TestCraftFailureReachableFormula(t *testing.T) {
cases := []struct {
name string
base float64
perLvl float64
cap float64
nilF bool
want bool
}{
{name: "no formula", nilF: true, want: false},
{name: "50% base", base: 0.5, perLvl: 0.02, cap: 0.95, want: true},
{name: "always succeeds", base: 1.0, perLvl: 0, cap: 1.0, want: false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
craft := &item.CraftDef{Level: 1}
if !c.nilF {
craft.Success = &item.SuccessFormula{Base: c.base, PerLevel: c.perLvl, Cap: c.cap}
}
if got := craftFailureReachable(craft); got != c.want {
t.Errorf("craftFailureReachable = %v, want %v", got, c.want)
}
})
}
}
|