~bigbes/game-prototype-ftl

ref: 7d266a3ed0ff36694b042467963d582a7b694405 game-prototype-ftl/systems_test.go -rw-r--r-- 5.4 KiB
7d266a3e — Eugene Blikh fix(wasm): set go.env to empty object 6 days ago
                                                                                
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package main

import "testing"

// TestNewStartingSystems_layout verifies the 5-entry slice is indexed by
// int(RoomRole) and that each entry carries the correct role and cap.
func TestNewStartingSystems_layout(t *testing.T) {
	sys := NewStartingSystems()
	if len(sys) != 5 {
		t.Fatalf("expected 5 systems, got %d", len(sys))
	}

	type want struct {
		role     RoomRole
		maxLevel int
	}
	cases := []want{
		{RolePilot, 1},
		{RoleWeapons, 4},
		{RoleShields, 4},
		{RoleMedBay, 2},
		{RoleEngines, 4},
	}

	for _, w := range cases {
		idx := int(w.role)
		s := sys[idx]
		if s.Role != w.role {
			t.Errorf("sys[%d].Role = %v, want %v", idx, s.Role, w.role)
		}
		if s.MaxLevel != w.maxLevel {
			t.Errorf("sys[%d].MaxLevel = %d, want %d", idx, s.MaxLevel, w.maxLevel)
		}
		if s.PowerLevel != 0 {
			t.Errorf("sys[%d].PowerLevel = %d, want 0", idx, s.PowerLevel)
		}
	}
}

// TestReactorUsed_zero verifies that fresh systems report zero reactor usage.
func TestReactorUsed_zero(t *testing.T) {
	sys := NewStartingSystems()
	if got := reactorUsed(sys); got != 0 {
		t.Fatalf("reactorUsed on fresh systems = %d, want 0", got)
	}
}

// TestReactorUsed_sums verifies that reactorUsed returns the sum of all PowerLevels.
func TestReactorUsed_sums(t *testing.T) {
	sys := NewStartingSystems()
	// Directly set some levels for this test only (bypassing addPower to isolate the sum logic).
	sys[int(RolePilot)].PowerLevel = 1
	sys[int(RoleWeapons)].PowerLevel = 3
	sys[int(RoleEngines)].PowerLevel = 2
	want := 1 + 3 + 2
	if got := reactorUsed(sys); got != want {
		t.Fatalf("reactorUsed = %d, want %d", got, want)
	}
}

// TestAddPower_happy verifies a normal add when below cap and reactor has headroom.
func TestAddPower_happy(t *testing.T) {
	sys := NewStartingSystems()
	ok := addPower(sys, RoleWeapons, ReactorCap)
	if !ok {
		t.Fatal("addPower returned false, expected true")
	}
	if sys[int(RoleWeapons)].PowerLevel != 1 {
		t.Fatalf("PowerLevel = %d, want 1", sys[int(RoleWeapons)].PowerLevel)
	}
}

// TestAddPower_systemAtCap verifies that adding beyond MaxLevel is rejected.
func TestAddPower_systemAtCap(t *testing.T) {
	sys := NewStartingSystems()
	// Pilot cap is 1; add once to hit the cap.
	if !addPower(sys, RolePilot, ReactorCap) {
		t.Fatal("first addPower on Pilot should succeed")
	}
	if sys[int(RolePilot)].PowerLevel != 1 {
		t.Fatalf("expected level 1 after first add, got %d", sys[int(RolePilot)].PowerLevel)
	}
	// Now at cap — must be rejected.
	ok := addPower(sys, RolePilot, ReactorCap)
	if ok {
		t.Fatal("addPower at cap returned true, expected false")
	}
	if sys[int(RolePilot)].PowerLevel != 1 {
		t.Fatalf("PowerLevel changed after rejected add: got %d", sys[int(RolePilot)].PowerLevel)
	}
}

// TestAddPower_reactorFull verifies that adding power when reactor is exhausted is rejected.
func TestAddPower_reactorFull(t *testing.T) {
	sys := NewStartingSystems()
	// Fill reactor to cap using Weapons (cap 4) and Engines (cap 4).
	for i := 0; i < 4; i++ {
		if !addPower(sys, RoleWeapons, ReactorCap) {
			t.Fatalf("addPower Weapons step %d failed unexpectedly", i)
		}
	}
	for i := 0; i < 4; i++ {
		if !addPower(sys, RoleEngines, ReactorCap) {
			t.Fatalf("addPower Engines step %d failed unexpectedly", i)
		}
	}
	if reactorUsed(sys) != ReactorCap {
		t.Fatalf("expected reactor full (%d), got %d", ReactorCap, reactorUsed(sys))
	}
	// Shields is below cap but reactor is full — must be rejected.
	ok := addPower(sys, RoleShields, ReactorCap)
	if ok {
		t.Fatal("addPower with full reactor returned true, expected false")
	}
	if sys[int(RoleShields)].PowerLevel != 0 {
		t.Fatalf("PowerLevel changed on rejected add: got %d", sys[int(RoleShields)].PowerLevel)
	}
}

// TestAddPower_invariantsHold verifies IV3 and IV4 across all outcomes of addPower.
func TestAddPower_invariantsHold(t *testing.T) {
	checkInvariants := func(sys []System) {
		t.Helper()
		used := reactorUsed(sys)
		if used > ReactorCap {
			t.Errorf("IV3 violated: reactorUsed %d > ReactorCap %d", used, ReactorCap)
		}
		for i, s := range sys {
			if s.PowerLevel < 0 || s.PowerLevel > s.MaxLevel {
				t.Errorf("IV4 violated: sys[%d].PowerLevel %d not in [0, %d]", i, s.PowerLevel, s.MaxLevel)
			}
		}
	}

	roles := []RoomRole{RolePilot, RoleWeapons, RoleShields, RoleMedBay, RoleEngines}
	sys := NewStartingSystems()

	// Repeatedly try to add power to every system in a round-robin until the
	// reactor is full, then one more round of attempts (all should fail).
	for round := 0; round < 10; round++ {
		for _, r := range roles {
			addPower(sys, r, ReactorCap) //nolint:errcheck — we only care about invariants
			checkInvariants(sys)
		}
	}
}

// TestRemovePower_happy verifies that removing from a powered system decrements level.
func TestRemovePower_happy(t *testing.T) {
	sys := NewStartingSystems()
	if !addPower(sys, RoleShields, ReactorCap) {
		t.Fatal("addPower Shields failed")
	}
	ok := removePower(sys, RoleShields)
	if !ok {
		t.Fatal("removePower returned false, expected true")
	}
	if sys[int(RoleShields)].PowerLevel != 0 {
		t.Fatalf("PowerLevel = %d, want 0", sys[int(RoleShields)].PowerLevel)
	}
}

// TestRemovePower_zeroLevel verifies that removing from an unpowered system is rejected.
func TestRemovePower_zeroLevel(t *testing.T) {
	sys := NewStartingSystems()
	ok := removePower(sys, RoleEngines)
	if ok {
		t.Fatal("removePower at level 0 returned true, expected false")
	}
	if sys[int(RoleEngines)].PowerLevel != 0 {
		t.Fatalf("PowerLevel changed after rejected remove: got %d", sys[int(RoleEngines)].PowerLevel)
	}
}