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
|
package game
// Runtime room-ID migration regressions: Hub buckets, casino room keys,
// world room-keyed state, mob instances, and player state all move
// together through Game.MigrateRoomIDs (owner-executed).
import (
"testing"
"thehouseoficarus/internal/casino"
)
// TestMigrateRoomIDsMovesEverythingTogether: a live player in room 2, a
// casino machine in room 2, and seeded world state all migrate to room 4
// in one owner task; nothing stays attached to room 2.
func TestMigrateRoomIDsMovesEverythingTogether(t *testing.T) {
h := newHarness(t)
h.seedAccount("alice")
h.seedCharacter("alice", "alice")
sess, _ := h.connect("alice")
p := sess.Player
h.g.RunOnOwner(func() {
p.RoomID = 2
h.hub.EnterRoom(sess, 2)
// A machine in room 2 with a committed stake.
h.g.Casino.JoinMachine(2, casino.MachineConfig{ID: "slots", Game: casino.GameSlots, MinBet: 1}, "alice")
})
// Migrate 2 -> 4 through the full runtime entry point.
h.g.MigrateRoomIDs(map[int]int{2: 4})
h.barrier()
h.g.RunOnOwner(func() {
if p.RoomID != 4 {
t.Fatalf("player room = %d, want 4", p.RoomID)
}
if room := h.hub.PlayersInRoom(4); len(room) != 1 || room[0] != sess {
t.Fatalf("hub bucket for room 4 = %v, want [alice]", len(room))
}
if room := h.hub.PlayersInRoom(2); len(room) != 0 {
t.Fatalf("stale hub bucket for room 2: %d players", len(room))
}
if _, playing := h.g.Casino.Participation("alice"); !playing {
t.Fatal("casino participation lost during migration")
}
// The machine's events must route to room 4 now: emit one and
// check the room target.
machine, ok := h.g.Casino.MachineFor("alice")
if !ok {
t.Fatal("machine lost during migration")
}
if machine.RoomID != 4 {
t.Fatalf("machine room = %d, want 4", machine.RoomID)
}
})
}
// TestMigrateRoomIDsRoundTrip preserves participation both directions.
func TestMigrateRoomIDsRoundTrip(t *testing.T) {
h := newHarness(t)
h.seedAccount("alice")
h.seedCharacter("alice", "alice")
sess, _ := h.connect("alice")
p := sess.Player
h.g.RunOnOwner(func() {
p.RoomID = 3
h.hub.EnterRoom(sess, 3)
})
h.g.MigrateRoomIDs(map[int]int{3: 9})
h.g.RunOnOwner(func() {
if p.RoomID != 9 {
t.Fatalf("forward migration: player room = %d, want 9", p.RoomID)
}
})
h.g.MigrateRoomIDs(map[int]int{9: 3})
h.g.RunOnOwner(func() {
if p.RoomID != 3 {
t.Fatalf("inverse migration: player room = %d, want 3", p.RoomID)
}
if room := h.hub.PlayersInRoom(3); len(room) != 1 {
t.Fatalf("inverse migration: hub bucket room 3 = %d players", len(room))
}
})
}
// TestMigrateRoomIDsPersistsOnlinePlayer: the migrated player state is
// saved to disk (RoomID rewritten in the character file).
func TestMigrateRoomIDsPersistsOnlinePlayer(t *testing.T) {
h := newHarness(t)
h.seedAccount("alice")
h.seedCharacter("alice", "alice")
sess, _ := h.connect("alice")
p := sess.Player
h.g.RunOnOwner(func() {
p.RoomID = 2
p.Flags["hidden_exit_2_north"] = true
h.hub.EnterRoom(sess, 2)
})
h.g.MigrateRoomIDs(map[int]int{2: 4})
h.barrier()
loaded, err := h.g.AccountStore.LoadCharacter("alice")
if err != nil {
t.Fatal(err)
}
if loaded.RoomID != 4 {
t.Fatalf("saved room = %d, want 4", loaded.RoomID)
}
if _, ok := loaded.Flags["hidden_exit_4_north"]; !ok {
t.Fatalf("discovered-exit flag not rewritten: %v", loaded.Flags)
}
}
|