{"record":{"id":"3d67bf3cb1111671","repo":"ppy/osu","slug":"accessing-the-client-side-room-via-nameof-testmul","errorCode":null,"errorMessage":"Accessing the client-side room via {nameof(TestMultiplayerClient)} is unsafe. Use {nameof(ClientRoom)} if this was intended.","messagePattern":"Accessing the client-side room via (.+?) is unsafe\\. Use (.+?) if this was intended\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs","lineNumber":67,"sourceCode":"        /// </summary>\r\n        public MultiplayerRoom? ClientRoom => base.Room;\r\n\r\n        /// <summary>\r\n        /// The server's <see cref=\"Room\"/>. This is always up-to-date.\r\n        /// </summary>\r\n        public Room? ServerAPIRoom { get; private set; }\r\n\r\n        /// <summary>\r\n        /// The server's <see cref=\"MultiplayerRoom\"/>. This is always up-to-date.\r\n        /// </summary>\r\n        public MultiplayerRoom? ServerRoom { get; private set; }\r\n\r\n        [Obsolete]\r\n        protected new Room APIRoom => throw new InvalidOperationException($\"Accessing the client-side API room via {nameof(TestMultiplayerClient)} is unsafe. \"\r\n                                                                          + $\"Use {nameof(ClientAPIRoom)} if this was intended.\");\r\n\r\n        [Obsolete]\r\n        public new MultiplayerRoom Room => throw new InvalidOperationException($\"Accessing the client-side room via {nameof(TestMultiplayerClient)} is unsafe. \"\r\n                                                                               + $\"Use {nameof(ClientRoom)} if this was intended.\");\r\n\r\n        public new MultiplayerRoomUser? LocalUser => ServerRoom?.Users.SingleOrDefault(u => u.User?.Id == API.LocalUser.Value.Id);\r\n\r\n        public Action<MultiplayerRoom>? RoomSetupAction;\r\n\r\n        public bool RoomJoined { get; private set; }\r\n\r\n        [Resolved]\r\n        private IAPIProvider api { get; set; } = null!;\r\n\r\n        private MultiplayerPlaylistItem? currentItem => ServerRoom?.Playlist[currentIndex];\r\n        private int currentIndex;\r\n        private long lastPlaylistItemId;\r\n        private int lastCountdownId;\r\n\r\n        private readonly Dictionary<int, long> matchmakingUserPicks = new Dictionary<int, long>();\r\n\r","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/ppy/osu/blob/d9c73e12adff2feaae4a3e158d36fe5883faf6ca/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs#L49-L85","documentation":"Same disambiguation guard as the API Room, but for the in-memory MultiplayerRoom state. TestMultiplayerClient maintains a server-side MultiplayerRoom (ServerRoom) and an inherited client-side one (exposed as ClientRoom). The base MultiplayerClient.Room is shadowed with `new` + [Obsolete] and throws so tests do not accidentally assert against the wrong room object.","triggerScenarios":"Reading `client.Room` (the inherited public property) instead of ClientRoom (client view) or ServerRoom (server view) in test code.","commonSituations":"Porting code that used MultiplayerClient.Room into a test; autocomplete selecting the shadowed member; assuming Room is the server state.","solutions":["Use ClientRoom for the client's received MultiplayerRoom state.","Use ServerRoom for the authoritative server-side MultiplayerRoom.","Remember ClientRoom already wraps base.Room, so it is the safe equivalent of the old property."],"exampleFix":"// before\nMultiplayerRoom? room = multiplayerClient.Room;\n\n// after\nMultiplayerRoom? room = multiplayerClient.ServerRoom; // authoritative server view\n// or: multiplayerClient.ClientRoom; // client view","handlingStrategy":"validation","validationCode":"// never read the shadowed base Room; pick the explicit view up front\nMultiplayerRoom? room = wantServerView ? multiplayerClient.ServerRoom : multiplayerClient.ClientRoom;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat the [Obsolete] throw as guidance, not something to swallow in try/catch.","Assert against ServerRoom when you want authoritative state; ClientRoom for what the client received.","Audit test helpers for `.Room` access on a TestMultiplayerClient."],"tags":["test-infra","multiplayer","obsolete","property-shadowing"],"backgroundTag":null,"analyzedSha":"d9c73e12adff2feaae4a3e158d36fe5883faf6ca","analyzedAt":"2026-08-13T14:12:54.015Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}