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", "pw", false) p := 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", "pw", false) 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", "pw", false) 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) } }