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
174
175
176
177
178
179
180
181
182
183
184
185
186
|
package world
import "testing"
// insertSentinel is the hypothetical new-room ID passed to InsertGridConflicts
// during tests. It must be a positive value not used by any real room in the
// fixture (BuildGrid skips exits whose target <= 0).
const insertSentinel = 9999
func TestInsertGridConflictsCleanNSEW(t *testing.T) {
dir := t.TempDir()
writeGridTestRoom(t, dir, 1, "exits:\n east: 2\n")
writeGridTestRoom(t, dir, 2, "exits:\n west: 1\n")
w := New(dir)
if c := InsertGridConflicts(1, East, 2, insertSentinel, loadGridRoom(w)); len(c) != 0 {
t.Errorf("clean east insert: expected no conflicts, got %+v", c)
}
}
func TestInsertGridConflictsCleanDiagonal(t *testing.T) {
dir := t.TempDir()
writeGridTestRoom(t, dir, 1, "exits:\n northeast: 2\n")
writeGridTestRoom(t, dir, 2, "exits:\n southwest: 1\n")
w := New(dir)
if c := InsertGridConflicts(1, Northeast, 2, insertSentinel, loadGridRoom(w)); len(c) != 0 {
t.Errorf("clean NE insert: expected no conflicts, got %+v", c)
}
}
func TestInsertGridConflictsCleanUpDown(t *testing.T) {
dir := t.TempDir()
writeGridTestRoom(t, dir, 1, "exits:\n up: 2\n")
writeGridTestRoom(t, dir, 2, "exits:\n down: 1\n")
w := New(dir)
if c := InsertGridConflicts(1, Up, 2, insertSentinel, loadGridRoom(w)); len(c) != 0 {
t.Errorf("clean up insert: expected no conflicts, got %+v", c)
}
}
// TestInsertGridConflictsOverlap: inserting on 1→east→2 would push room 3
// (beyond 2 to the east) from (2,0,0) to (3,0,0); a separate room 4 already at
// (3,0,0) via 1→north→5→east→4 collides with the pushed 3.
//
// 1 east→2 east→3 (3 at (2,0,0), pushed to (3,0,0))
// 1 north→5 east→4 (4 at (1,-1,0)? no: 5 at (0,-1,0), 4 at (1,-1,0))
//
// To land 4 at (3,0,0) we use 1→east is the insert axis, so place the blocker
// via a longer chain that resolves to (3,0,0): 1→north→5→east→6→east→7 lands
// 7 at (2,-1,0), not (3,0,0). Instead use a clean ring: 1→east→2 is the axis,
// and put 4 at (2,0,0) (3's current spot) — no, 3 is there.
//
// Simplest reliable overlap: 1→east→2→east→3 and 1→south→6→east→7→north→3 is
// a twist (3 reachable two ways). For an *overlap* on push, the pushed 3 must
// collide with a room NOT in the beyond-set. Place room 8 at (3,0,0) via a
// path that does not pass through 2: 1→north→5→north→8? that is (0,-2,0).
//
// Correct geometry for an overlap: 1 east→2 east→3 (3 at (2,0,0)). Insert on
// 1→east pushes 2→(2,0,0)? No — insert pushes the *beyond* set (rooms beyond
// the inserted room), i.e. 2 and everything reachable from 2 without going
// back through 1. Inserting between 1 and 2 pushes 2 (and 3) east by one:
// 2→(2,0,0), 3→(3,0,0). So we need a room already at (3,0,0) reachable from 1
// without using 1→east. 1→south→4→east→5→east→6 lands 6 at (2,1,0). Not it.
// 1→south→4→east→5→north→6 lands 6 at (1,0,0)? 4 at (0,1,0), 5 at (1,1,0),
// 6 at (1,0,0) — that's where 2 currently is → overlap with 2 after push? 2
// pushes to (2,0,0), 6 stays at (1,0,0): no collision. We need (3,0,0).
// 1→south→4→east→5→north→6→east→7: 4(0,1,0) 5(1,1,0) 6(1,0,0) 7(2,0,0) =
// collides with 3's *current* (2,0,0) → that's a pre-existing overlap (invalid
// world), not what we want.
//
// Use a longer chain to (3,0,0): 1→south→4(0,1,0)→east→5(1,1,0)→east→6(2,1,0)
// →north→7(2,0,0) collides with 3. So reach (3,0,0): 1→s→4→e→5→e→6→e→7(3,1,0)
// →n→8(3,0,0). Then pushing 3 to (3,0,0) collides with 8. 8 is not in the
// beyond-set (reachable from 1 via south, not via east/2), so it's an overlap.
func TestInsertGridConflictsOverlap(t *testing.T) {
dir := t.TempDir()
writeGridTestRoom(t, dir, 1, "exits:\n east: 2\n south: 4\n")
writeGridTestRoom(t, dir, 2, "exits:\n east: 3\n west: 1\n")
writeGridTestRoom(t, dir, 3, "exits:\n west: 2\n")
writeGridTestRoom(t, dir, 4, "exits:\n east: 5\n north: 1\n")
writeGridTestRoom(t, dir, 5, "exits:\n east: 6\n west: 4\n")
writeGridTestRoom(t, dir, 6, "exits:\n east: 7\n west: 5\n")
writeGridTestRoom(t, dir, 7, "exits:\n north: 8\n west: 6\n")
writeGridTestRoom(t, dir, 8, "exits:\n south: 7\n")
w := New(dir)
// Sanity: the pre-edit world must be clean.
if c := BuildGridConflicts(1, loadGridRoom(w)); len(c) != 0 {
t.Fatalf("precondition: world should be clean, got %+v", c)
}
c := InsertGridConflicts(1, East, 2, insertSentinel, loadGridRoom(w))
if !hasConflictKind(c, "overlap") {
t.Errorf("expected overlap from pushing 3 onto 8's cell, got %+v", c)
}
}
// TestInsertGridConflictsTwist: room 3 beyond 2 is also reachable from 1 via a
// separate path, so after the push 3 would have two candidate positions.
// 1 east→2 east→3 (3 at (2,0,0), pushed to (3,0,0))
// 1 south→4 east→5 north→3 (3 at (1,0,0))
// 3 is reachable two ways already → the pre-edit world is itself a twist. The
// insert helper must surface a twist conflict.
func TestInsertGridConflictsTwist(t *testing.T) {
dir := t.TempDir()
writeGridTestRoom(t, dir, 1, "exits:\n east: 2\n south: 4\n")
writeGridTestRoom(t, dir, 2, "exits:\n east: 3\n west: 1\n")
writeGridTestRoom(t, dir, 3, "exits:\n west: 2\n south: 5\n")
writeGridTestRoom(t, dir, 4, "exits:\n east: 5\n north: 1\n")
writeGridTestRoom(t, dir, 5, "exits:\n north: 3\n west: 4\n")
w := New(dir)
c := InsertGridConflicts(1, East, 2, insertSentinel, loadGridRoom(w))
if !hasConflictKind(c, "twist") {
t.Errorf("expected a twist conflict, got %+v", c)
}
}
// TestInsertGridConflictsOneWayFarReciprocal: when the far room's opposite
// exit does NOT point back at the source, insert performs a one-way insertion
// (only the near side is rewired). The helper must model that and still allow a
// geometrically clean insert.
func TestInsertGridConflictsOneWayFarReciprocal(t *testing.T) {
dir := t.TempDir()
writeGridTestRoom(t, dir, 1, "exits:\n east: 2\n")
writeGridTestRoom(t, dir, 2, "exits:\n") // no west exit back to 1
w := New(dir)
if c := InsertGridConflicts(1, East, 2, insertSentinel, loadGridRoom(w)); len(c) != 0 {
t.Errorf("one-way insert should be clean, got %+v", c)
}
}
func TestRemoveGridConflictsCleanPull(t *testing.T) {
dir := t.TempDir()
// 1 east→2 east→3, with reciprocals. Removing 2 pulls 3 to (1,0,0). Clean.
writeGridTestRoom(t, dir, 1, "exits:\n east: 2\n")
writeGridTestRoom(t, dir, 2, "exits:\n east: 3\n west: 1\n")
writeGridTestRoom(t, dir, 3, "exits:\n west: 2\n")
w := New(dir)
if c := RemoveGridConflicts(1, East, loadGridRoom(w)); len(c) != 0 {
t.Errorf("clean pull should produce no conflicts, got %+v", c)
}
}
func TestRemoveGridConflictsDiagonalPull(t *testing.T) {
dir := t.TempDir()
writeGridTestRoom(t, dir, 1, "exits:\n northeast: 2\n")
writeGridTestRoom(t, dir, 2, "exits:\n northeast: 3\n southwest: 1\n")
writeGridTestRoom(t, dir, 3, "exits:\n southwest: 2\n")
w := New(dir)
if c := RemoveGridConflicts(1, Northeast, loadGridRoom(w)); len(c) != 0 {
t.Errorf("clean diagonal pull should produce no conflicts, got %+v", c)
}
}
// TestRemoveGridConflictsOverlap mirrors TestBuildGridConflictsHypotheticalRemoveOverlap:
// removing 2 pulls 6 onto 5's cell.
func TestRemoveGridConflictsOverlap(t *testing.T) {
dir := t.TempDir()
writeGridTestRoom(t, dir, 1, "exits:\n east: 2\n south: 4\n")
writeGridTestRoom(t, dir, 2, "exits:\n east: 3\n west: 1\n")
writeGridTestRoom(t, dir, 3, "exits:\n south: 6\n west: 2\n")
writeGridTestRoom(t, dir, 4, "exits:\n east: 5\n north: 1\n")
writeGridTestRoom(t, dir, 5, "exits:\n west: 4\n")
writeGridTestRoom(t, dir, 6, "exits:\n north: 3\n")
w := New(dir)
if c := BuildGridConflicts(1, loadGridRoom(w)); len(c) != 0 {
t.Fatalf("precondition: world should be clean, got %+v", c)
}
c := RemoveGridConflicts(1, East, loadGridRoom(w))
if !hasConflictKind(c, "overlap") {
t.Errorf("expected overlap from pulling 6 onto 5's cell, got %+v", c)
}
}
// TestRemoveGridConflictsDeadEnd: when the room being removed has no forward
// exit, the helper models a plain deletion (A's dir exit removed). In a clean
// world this cannot introduce a conflict.
func TestRemoveGridConflictsDeadEnd(t *testing.T) {
dir := t.TempDir()
writeGridTestRoom(t, dir, 1, "exits:\n east: 2\n")
writeGridTestRoom(t, dir, 2, "exits:\n west: 1\n")
w := New(dir)
if c := RemoveGridConflicts(1, East, loadGridRoom(w)); len(c) != 0 {
t.Errorf("dead-end removal should produce no conflicts, got %+v", c)
}
}
|